const std = @import("std");const session = @import("session.zig");const session_usage = @import("session_usage.zig");const session_permission_state = @import("../permissions/session_permission_state.zig");const types = @import("../shared/types.zig");const captured_command = @import("../tooling/captured_command.zig"); const Allocator = std.mem.Allocator;const Sha256 = std.crypto.hash.sha2.Sha256; pub const DurableSessionPreferences = struct { model: []u8, effort: types.ReasoningEffort, fast_mode: bool, pub fn deinit(self: *DurableSessionPreferences, alloc: Allocator) void { alloc.free(self.model); self.* = undefined; } pub fn dupe(self: DurableSessionPreferences, alloc: Allocator) !DurableSessionPreferences { return .{ .model = try alloc.dupe(u8, self.model), .effort = self.effort, .fast_mode = self.fast_mode, }; }}; pub const RecoveryToolState = enum { none, proven_unexecuted, confirmed, uncertain,}; pub const RecoveryCheckpoint = struct { version: u8 = 1, turn_id: u64, user: session.UserTurn, assistant_source: []u8, execution: session.ExecutionMemory = .{}, cause: types.ModelRecoveryCause, action: types.ModelRecoveryAction, tool_state: RecoveryToolState = .none, route_model: []u8, requested_fast_mode: bool, fast_mode: bool, max_provider_attempts: usize, consumed_provider_attempts: usize, outstanding_reservation: bool = false, pub fn deinit(self: *RecoveryCheckpoint, alloc: Allocator) void { session.freeUserTurn(alloc, self.user); alloc.free(self.assistant_source); session.freeExecutionMemory(alloc, self.execution); alloc.free(self.route_model); self.* = undefined; } pub fn dupe(self: RecoveryCheckpoint, alloc: Allocator) !RecoveryCheckpoint { const user = try session.dupeUserTurn(alloc, self.user); errdefer session.freeUserTurn(alloc, user); const assistant_source = try alloc.dupe(u8, self.assistant_source); errdefer alloc.free(assistant_source); const execution = try types.dupeExecutionMemory(alloc, self.execution); errdefer session.freeExecutionMemory(alloc, execution); const route_model = try alloc.dupe(u8, self.route_model); return .{ .version = self.version, .turn_id = self.turn_id, .user = user, .assistant_source = assistant_source, .execution = execution, .cause = self.cause, .action = self.action, .tool_state = self.tool_state, .route_model = route_model, .requested_fast_mode = self.requested_fast_mode, .fast_mode = self.fast_mode, .max_provider_attempts = self.max_provider_attempts, .consumed_provider_attempts = self.consumed_provider_attempts, .outstanding_reservation = self.outstanding_reservation, }; }}; pub const DurableSessionState = struct { id: []u8, origin_workspace_root: []u8, workspace_root: []u8, created_at_ms: i64, updated_at_ms: i64, conversation_language: session.ConversationLanguage, preferences: DurableSessionPreferences, history: []session.HistoryTurn, context_history_start: usize = 0, total_input_tokens: u64, total_output_tokens: u64, permission_state: session_permission_state.State = .{}, /// Last ordinary history commit correlated to durable subagent work. This /// control-only marker is not included in model history. last_subagent_work_id: ?[]u8 = null, /// Null only for sessions written before durable usage accounting. usage: ?session_usage.Snapshot = null, /// Active control state. It is never projected into model history and a /// restored checkpoint requires explicit host authority before any send. recovery_checkpoint: ?RecoveryCheckpoint = null, pub fn deinit(self: *DurableSessionState, alloc: Allocator) void { alloc.free(self.id); alloc.free(self.origin_workspace_root); alloc.free(self.workspace_root); self.preferences.deinit(alloc); session.freeHistoryTurnSlice(alloc, self.history); self.permission_state.deinit(alloc); if (self.last_subagent_work_id) |id| alloc.free(id); if (self.usage) |*usage| usage.deinit(alloc); if (self.recovery_checkpoint) |*checkpoint| checkpoint.deinit(alloc); self.* = undefined; } pub fn dupe(self: DurableSessionState, alloc: Allocator) !DurableSessionState { const id = try alloc.dupe(u8, self.id); errdefer alloc.free(id); const origin_workspace_root = try alloc.dupe(u8, self.origin_workspace_root); errdefer alloc.free(origin_workspace_root); const workspace_root = try alloc.dupe(u8, self.workspace_root); errdefer alloc.free(workspace_root); const preferences = try self.preferences.dupe(alloc); errdefer { var owned_preferences = preferences; owned_preferences.deinit(alloc); } const history = try dupeHistory(alloc, self.history); errdefer session.freeHistoryTurnSlice(alloc, history); const last_subagent_work_id = if (self.last_subagent_work_id) |work_id| try alloc.dupe(u8, work_id) else null; errdefer if (last_subagent_work_id) |work_id| alloc.free(work_id); var usage = if (self.usage) |snapshot| try session_usage.dupeSnapshotOwned(alloc, snapshot) else null; errdefer if (usage) |*snapshot| snapshot.deinit(alloc); const recovery_checkpoint = if (self.recovery_checkpoint) |checkpoint| try checkpoint.dupe(alloc) else null; const permission_state = try session_permission_state.dupe( alloc, self.permission_state, ); return .{ .id = id, .origin_workspace_root = origin_workspace_root, .workspace_root = workspace_root, .created_at_ms = self.created_at_ms, .updated_at_ms = self.updated_at_ms, .conversation_language = self.conversation_language, .preferences = preferences, .history = history, .context_history_start = self.context_history_start, .total_input_tokens = self.total_input_tokens, .total_output_tokens = self.total_output_tokens, .permission_state = permission_state, .last_subagent_work_id = last_subagent_work_id, .usage = usage, .recovery_checkpoint = recovery_checkpoint, }; }}; pub const EncodeSummary = struct { encoded_bytes: u64, sha256: [Sha256.digest_length]u8,}; pub const DecodeLimits = struct { max_history_turns: usize = std.math.maxInt(usize), max_value_bytes: usize = std.math.maxInt(usize) / 2,}; const CountingSha256 = struct { sha256: Sha256 = Sha256.init(.{}), bytes: u64 = 0, pub fn update(self: *CountingSha256, bytes: []const u8) void { self.sha256.update(bytes); self.bytes += bytes.len; }}; pub fn writeDurableBytes(writer: *std.Io.Writer, bytes: []const u8) !void { if (std.unicode.utf8ValidateSlice(bytes)) { return writeJsonString(writer, bytes); } try writer.writeAll("{\"encoding\":\"base64\",\"data\":\""); try std.base64.standard.Encoder.encodeWriter(writer, bytes); try writer.writeAll("\"}");} pub fn parseDurableBytes(alloc: Allocator, value: std.json.Value) ![]u8 { switch (value) { .string => |text| return try alloc.dupe(u8, text), .object => |object| { if (object.count() != 2) return error.InvalidDurableBytes; var iterator = object.iterator(); const encoding_entry = iterator.next() orelse return error.InvalidDurableBytes; const data_entry = iterator.next() orelse return error.InvalidDurableBytes; if (!std.mem.eql(u8, encoding_entry.key_ptr.*, "encoding") or !std.mem.eql(u8, data_entry.key_ptr.*, "data")) { return error.InvalidDurableBytes; } if (encoding_entry.value_ptr.* != .string or !std.mem.eql(u8, encoding_entry.value_ptr.string, "base64") or data_entry.value_ptr.* != .string) { return error.InvalidDurableBytes; } const encoded = data_entry.value_ptr.string; const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(encoded) catch return error.InvalidDurableBytes; const decoded = try alloc.alloc(u8, decoded_len); errdefer alloc.free(decoded); std.base64.standard.Decoder.decode(decoded, encoded) catch return error.InvalidDurableBytes; if (std.unicode.utf8ValidateSlice(decoded)) return error.InvalidDurableBytes; const canonical_len = std.base64.standard.Encoder.calcSize(decoded.len); const canonical = try alloc.alloc(u8, canonical_len); defer alloc.free(canonical); const written = std.base64.standard.Encoder.encode(canonical, decoded); if (!std.mem.eql(u8, written, encoded)) return error.InvalidDurableBytes; return decoded; }, else => return error.InvalidDurableBytes, }} pub fn encodeState(state: DurableSessionState, writer: *std.Io.Writer) !EncodeSummary { try validateState(state); var hash_buffer: [4096]u8 = undefined; var hashed = std.Io.Writer.hashed(writer, CountingSha256{}, &hash_buffer); try writeState(&hashed.writer, state); try hashed.writer.flush(); return .{ .encoded_bytes = hashed.hasher.bytes, .sha256 = hashed.hasher.sha256.finalResult(), };} pub fn decodeState(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits) !DurableSessionState { return decodeStateImpl(alloc, source, limits) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.InvalidDurableField => return error.InvalidDurableField, error.InvalidDurableBytes => return error.InvalidDurableBytes, else => return error.InvalidSessionFormat, };} pub fn writeHistoryTurn(writer: *std.Io.Writer, turn: session.HistoryTurn) !void { switch (turn) { .compacted_summary => |entry| { try writer.writeAll("{\"kind\":\"compacted_summary\",\"summary\":"); try writeDurableBytes(writer, entry.summary); try writer.print(",\"removed_turn_count\":{d},\"compaction_count\":{d}", .{ entry.removed_turn_count, entry.compaction_count, }); try writer.writeByte('}'); }, .assistant => |entry| { try writer.writeAll("{\"kind\":\"assistant\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeDurableBytes(writer, entry.assistant); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); try writer.writeByte('}'); }, .background_command => |entry| { const extended = entry.assistant != null or !entry.execution.isEmpty(); try writer.writeAll("{\"kind\":\"background_command\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"log_path\":"); try writeDurableBytes(writer, entry.log_path); try writer.print(",\"expect_url\":{s},\"url\":", .{ if (entry.expect_url) "true" else "false", }); try writeOptionalDurableBytes(writer, entry.url); try writer.writeAll(",\"background_record_id\":"); if (entry.background_record_id) |record_id| { try writeHexString(writer, &record_id); } else { try writer.writeAll("null"); } if (extended) { try writer.writeAll(",\"assistant\":"); try writeOptionalDurableBytes(writer, entry.assistant); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); } try writer.writeByte('}'); }, .interrupted => |entry| { try writer.writeAll("{\"kind\":\"interrupted\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeOptionalDurableBytes(writer, entry.assistant); try writer.writeAll(",\"tool_call\":"); if (entry.tool_call) |tool_call| { try writeToolCall(writer, tool_call); } else { try writer.writeAll("null"); } try writer.writeAll(",\"completed_tool_names\":["); for (entry.completed_tool_names, 0..) |name, i| { if (i > 0) try writer.writeByte(','); try writeDurableBytes(writer, name); } try writer.writeByte(']'); try writer.writeAll(",\"terminal_reason\":"); try writeJsonString(writer, @tagName(entry.terminal_reason)); if (!entry.execution.isEmpty()) { try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); } if (entry.cancelled_command) |presentation| { try writer.writeAll(",\"cancelled_command\":"); try writeCancelledCommandPresentation(writer, presentation); } try writer.writeByte('}'); }, }} pub fn parseHistoryTurn(alloc: Allocator, value: std.json.Value) !session.HistoryTurn { const kind = try requireString(try requireObject(value), "kind"); if (std.mem.eql(u8, kind, "compacted_summary")) { const raw_object = try requireObject(value); const has_root_user_messages = raw_object.get("root_user_messages") != null; const has_completeness = raw_object.get("root_user_messages_complete") != null; const has_permission_feedback = raw_object.get("permission_feedback") != null; const has_feedback_completeness = raw_object.get("permission_feedback_complete") != null; if (has_permission_feedback != has_feedback_completeness) { return error.InvalidSessionFormat; } const object = if (has_feedback_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete", "permission_feedback", "permission_feedback_complete" }) else if (has_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete" }) else if (has_root_user_messages) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages" }) else try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count" }); const summary = try parseRequiredDurableBytes(alloc, object, "summary"); errdefer alloc.free(summary); const root_user_messages: [][]u8 = if (has_root_user_messages) try parseDurableBytesArray( alloc, object.get("root_user_messages") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freeCompletedToolNames(alloc, root_user_messages); const permission_feedback: [][]u8 = if (has_permission_feedback) try parseDurableBytesArray( alloc, object.get("permission_feedback") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freePermissionFeedback(alloc, permission_feedback); const removed_turn_count = try requireUsize(object, "removed_turn_count"); return .{ .compacted_summary = .{ .summary = summary, .removed_turn_count = removed_turn_count, .compaction_count = try requireUsize(object, "compaction_count"), .root_user_messages = root_user_messages, .root_user_messages_complete = if (has_completeness) try requireBool(object, "root_user_messages_complete") else has_root_user_messages or removed_turn_count == 0, .permission_feedback = permission_feedback, .permission_feedback_complete = if (has_feedback_completeness) try requireBool(object, "permission_feedback_complete") else removed_turn_count == 0, } }; } if (std.mem.eql(u8, kind, "assistant")) { const object = try exactObject(value, &.{ "kind", "user", "assistant", "execution" }); const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseRequiredDurableBytes(alloc, object, "assistant"); errdefer alloc.free(assistant); const execution = try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat); errdefer session.freeExecutionMemory(alloc, execution); return .{ .assistant = .{ .user = user, .assistant = assistant, .execution = execution, } }; } if (std.mem.eql(u8, kind, "background_command")) { const shape = try exactVariantObject( value, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id" }, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id", "assistant", "execution" }, ); const object = shape.object; const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = if (shape.extended) try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat) else null; errdefer if (assistant) |owned| alloc.free(owned); const execution = if (shape.extended) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const log_path = try parseRequiredDurableBytes(alloc, object, "log_path"); errdefer alloc.free(log_path); const url = try parseOptionalDurableBytes(alloc, object.get("url") orelse return error.InvalidSessionFormat); errdefer if (url) |owned| alloc.free(owned); const background_record_id = try parseOptionalIdentifier(object.get("background_record_id") orelse return error.InvalidSessionFormat); return .{ .background_command = .{ .user = user, .assistant = assistant, .execution = execution, .log_path = log_path, .expect_url = try requireBool(object, "expect_url"), .url = url, .background_record_id = background_record_id, } }; } if (std.mem.eql(u8, kind, "interrupted")) { const source = try requireObject(value); const has_execution = source.get("execution") != null; const has_presentation = source.get("cancelled_command") != null; const has_terminal_reason = source.get("terminal_reason") != null; const object = try exactInterruptedHistoryObject( value, has_execution, has_presentation, has_terminal_reason, ); const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat); errdefer if (assistant) |owned| alloc.free(owned); const tool_call = try parseOptionalToolCall(alloc, object.get("tool_call") orelse return error.InvalidSessionFormat); errdefer if (tool_call) |call| session.freeToolCall(alloc, call); const completed_tool_names = try parseDurableBytesArray( alloc, object.get("completed_tool_names") orelse return error.InvalidSessionFormat, ); errdefer session.freeCompletedToolNames(alloc, completed_tool_names); const terminal_reason = if (has_terminal_reason) try parseInterruptedTerminalReason(object, "terminal_reason") else types.InterruptedTerminalReason.cancelled; const execution = if (has_execution) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const cancelled_command = if (has_presentation) try parseCancelledCommandPresentation( alloc, object.get("cancelled_command") orelse return error.InvalidSessionFormat, ) else null; errdefer if (cancelled_command) |presentation| { types.freeCancelledCommandPresentation(alloc, presentation); }; if (cancelled_command != null and (terminal_reason != .cancelled or tool_call == null or !try isCapturedCommandToolCall(alloc, tool_call.?))) { return error.InvalidSessionFormat; } return .{ .interrupted = .{ .user = user, .assistant = assistant, .tool_call = tool_call, .completed_tool_names = completed_tool_names, .execution = execution,Lines 1–500 of 3403. Load more
const std = @import("std"); const session = @import("session.zig"); const session_usage = @import("session_usage.zig"); const session_permission_state = @import("../permissions/session_permission_state.zig"); const types = @import("../shared/types.zig"); const captured_command = @import("../tooling/captured_command.zig"); const Allocator = std.mem.Allocator; const Sha256 = std.crypto.hash.sha2.Sha256; pub const DurableSessionPreferences = struct { model: []u8, effort: types.ReasoningEffort, fast_mode: bool, pub fn deinit(self: *DurableSessionPreferences, alloc: Allocator) void { alloc.free(self.model); self.* = undefined; } pub fn dupe(self: DurableSessionPreferences, alloc: Allocator) !DurableSessionPreferences { return .{ .model = try alloc.dupe(u8, self.model), .effort = self.effort, .fast_mode = self.fast_mode, }; } }; pub const RecoveryToolState = enum { none, proven_unexecuted, confirmed, uncertain, }; pub const RecoveryCheckpoint = struct { version: u8 = 1, turn_id: u64, user: session.UserTurn, assistant_source: []u8, execution: session.ExecutionMemory = .{}, cause: types.ModelRecoveryCause, action: types.ModelRecoveryAction, tool_state: RecoveryToolState = .none, route_model: []u8, requested_fast_mode: bool, fast_mode: bool, max_provider_attempts: usize, consumed_provider_attempts: usize, outstanding_reservation: bool = false, pub fn deinit(self: *RecoveryCheckpoint, alloc: Allocator) void { session.freeUserTurn(alloc, self.user); alloc.free(self.assistant_source); session.freeExecutionMemory(alloc, self.execution); alloc.free(self.route_model); self.* = undefined; } pub fn dupe(self: RecoveryCheckpoint, alloc: Allocator) !RecoveryCheckpoint { const user = try session.dupeUserTurn(alloc, self.user); errdefer session.freeUserTurn(alloc, user); const assistant_source = try alloc.dupe(u8, self.assistant_source); errdefer alloc.free(assistant_source); const execution = try types.dupeExecutionMemory(alloc, self.execution); errdefer session.freeExecutionMemory(alloc, execution); const route_model = try alloc.dupe(u8, self.route_model); return .{ .version = self.version, .turn_id = self.turn_id, .user = user, .assistant_source = assistant_source, .execution = execution, .cause = self.cause, .action = self.action, .tool_state = self.tool_state, .route_model = route_model, .requested_fast_mode = self.requested_fast_mode, .fast_mode = self.fast_mode, .max_provider_attempts = self.max_provider_attempts, .consumed_provider_attempts = self.consumed_provider_attempts, .outstanding_reservation = self.outstanding_reservation, }; } }; pub const DurableSessionState = struct { id: []u8, origin_workspace_root: []u8, workspace_root: []u8, created_at_ms: i64, updated_at_ms: i64, conversation_language: session.ConversationLanguage, preferences: DurableSessionPreferences, history: []session.HistoryTurn, context_history_start: usize = 0, total_input_tokens: u64, total_output_tokens: u64, permission_state: session_permission_state.State = .{}, /// Last ordinary history commit correlated to durable subagent work. This /// control-only marker is not included in model history. last_subagent_work_id: ?[]u8 = null, /// Null only for sessions written before durable usage accounting. usage: ?session_usage.Snapshot = null, /// Active control state. It is never projected into model history and a /// restored checkpoint requires explicit host authority before any send. recovery_checkpoint: ?RecoveryCheckpoint = null, pub fn deinit(self: *DurableSessionState, alloc: Allocator) void { alloc.free(self.id); alloc.free(self.origin_workspace_root); alloc.free(self.workspace_root); self.preferences.deinit(alloc); session.freeHistoryTurnSlice(alloc, self.history); self.permission_state.deinit(alloc); if (self.last_subagent_work_id) |id| alloc.free(id); if (self.usage) |*usage| usage.deinit(alloc); if (self.recovery_checkpoint) |*checkpoint| checkpoint.deinit(alloc); self.* = undefined; } pub fn dupe(self: DurableSessionState, alloc: Allocator) !DurableSessionState { const id = try alloc.dupe(u8, self.id); errdefer alloc.free(id); const origin_workspace_root = try alloc.dupe(u8, self.origin_workspace_root); errdefer alloc.free(origin_workspace_root); const workspace_root = try alloc.dupe(u8, self.workspace_root); errdefer alloc.free(workspace_root); const preferences = try self.preferences.dupe(alloc); errdefer { var owned_preferences = preferences; owned_preferences.deinit(alloc); } const history = try dupeHistory(alloc, self.history); errdefer session.freeHistoryTurnSlice(alloc, history); const last_subagent_work_id = if (self.last_subagent_work_id) |work_id| try alloc.dupe(u8, work_id) else null; errdefer if (last_subagent_work_id) |work_id| alloc.free(work_id); var usage = if (self.usage) |snapshot| try session_usage.dupeSnapshotOwned(alloc, snapshot) else null; errdefer if (usage) |*snapshot| snapshot.deinit(alloc); const recovery_checkpoint = if (self.recovery_checkpoint) |checkpoint| try checkpoint.dupe(alloc) else null; const permission_state = try session_permission_state.dupe( alloc, self.permission_state, ); return .{ .id = id, .origin_workspace_root = origin_workspace_root, .workspace_root = workspace_root, .created_at_ms = self.created_at_ms, .updated_at_ms = self.updated_at_ms, .conversation_language = self.conversation_language, .preferences = preferences, .history = history, .context_history_start = self.context_history_start, .total_input_tokens = self.total_input_tokens, .total_output_tokens = self.total_output_tokens, .permission_state = permission_state, .last_subagent_work_id = last_subagent_work_id, .usage = usage, .recovery_checkpoint = recovery_checkpoint, }; } }; pub const EncodeSummary = struct { encoded_bytes: u64, sha256: [Sha256.digest_length]u8, }; pub const DecodeLimits = struct { max_history_turns: usize = std.math.maxInt(usize), max_value_bytes: usize = std.math.maxInt(usize) / 2, }; const CountingSha256 = struct { sha256: Sha256 = Sha256.init(.{}), bytes: u64 = 0, pub fn update(self: *CountingSha256, bytes: []const u8) void { self.sha256.update(bytes); self.bytes += bytes.len; } }; pub fn writeDurableBytes(writer: *std.Io.Writer, bytes: []const u8) !void { if (std.unicode.utf8ValidateSlice(bytes)) { return writeJsonString(writer, bytes); } try writer.writeAll("{\"encoding\":\"base64\",\"data\":\""); try std.base64.standard.Encoder.encodeWriter(writer, bytes); try writer.writeAll("\"}"); } pub fn parseDurableBytes(alloc: Allocator, value: std.json.Value) ![]u8 { switch (value) { .string => |text| return try alloc.dupe(u8, text), .object => |object| { if (object.count() != 2) return error.InvalidDurableBytes; var iterator = object.iterator(); const encoding_entry = iterator.next() orelse return error.InvalidDurableBytes; const data_entry = iterator.next() orelse return error.InvalidDurableBytes; if (!std.mem.eql(u8, encoding_entry.key_ptr.*, "encoding") or !std.mem.eql(u8, data_entry.key_ptr.*, "data")) { return error.InvalidDurableBytes; } if (encoding_entry.value_ptr.* != .string or !std.mem.eql(u8, encoding_entry.value_ptr.string, "base64") or data_entry.value_ptr.* != .string) { return error.InvalidDurableBytes; } const encoded = data_entry.value_ptr.string; const decoded_len = std.base64.standard.Decoder.calcSizeForSlice(encoded) catch return error.InvalidDurableBytes; const decoded = try alloc.alloc(u8, decoded_len); errdefer alloc.free(decoded); std.base64.standard.Decoder.decode(decoded, encoded) catch return error.InvalidDurableBytes; if (std.unicode.utf8ValidateSlice(decoded)) return error.InvalidDurableBytes; const canonical_len = std.base64.standard.Encoder.calcSize(decoded.len); const canonical = try alloc.alloc(u8, canonical_len); defer alloc.free(canonical); const written = std.base64.standard.Encoder.encode(canonical, decoded); if (!std.mem.eql(u8, written, encoded)) return error.InvalidDurableBytes; return decoded; }, else => return error.InvalidDurableBytes, } } pub fn encodeState(state: DurableSessionState, writer: *std.Io.Writer) !EncodeSummary { try validateState(state); var hash_buffer: [4096]u8 = undefined; var hashed = std.Io.Writer.hashed(writer, CountingSha256{}, &hash_buffer); try writeState(&hashed.writer, state); try hashed.writer.flush(); return .{ .encoded_bytes = hashed.hasher.bytes, .sha256 = hashed.hasher.sha256.finalResult(), }; } pub fn decodeState(alloc: Allocator, source: *std.Io.Reader, limits: DecodeLimits) !DurableSessionState { return decodeStateImpl(alloc, source, limits) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.InvalidDurableField => return error.InvalidDurableField, error.InvalidDurableBytes => return error.InvalidDurableBytes, else => return error.InvalidSessionFormat, }; } pub fn writeHistoryTurn(writer: *std.Io.Writer, turn: session.HistoryTurn) !void { switch (turn) { .compacted_summary => |entry| { try writer.writeAll("{\"kind\":\"compacted_summary\",\"summary\":"); try writeDurableBytes(writer, entry.summary); try writer.print(",\"removed_turn_count\":{d},\"compaction_count\":{d}", .{ entry.removed_turn_count, entry.compaction_count, }); try writer.writeByte('}'); }, .assistant => |entry| { try writer.writeAll("{\"kind\":\"assistant\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeDurableBytes(writer, entry.assistant); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); try writer.writeByte('}'); }, .background_command => |entry| { const extended = entry.assistant != null or !entry.execution.isEmpty(); try writer.writeAll("{\"kind\":\"background_command\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"log_path\":"); try writeDurableBytes(writer, entry.log_path); try writer.print(",\"expect_url\":{s},\"url\":", .{ if (entry.expect_url) "true" else "false", }); try writeOptionalDurableBytes(writer, entry.url); try writer.writeAll(",\"background_record_id\":"); if (entry.background_record_id) |record_id| { try writeHexString(writer, &record_id); } else { try writer.writeAll("null"); } if (extended) { try writer.writeAll(",\"assistant\":"); try writeOptionalDurableBytes(writer, entry.assistant); try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); } try writer.writeByte('}'); }, .interrupted => |entry| { try writer.writeAll("{\"kind\":\"interrupted\",\"user\":"); try writeUserTurn(writer, entry.user); try writer.writeAll(",\"assistant\":"); try writeOptionalDurableBytes(writer, entry.assistant); try writer.writeAll(",\"tool_call\":"); if (entry.tool_call) |tool_call| { try writeToolCall(writer, tool_call); } else { try writer.writeAll("null"); } try writer.writeAll(",\"completed_tool_names\":["); for (entry.completed_tool_names, 0..) |name, i| { if (i > 0) try writer.writeByte(','); try writeDurableBytes(writer, name); } try writer.writeByte(']'); try writer.writeAll(",\"terminal_reason\":"); try writeJsonString(writer, @tagName(entry.terminal_reason)); if (!entry.execution.isEmpty()) { try writer.writeAll(",\"execution\":"); try writeExecutionMemory(writer, entry.execution); } if (entry.cancelled_command) |presentation| { try writer.writeAll(",\"cancelled_command\":"); try writeCancelledCommandPresentation(writer, presentation); } try writer.writeByte('}'); }, } } pub fn parseHistoryTurn(alloc: Allocator, value: std.json.Value) !session.HistoryTurn { const kind = try requireString(try requireObject(value), "kind"); if (std.mem.eql(u8, kind, "compacted_summary")) { const raw_object = try requireObject(value); const has_root_user_messages = raw_object.get("root_user_messages") != null; const has_completeness = raw_object.get("root_user_messages_complete") != null; const has_permission_feedback = raw_object.get("permission_feedback") != null; const has_feedback_completeness = raw_object.get("permission_feedback_complete") != null; if (has_permission_feedback != has_feedback_completeness) { return error.InvalidSessionFormat; } const object = if (has_feedback_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete", "permission_feedback", "permission_feedback_complete" }) else if (has_completeness) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages", "root_user_messages_complete" }) else if (has_root_user_messages) try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count", "root_user_messages" }) else try exactObject(value, &.{ "kind", "summary", "removed_turn_count", "compaction_count" }); const summary = try parseRequiredDurableBytes(alloc, object, "summary"); errdefer alloc.free(summary); const root_user_messages: [][]u8 = if (has_root_user_messages) try parseDurableBytesArray( alloc, object.get("root_user_messages") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freeCompletedToolNames(alloc, root_user_messages); const permission_feedback: [][]u8 = if (has_permission_feedback) try parseDurableBytesArray( alloc, object.get("permission_feedback") orelse return error.InvalidSessionFormat, ) else &.{}; errdefer types.freePermissionFeedback(alloc, permission_feedback); const removed_turn_count = try requireUsize(object, "removed_turn_count"); return .{ .compacted_summary = .{ .summary = summary, .removed_turn_count = removed_turn_count, .compaction_count = try requireUsize(object, "compaction_count"), .root_user_messages = root_user_messages, .root_user_messages_complete = if (has_completeness) try requireBool(object, "root_user_messages_complete") else has_root_user_messages or removed_turn_count == 0, .permission_feedback = permission_feedback, .permission_feedback_complete = if (has_feedback_completeness) try requireBool(object, "permission_feedback_complete") else removed_turn_count == 0, } }; } if (std.mem.eql(u8, kind, "assistant")) { const object = try exactObject(value, &.{ "kind", "user", "assistant", "execution" }); const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseRequiredDurableBytes(alloc, object, "assistant"); errdefer alloc.free(assistant); const execution = try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat); errdefer session.freeExecutionMemory(alloc, execution); return .{ .assistant = .{ .user = user, .assistant = assistant, .execution = execution, } }; } if (std.mem.eql(u8, kind, "background_command")) { const shape = try exactVariantObject( value, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id" }, &.{ "kind", "user", "log_path", "expect_url", "url", "background_record_id", "assistant", "execution" }, ); const object = shape.object; const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = if (shape.extended) try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat) else null; errdefer if (assistant) |owned| alloc.free(owned); const execution = if (shape.extended) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const log_path = try parseRequiredDurableBytes(alloc, object, "log_path"); errdefer alloc.free(log_path); const url = try parseOptionalDurableBytes(alloc, object.get("url") orelse return error.InvalidSessionFormat); errdefer if (url) |owned| alloc.free(owned); const background_record_id = try parseOptionalIdentifier(object.get("background_record_id") orelse return error.InvalidSessionFormat); return .{ .background_command = .{ .user = user, .assistant = assistant, .execution = execution, .log_path = log_path, .expect_url = try requireBool(object, "expect_url"), .url = url, .background_record_id = background_record_id, } }; } if (std.mem.eql(u8, kind, "interrupted")) { const source = try requireObject(value); const has_execution = source.get("execution") != null; const has_presentation = source.get("cancelled_command") != null; const has_terminal_reason = source.get("terminal_reason") != null; const object = try exactInterruptedHistoryObject( value, has_execution, has_presentation, has_terminal_reason, ); const user = try parseUserTurn(alloc, object.get("user") orelse return error.InvalidSessionFormat); errdefer session.freeUserTurn(alloc, user); const assistant = try parseOptionalDurableBytes(alloc, object.get("assistant") orelse return error.InvalidSessionFormat); errdefer if (assistant) |owned| alloc.free(owned); const tool_call = try parseOptionalToolCall(alloc, object.get("tool_call") orelse return error.InvalidSessionFormat); errdefer if (tool_call) |call| session.freeToolCall(alloc, call); const completed_tool_names = try parseDurableBytesArray( alloc, object.get("completed_tool_names") orelse return error.InvalidSessionFormat, ); errdefer session.freeCompletedToolNames(alloc, completed_tool_names); const terminal_reason = if (has_terminal_reason) try parseInterruptedTerminalReason(object, "terminal_reason") else types.InterruptedTerminalReason.cancelled; const execution = if (has_execution) try parseExecutionMemory(alloc, object.get("execution") orelse return error.InvalidSessionFormat) else session.ExecutionMemory{}; errdefer session.freeExecutionMemory(alloc, execution); const cancelled_command = if (has_presentation) try parseCancelledCommandPresentation( alloc, object.get("cancelled_command") orelse return error.InvalidSessionFormat, ) else null; errdefer if (cancelled_command) |presentation| { types.freeCancelledCommandPresentation(alloc, presentation); }; if (cancelled_command != null and (terminal_reason != .cancelled or tool_call == null or !try isCapturedCommandToolCall(alloc, tool_call.?))) { return error.InvalidSessionFormat; } return .{ .interrupted = .{ .user = user, .assistant = assistant, .tool_call = tool_call, .completed_tool_names = completed_tool_names, .execution = execution,