3403 lines · 147.5 KB · Zig
RawDownload
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.