import 'content_block.dart'; import 'token_usage.dart'; class LogEntry { final String? uuid; final String? parentUuid; final String? sessionId; final DateTime? timestamp; final String? cwd; final String? version; final String? gitBranch; final String type; final bool isSidechain; final Map raw; LogEntry({ this.uuid, this.parentUuid, this.sessionId, this.timestamp, this.cwd, this.version, this.gitBranch, required this.type, this.isSidechain = false, required this.raw, }); factory LogEntry.fromJson(Map json) { final type = json['type'] as String? ?? 'unknown'; switch (type) { case 'user': return UserEntry.fromJson(json); case 'assistant': return AssistantEntry.fromJson(json); case 'system': return SystemEntry.fromJson(json); case 'progress': return ProgressEntry.fromJson(json); case 'file-history-snapshot': return FileSnapshotEntry.fromJson(json); default: return _baseFromJson(json, type); } } static LogEntry _baseFromJson(Map json, String type) { return LogEntry( uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: _parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, type: type, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } static DateTime? _parseTimestamp(dynamic ts) { if (ts is String) return DateTime.tryParse(ts); return null; } } class UserEntry extends LogEntry { final dynamic content; UserEntry({ required this.content, required super.uuid, required super.parentUuid, required super.sessionId, required super.timestamp, required super.cwd, required super.version, required super.gitBranch, required super.isSidechain, required super.raw, }) : super(type: 'user'); String get promptText { if (content is String) return content; return ''; } bool get isToolResult => content is List; List get toolResults { if (content is! List) return []; return (content as List) .where((c) => c is Map && c['type'] == 'tool_result') .map((c) => ToolResultData.fromJson(c as Map)) .toList(); } factory UserEntry.fromJson(Map json) { final message = json['message'] as Map? ?? {}; return UserEntry( content: message['content'], uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: LogEntry._parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } } class AssistantEntry extends LogEntry { final String? model; final String? messageId; final List contentBlocks; final TokenUsage? usage; final String? stopReason; AssistantEntry({ this.model, this.messageId, required this.contentBlocks, this.usage, this.stopReason, required super.uuid, required super.parentUuid, required super.sessionId, required super.timestamp, required super.cwd, required super.version, required super.gitBranch, required super.isSidechain, required super.raw, }) : super(type: 'assistant'); List get textBlocks => contentBlocks.whereType().toList(); List get thinkingBlocks => contentBlocks.whereType().toList(); List get toolUseBlocks => contentBlocks.whereType().toList(); bool get hasText => textBlocks.isNotEmpty; bool get hasThinking => thinkingBlocks.isNotEmpty; bool get hasToolUse => toolUseBlocks.isNotEmpty; factory AssistantEntry.fromJson(Map json) { final message = json['message'] as Map? ?? {}; final contentList = message['content'] as List? ?? []; final usageJson = message['usage'] as Map?; return AssistantEntry( model: message['model'] as String?, messageId: message['id'] as String?, contentBlocks: contentList .whereType>() .map((c) => ContentBlock.fromJson(c)) .toList(), usage: usageJson != null ? TokenUsage.fromJson(usageJson) : null, stopReason: message['stop_reason'] as String?, uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: LogEntry._parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } } class SystemEntry extends LogEntry { final String? subtype; final int? durationMs; final String? slug; SystemEntry({ this.subtype, this.durationMs, this.slug, required super.uuid, required super.parentUuid, required super.sessionId, required super.timestamp, required super.cwd, required super.version, required super.gitBranch, required super.isSidechain, required super.raw, }) : super(type: 'system'); factory SystemEntry.fromJson(Map json) { return SystemEntry( subtype: json['subtype'] as String?, durationMs: json['durationMs'] as int?, slug: json['slug'] as String?, uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: LogEntry._parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } } class ProgressEntry extends LogEntry { final String? slug; final String? toolUseID; final String? parentToolUseID; final Map data; ProgressEntry({ this.slug, this.toolUseID, this.parentToolUseID, required this.data, required super.uuid, required super.parentUuid, required super.sessionId, required super.timestamp, required super.cwd, required super.version, required super.gitBranch, required super.isSidechain, required super.raw, }) : super(type: 'progress'); Map? get progressMessage => data['message'] as Map?; factory ProgressEntry.fromJson(Map json) { return ProgressEntry( slug: json['slug'] as String?, toolUseID: json['toolUseID'] as String?, parentToolUseID: json['parentToolUseID'] as String?, data: (json['data'] as Map?) ?? {}, uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: LogEntry._parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } } class FileSnapshotEntry extends LogEntry { final String? messageId; final Map snapshot; final bool isSnapshotUpdate; FileSnapshotEntry({ this.messageId, required this.snapshot, this.isSnapshotUpdate = false, required super.uuid, required super.parentUuid, required super.sessionId, required super.timestamp, required super.cwd, required super.version, required super.gitBranch, required super.isSidechain, required super.raw, }) : super(type: 'file-history-snapshot'); factory FileSnapshotEntry.fromJson(Map json) { return FileSnapshotEntry( messageId: json['messageId'] as String?, snapshot: (json['snapshot'] as Map?) ?? {}, isSnapshotUpdate: json['isSnapshotUpdate'] as bool? ?? false, uuid: json['uuid'] as String?, parentUuid: json['parentUuid'] as String?, sessionId: json['sessionId'] as String?, timestamp: LogEntry._parseTimestamp(json['timestamp']), cwd: json['cwd'] as String?, version: json['version'] as String?, gitBranch: json['gitBranch'] as String?, isSidechain: json['isSidechain'] as bool? ?? false, raw: json, ); } }