SessionRecord.fromJson constructor

SessionRecord.fromJson(
  1. Map<String, dynamic> json
)

Deserializes a record JSON map, dispatching on type.

Throws FormatException for unknown types or invalid base fields, mirroring pi's parseEntryLine validation.

Implementation

factory SessionRecord.fromJson(Map<String, dynamic> json) {
  FormatException invalid(String message) =>
      FormatException('Invalid session record: $message');
  final type = json['type'];
  if (type is! String) throw invalid('missing entry type');
  final id = json['id'];
  if (id is! String || id.isEmpty) throw invalid('missing entry id');
  final parentId = json['parentId'];
  if (parentId != null && parentId is! String) {
    throw invalid('invalid parentId');
  }
  final timestampRaw = json['timestamp'];
  if (timestampRaw is! String || timestampRaw.isEmpty) {
    throw invalid('missing timestamp');
  }
  final timestamp = DateTime.parse(timestampRaw);
  return switch (type) {
    'message' => MessageRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      message: messageFromJson(
        (json['message'] as Map?)?.cast<String, dynamic>() ??
            const <String, dynamic>{},
      ),
    ),
    'thinking_level_change' => ThinkingLevelChangeRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      thinkingLevel: json['thinkingLevel'] as String? ?? 'off',
    ),
    'model_change' => ModelChangeRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      provider: json['provider'] as String? ?? '',
      modelId: json['modelId'] as String? ?? '',
      baseUrl: json['baseUrl'] as String?,
      customProvider: json['customProvider'] as String?,
    ),
    'active_tools_change' => ActiveToolsChangeRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      activeToolNames: [
        for (final name in (json['activeToolNames'] as List?) ?? const [])
          name as String,
      ],
    ),
    'compaction' => CompactionRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      summary: json['summary'] as String? ?? '',
      firstKeptEntryId: json['firstKeptEntryId'] as String? ?? '',
      tokensBefore: json['tokensBefore'] as int? ?? 0,
      details: json['details'],
      fromHook: json['fromHook'] as bool?,
    ),
    'branch_summary' => BranchSummaryRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      fromId: json['fromId'] as String? ?? '',
      summary: json['summary'] as String? ?? '',
      details: json['details'],
      fromHook: json['fromHook'] as bool?,
    ),
    'custom' => CustomRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      customType: json['customType'] as String? ?? '',
      data: json['data'],
    ),
    'checkpoint' => CheckpointRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      messageCount: json['messageCount'] as int? ?? 0,
      goal: json['goal'] as String?,
    ),
    'custom_message' => CustomMessageRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      customType: json['customType'] as String? ?? '',
      content: _parseCustomMessageContent(json['content']),
      display: json['display'] as bool? ?? false,
      details: json['details'],
    ),
    'label' => LabelRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      targetId: json['targetId'] as String? ?? '',
      label: json['label'] as String?,
    ),
    'session_info' => SessionInfoRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      name: json['name'] as String?,
    ),
    'hidden_range' => HiddenRangeRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      recordIds: [
        for (final recordId in (json['recordIds'] as List?) ?? const [])
          recordId as String,
      ],
    ),
    'compact_checkpoint' => CompactCheckpointRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      firstRecordId: json['firstRecordId'] as String? ?? '',
      lastRecordId: json['lastRecordId'] as String? ?? '',
      text: json['text'] as String? ?? '',
      coversRecordIds: [
        for (final recordId in (json['coversRecordIds'] as List?) ?? const [])
          recordId as String,
      ],
      flattenedRecordIds: [
        for (final recordId
            in (json['flattenedRecordIds'] as List?) ?? const [])
          recordId as String,
      ],
    ),
    'leaf' => LeafRecord(
      id: id,
      parentId: parentId as String?,
      timestamp: timestamp,
      targetId: json['targetId'] as String?,
    ),
    _ => throw invalid('unknown entry type: $type'),
  };
}