SessionRecord.fromJson constructor
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'),
};
}