PranitInitial commit439f83c2 months agoHistory
1740 lines · 61.3 KB · Zig
RawDownload
const std = @import("std");const debug_trace = @import("../shared/debug_trace.zig");const io_mod = @import("../shared/io.zig");const message = @import("../shared/message.zig");const text_utils = @import("../shared/text_utils.zig");const types = @import("../shared/types.zig"); const Allocator = std.mem.Allocator;const ToolCall = types.ToolCall; pub fn makePersistedToolResult(    alloc: Allocator,    tool_call_id_src: []const u8,    tool_name_src: []const u8,    status: types.PersistedToolStatus,    output_src: []const u8,    memory: ?types.ToolResultMemory,) !types.PersistedToolResult {    const tool_call_id = try durableIdentifier(alloc, tool_call_id_src);    errdefer alloc.free(tool_call_id);    const tool_name = try alloc.dupe(u8, tool_name_src);    errdefer alloc.free(tool_name);    const output = try redactText(alloc, output_src);    errdefer alloc.free(output);    const output_handle = if (memory) |info| if (info.output_handle) |handle| try alloc.dupe(u8, handle) else null else null;    errdefer if (output_handle) |handle| alloc.free(handle);    const preview = if (memory) |info| if (info.preview) |text| try redactText(alloc, text) else null else null;    errdefer if (preview) |text| alloc.free(text);    const command_output_replay = if (memory) |info| if (info.command_output_replay) |replay|        try types.dupeCommandOutputReplay(alloc, replay)    else        null else null;    errdefer if (command_output_replay) |replay| types.freeCommandOutputReplay(alloc, replay);    var result: types.PersistedToolResult = .{        .tool_call_id = tool_call_id,        .tool_name = tool_name,        .status = status,        .output = output,        .output_handle = output_handle,        .preview = preview,        .output_bytes = if (memory) |info| info.output_bytes else output_src.len,        .stored_output_bytes = if (memory) |info| info.stored_output_bytes else output.len,        .truncated = if (memory) |info| info.truncated else false,        .provider_native = false,        .created_at_ms = io_mod.milliTimestamp(),        .command_output_replay = command_output_replay,        .command_process_presentation = if (memory) |info| info.command_process_presentation else null,    };    if (memory) |info| {        if (info.committed_file_presentation) |presentation| {            result.committed_file_presentation = dupeRedactedCommittedFilePresentation(                alloc,                presentation,            ) catch |err| blk: {                debug_trace.logf(                    "session",                    "committed file resume presentation unavailable call_id={s} err={s}",                    .{ tool_call_id_src, @errorName(err) },                );                break :blk null;            };        }    }    return result;} pub fn buildNormalChatExecutionMemory(    alloc: Allocator,    messages: []const types.ChatMessage,) !types.ExecutionMemory {    return buildNormalExecutionMemory(ChatMessageAdapter, alloc, messages);} pub fn buildNormalMessageExecutionMemory(    alloc: Allocator,    messages: []const message.Message,) !types.ExecutionMemory {    return buildNormalExecutionMemory(MessageAdapter, alloc, messages);} pub fn dupeCompletedRedactedToolCalls(    alloc: Allocator,    calls: []const ToolCall,    results: []const types.PersistedToolResult,) ![]ToolCall {    if (calls.len == 0 or results.len == 0) return &.{};     var copy: std.ArrayList(ToolCall) = .empty;    errdefer {        for (copy.items) |call| types.freeToolCall(alloc, call);        copy.deinit(alloc);    }    for (calls) |call| {        const completed = blk: {            const durable_id = try durableIdentifier(alloc, call.id);            defer alloc.free(durable_id);            break :blk hasPersistedResultForCall(results, durable_id);        };        if (!completed) continue;         const duplicated = try dupeRedactedToolCall(alloc, call);        copy.append(alloc, duplicated) catch |err| {            types.freeToolCall(alloc, duplicated);            return err;        };    }    return copy.toOwnedSlice(alloc);} pub fn redactText(alloc: Allocator, text: []const u8) ![]u8 {    const masked = text_utils.maskSecrets(alloc, text) catch        return error.OutOfMemory;    if (masked.ptr == text.ptr) return alloc.dupe(u8, text);    return @constCast(masked);} fn dupeRedactedCommittedFilePresentation(    alloc: Allocator,    presentation: types.CommittedFilePresentation,) !types.CommittedFilePresentation {    const path = try redactText(alloc, presentation.path);    errdefer alloc.free(path);    const lines = try alloc.alloc(types.CommittedFilePresentationLine, presentation.lines.len);    errdefer alloc.free(lines);    var copied_lines: usize = 0;    errdefer {        for (lines[0..copied_lines]) |line| alloc.free(@constCast(line.text));    }    for (presentation.lines, 0..) |line, index| {        lines[index] = .{            .kind = line.kind,            .old_line = line.old_line,            .new_line = line.new_line,            .text = try redactText(alloc, line.text),        };        copied_lines += 1;    }    const previous_content = if (presentation.previous_content) |content|        try redactText(alloc, content)    else        null;    errdefer if (previous_content) |content| alloc.free(content);    const after_content = if (presentation.after_content) |content|        try redactText(alloc, content)    else        null;    errdefer if (after_content) |content| alloc.free(content);    const lifecycle_id: ?types.ToolLifecycleId = if (presentation.lifecycle_id) |id| .{        .turn_id = id.turn_id,        .call_id = try durableIdentifier(alloc, id.call_id),    } else null;    errdefer if (lifecycle_id) |id| alloc.free(@constCast(id.call_id));    return .{        .path = path,        .kind = presentation.kind,        .lines = lines,        .additions = presentation.additions,        .deletions = presentation.deletions,        .truncated = presentation.truncated,        .previous_content = previous_content,        .after_content = after_content,        .lifecycle_id = lifecycle_id,    };} pub fn appendFileEvidenceForTool(    alloc: Allocator,    files: *std.ArrayList(types.FileEvidence),    call: ToolCall,    status: types.PersistedToolStatus,    memory: ?types.ToolResultMemory,) !void {    const action = fileEvidenceActionForTool(call.name);    if (action == .unknown) return;     var parsed = std.json.parseFromSlice(std.json.Value, alloc, call.arguments_json, .{}) catch return;    defer parsed.deinit();    if (parsed.value != .object) return;    const object = parsed.value.object;     const path = switch (action) {        .rename => stringField(object, "old_path"),        .copy => stringField(object, "source"),        .search, .list => stringField(object, "path") orelse stringField(object, "directory") orelse stringField(object, "query"),        else => stringField(object, "path"),    } orelse return;    const new_path = switch (action) {        .rename => stringField(object, "new_path"),        .copy => stringField(object, "destination"),        else => null,    };     const evidence = try makeFileEvidence(        alloc,        call,        path,        new_path,        action,        status,        memory,    );    var owns_evidence = true;    errdefer if (owns_evidence) freeTransientFileEvidence(alloc, evidence);    try files.append(alloc, evidence);    owns_evidence = false;} pub fn markStaleFileEvidence(files: []types.FileEvidence) void {    for (files, 0..) |file, i| {        if (file.status != .success or !isMutationFileAction(file.action)) continue;        var prior_index: usize = 0;        while (prior_index < i) : (prior_index += 1) {            if (files[prior_index].action != .read) continue;            if (file.action != .copy and                std.mem.eql(u8, files[prior_index].path, file.path))            {                files[prior_index].stale = true;            }            if (file.new_path) |new_path| {                if (std.mem.eql(u8, files[prior_index].path, new_path)) {                    files[prior_index].stale = true;                }            }        }    }} pub fn findToolCallById(calls: []const ToolCall, id: []const u8) ?ToolCall {    for (calls) |call| {        if (std.mem.eql(u8, call.id, id)) return call;    }    return null;} const ChatMessageAdapter = struct {    pub const Message = types.ChatMessage;     fn isAssistant(value: Message) bool {        return value.role == .assistant;    }     fn isTool(value: Message) bool {        return value.role == .tool;    }     fn isUser(value: Message) bool {        return value.role == .user;    }     fn isPermissionFeedback(value: Message) bool {        return value.role == .user and value.permission_feedback;    }     fn toolCalls(value: Message) []const ToolCall {        return value.tool_calls;    }     fn toolCallId(value: Message) ?[]const u8 {        return value.tool_call_id;    }     fn toolName(value: Message) ?[]const u8 {        return value.tool_name;    }     fn toolResultStatus(value: Message) ?types.PersistedToolStatus {        return value.tool_result_status;    }     fn toolResultMemory(value: Message) ?types.ToolResultMemory {        return value.tool_result_memory;    }     fn content(value: Message) ?[]const u8 {        return value.content;    }}; const MessageAdapter = struct {    pub const Message = message.Message;     fn isAssistant(value: Message) bool {        return value.role == .assistant;    }     fn isTool(value: Message) bool {        return value.role == .tool;    }     fn isUser(value: Message) bool {        return value.role == .user;    }     fn isPermissionFeedback(value: Message) bool {        return value.role == .user and value.permission_feedback;    }     fn toolCalls(value: Message) []const ToolCall {        return value.tool_calls;    }     fn toolCallId(value: Message) ?[]const u8 {        return value.tool_call_id;    }     fn toolName(value: Message) ?[]const u8 {        return value.tool_name;    }     fn toolResultStatus(value: Message) ?types.PersistedToolStatus {        return value.tool_result_status;    }     fn toolResultMemory(value: Message) ?types.ToolResultMemory {        return value.tool_result_memory;    }     fn content(value: Message) ?[]const u8 {        return if (value.content) |content_value| content_value.asText() else null;    }}; fn buildNormalExecutionMemory(    comptime Adapter: type,    alloc: Allocator,    messages: []const Adapter.Message,) !types.ExecutionMemory {    var tool_steps: std.ArrayList(types.ToolExecutionStep) = .empty;    errdefer {        for (tool_steps.items) |step| {            freeTransientToolExecutionStep(alloc, step);        }        tool_steps.deinit(alloc);    }    var files: std.ArrayList(types.FileEvidence) = .empty;    errdefer {        for (files.items) |file| {            freeTransientFileEvidence(alloc, file);        }        files.deinit(alloc);    }     var i: usize = 0;    while (i < messages.len) {        const msg = messages[i];        const tool_calls = Adapter.toolCalls(msg);        if (!Adapter.isAssistant(msg) or tool_calls.len == 0) {            i += 1;            continue;        }         var results: std.ArrayList(types.PersistedToolResult) = .empty;        errdefer {            for (results.items) |result| {                freeTransientPersistedToolResult(alloc, result);            }            results.deinit(alloc);        }        var result_source_call_ids: std.ArrayList([]const u8) = .empty;        defer result_source_call_ids.deinit(alloc);         var j = i + 1;        var last_result_index: ?usize = null;        while (j < messages.len and Adapter.isTool(messages[j])) : (j += 1) {            const result_msg = messages[j];            const tool_call_id = Adapter.toolCallId(result_msg) orelse continue;            const call = findToolCallById(tool_calls, tool_call_id) orelse continue;            const output = Adapter.content(result_msg) orelse "";            const status = Adapter.toolResultStatus(result_msg) orelse                return error.MissingToolResultStatus;            const result_memory = Adapter.toolResultMemory(result_msg);            const record = try makePersistedToolResult(                alloc,                tool_call_id,                Adapter.toolName(result_msg) orelse call.name,                status,                output,                result_memory,            );            var owns_record = true;            errdefer if (owns_record) {                freeTransientPersistedToolResult(alloc, record);            };            try results.append(alloc, record);            owns_record = false;            try result_source_call_ids.append(alloc, tool_call_id);            last_result_index = results.items.len - 1;            try appendFileEvidenceForTool(                alloc,                &files,                call,                status,                result_memory,            );        }         while (j < messages.len and Adapter.isUser(messages[j])) : (j += 1) {            const feedback_msg = messages[j];            if (!Adapter.isPermissionFeedback(feedback_msg)) continue;            const text = Adapter.content(feedback_msg) orelse                return error.MissingPermissionFeedbackContent;            const index = if (Adapter.toolCallId(feedback_msg)) |tool_call_id|                findResultIndexBySourceCallId(result_source_call_ids.items, tool_call_id) orelse continue            else                last_result_index orelse return error.PermissionFeedbackWithoutResult;            try appendPersistedPermissionFeedback(alloc, &results.items[index], text);        }         if (results.items.len == 0) {            results.deinit(alloc);            i = j;            continue;        }         const assistant = if (Adapter.content(msg)) |content|            try redactText(alloc, content)        else            null;        errdefer if (assistant) |text| alloc.free(text);        const persisted_calls = try dupeCompletedRedactedToolCalls(            alloc,            tool_calls,            results.items,        );        errdefer types.freeToolCallSlice(alloc, persisted_calls);        const owned_results = try results.toOwnedSlice(alloc);        errdefer types.freePersistedToolResults(alloc, owned_results);        const step = types.ToolExecutionStep{            .assistant = assistant,            .tool_calls = persisted_calls,            .tool_results = owned_results,        };        try tool_steps.append(alloc, step);        i = j;    }     const owned_files = try files.toOwnedSlice(alloc);    errdefer types.freeFileEvidenceSlice(alloc, owned_files);    markStaleFileEvidence(owned_files);    return .{        .tool_steps = try tool_steps.toOwnedSlice(alloc),        .files = owned_files,    };} fn hasPersistedResultForCall(    results: []const types.PersistedToolResult,    call_id: []const u8,) bool {    for (results) |result| {        if (std.mem.eql(u8, result.tool_call_id, call_id)) return true;    }    return false;} fn findResultIndexBySourceCallId(    source_call_ids: []const []const u8,    call_id: []const u8,) ?usize {    for (source_call_ids, 0..) |source_call_id, index| {        if (std.mem.eql(u8, source_call_id, call_id)) return index;    }    return null;} pub fn freeTransientToolExecutionStep(    alloc: Allocator,    step: types.ToolExecutionStep,) void {    if (step.assistant) |assistant| alloc.free(assistant);    types.freeToolCallSlice(alloc, step.tool_calls);    types.freePersistedToolResults(alloc, step.tool_results);} pub fn freeTransientPersistedToolResult(    alloc: Allocator,    result: types.PersistedToolResult,) void {    alloc.free(result.tool_call_id);    alloc.free(result.tool_name);    alloc.free(result.output);    if (result.output_handle) |handle| alloc.free(handle);    if (result.preview) |preview| alloc.free(preview);    types.freePermissionFeedback(alloc, result.permission_feedback);    if (result.committed_file_presentation) |presentation| {        types.freeCommittedFilePresentation(alloc, presentation);    }    if (result.command_output_replay) |replay| {        types.freeCommandOutputReplay(alloc, replay);    }} fn appendPersistedPermissionFeedback(    alloc: Allocator,    result: *types.PersistedToolResult,    text: []const u8,) !void {    const redacted = try redactText(alloc, text);    errdefer alloc.free(redacted); 

Lines 1–500 of 1740.