821 lines · 31.1 KB · Zig
RawDownload
const std = @import("std");const debug_trace = @import("../../shared/debug_trace.zig");const types = @import("../../shared/types.zig");const execution_memory_helpers = @import("../execution_memory.zig");const result_store = @import("../../session/result_store.zig");const command_replay_store = @import("../../session/command_replay_store.zig");const session_child_store = @import("../../session/session_child_store.zig");const command_output_content = @import("../../tooling/command_output_content.zig");const io_mod = @import("../../shared/io.zig");const file_mutation = @import("../../tooling/file_mutation.zig");const tool_result_errors = @import("../../tooling/tool_result_errors.zig");const tool_result_limits = @import("../../tooling/tool_result_limits.zig"); const runtime_config = @import("config.zig");const runtime_tool_contracts = @import("tool_contracts.zig"); const Allocator = std.mem.Allocator;const ChatMessage = types.ChatMessage;const ToolCall = types.ToolCall;const Config = runtime_config.Config;const ToolExecutionStatus = runtime_tool_contracts.ToolExecutionStatus; pub fn persistedStatusForCurrentFxLocalResult(    status: ToolExecutionStatus,    output: []const u8,) types.PersistedToolStatus {    if (status == .failure) return .failure;    return if (tool_result_errors.isToolOutputError(output)) .failure else .success;} pub fn classifyProviderExecutedResultStatus(output: []const u8) types.PersistedToolStatus {    return if (tool_result_errors.isToolOutputError(output)) .failure else .success;} pub fn buildExecutionMemory(alloc: Allocator, within_turn_suffix: []const ChatMessage) !types.ExecutionMemory {    return execution_memory_helpers.buildNormalChatExecutionMemory(        alloc,        within_turn_suffix,    );} pub fn buildInterruptedExecutionMemory(    alloc: Allocator,    current_turn_messages: []const ChatMessage,    active_tool_call: ?ToolCall,) !types.ExecutionMemory {    var filtered: std.ArrayList(ChatMessage) = .empty;    defer filtered.deinit(alloc);    try filtered.ensureTotalCapacity(alloc, current_turn_messages.len);    var allocated_call_slices: std.ArrayList([]ToolCall) = .empty;    defer {        for (allocated_call_slices.items) |calls| alloc.free(calls);        allocated_call_slices.deinit(alloc);    }     var i: usize = 0;    while (i < current_turn_messages.len) {        const item = current_turn_messages[i];        if (item.role != .assistant or item.tool_calls.len == 0) {            i += 1;            continue;        }         var result_end = i + 1;        while (result_end < current_turn_messages.len and            current_turn_messages[result_end].role == .tool) : (result_end += 1)        {}        const result_messages = current_turn_messages[i + 1 .. result_end];        var user_tail_end = result_end;        while (user_tail_end < current_turn_messages.len and            current_turn_messages[user_tail_end].role == .user) : (user_tail_end += 1)        {}        const user_tail = current_turn_messages[result_end..user_tail_end];         var completed_count: usize = 0;        for (item.tool_calls) |call| {            if (active_tool_call) |active| {                if (std.mem.eql(u8, call.id, active.id)) continue;            }            if (hasToolResultForCall(result_messages, call.id)) {                completed_count += 1;            }        }         if (completed_count > 0) {            const calls = try alloc.alloc(ToolCall, completed_count);            errdefer alloc.free(calls);            try allocated_call_slices.append(alloc, calls);             var completed_index: usize = 0;            for (item.tool_calls) |call| {                if (active_tool_call) |active| {                    if (std.mem.eql(u8, call.id, active.id)) continue;                }                if (!hasToolResultForCall(result_messages, call.id)) continue;                calls[completed_index] = call;                completed_index += 1;            }             var projected = item;            projected.tool_calls = calls;            filtered.appendAssumeCapacity(projected);            for (result_messages) |result| {                const result_call_id = result.tool_call_id orelse continue;                if (execution_memory_helpers.findToolCallById(                    calls,                    result_call_id,                ) != null) {                    filtered.appendAssumeCapacity(result);                }            }            for (user_tail) |entry| {                if (!entry.permission_feedback) continue;                if (entry.tool_call_id) |source_tool_call_id| {                    if (execution_memory_helpers.findToolCallById(calls, source_tool_call_id) == null) {                        continue;                    }                }                filtered.appendAssumeCapacity(entry);            }        }        i = user_tail_end;    }     return buildExecutionMemory(alloc, filtered.items);} test "interrupted execution memory retains marked feedback through mixed user tail" {    const alloc = std.testing.allocator;    var calls = [_]ToolCall{        .{ .id = "call_first", .name = "run_command", .arguments_json = "{\"command\":\"printf first\"}" },        .{ .id = "call_second", .name = "run_command", .arguments_json = "{\"command\":\"printf second\"}" },        .{ .id = "call_active", .name = "run_command", .arguments_json = "{\"command\":\"printf active\"}" },    };    const messages = [_]ChatMessage{        .{ .role = .assistant, .tool_calls = calls[0..] },        .{ .role = .tool, .content = "first command completed", .tool_call_id = calls[0].id, .tool_name = calls[0].name, .tool_result_status = .success },        .{ .role = .tool, .content = "second command completed", .tool_call_id = calls[1].id, .tool_name = calls[1].name, .tool_result_status = .success },        .{ .role = .user, .content = "first command feedback marker", .tool_call_id = calls[0].id, .permission_feedback = true },        .{ .role = .user, .content = "sandbox hint", .permission_feedback = false },        .{ .role = .user, .content = "second command feedback marker", .tool_call_id = calls[1].id, .permission_feedback = true },    };     const memory = try buildInterruptedExecutionMemory(alloc, &messages, calls[2]);    defer types.freeExecutionMemory(alloc, memory);     const results = memory.tool_steps[0].tool_results;    try std.testing.expectEqual(@as(usize, 1), results[0].permission_feedback.len);    try std.testing.expectEqualStrings("first command feedback marker", results[0].permission_feedback[0]);    try std.testing.expectEqual(@as(usize, 1), results[1].permission_feedback.len);    try std.testing.expectEqualStrings("second command feedback marker", results[1].permission_feedback[0]);} fn hasToolResultForCall(    result_messages: []const ChatMessage,    call_id: []const u8,) bool {    for (result_messages) |result| {        if (result.tool_call_id) |result_call_id| {            if (std.mem.eql(u8, result_call_id, call_id)) return true;        }    }    return false;} pub fn prepareToolModelOutput(arena: Allocator, config: Config, tool_call: ToolCall, raw_output: []const u8) !result_store.PreparedResult {    if ((config.session_child_capability != null or config.tool_result_dir != null) and        raw_output.len > result_store.large_result_threshold_bytes)    {        const redacted_output = try execution_memory_helpers.redactText(            arena,            raw_output,        );        if (config.session_child_capability != null) {            return result_store.prepareManaged(                arena,                config.session_child_capability,                tool_call.id,                tool_call.name,                raw_output.len,                redacted_output,                config.max_tool_result_bytes,            );        }        return result_store.prepare(            arena,            config.tool_result_dir,            tool_call.id,            tool_call.name,            raw_output.len,            redacted_output,            config.max_tool_result_bytes,        );    }    const safe_output = try tool_result_limits.prepareModelOutput(        arena,        tool_call.name,        raw_output,        config.max_tool_result_bytes,    );    return .{        .model_output = safe_output,        .memory = .{            .output_bytes = raw_output.len,            .stored_output_bytes = safe_output.len,            .truncated = safe_output.len < raw_output.len,        },    };} pub fn applyToolResultMemory(    prepared: *types.ToolResultMemory,    source: ?types.ToolResultMemory,) void {    const source_memory = source orelse return;    prepared.command_output_replay = source_memory.command_output_replay;    prepared.command_process_presentation = source_memory.command_process_presentation;    const source_covers_full_file =        source_memory.model_view_covers_full_file orelse return;    prepared.model_view_covers_full_file =        source_covers_full_file and        !source_memory.truncated and        source_memory.output_handle == null and        !prepared.truncated and        prepared.output_handle == null;} /// Consumes a tentative interactive command capture after ordinary result/// storage has selected the exact source that completed Ctrl-O and resume will/// read. Exact round trips delete the candidate; every unprovable case keeps a/// private tagged replay descriptor.pub fn finalizeCommandReplay(    arena: Allocator,    tool_call: ToolCall,    prepared: *result_store.PreparedResult,    session_child_capability: ?*session_child_store.SessionChildCapability,    capture: ?*command_replay_store.Capture,) void {    const candidate = capture orelse return;    if (!isCapturedCommandCall(arena, tool_call)) {        candidate.abort(arena);        return;    }    if (!candidate.hasOutput()) {        candidate.discard(arena);        return;    }     var captured = candidate.canonicalizeForComparison(arena) catch |err| {        debug_trace.logf(            "session",            "command replay comparison unavailable call_id_bytes={d} err={s}",            .{ tool_call.id.len, @errorName(err) },        );        retainCommandReplay(arena, candidate, &prepared.memory);        return;    } orelse {        retainCommandReplay(arena, candidate, &prepared.memory);        return;    };    defer captured.deinit(arena);     const source = selectedCommandSource(        arena,        prepared.*,        session_child_capability,        candidate.comparisonLimit(),    ) catch |err| {        debug_trace.logf(            "session",            "command replay source comparison failed call_id_bytes={d} err={s}",            .{ tool_call.id.len, @errorName(err) },        );        retainCommandReplay(arena, candidate, &prepared.memory);        return;    } orelse {        retainCommandReplay(arena, candidate, &prepared.memory);        return;    };    defer arena.free(source);    var ordinary = (command_output_content.canonicalizeForegroundResult(        arena,        source,    ) catch |err| {        debug_trace.logf(            "session",            "command replay envelope comparison failed call_id_bytes={d} err={s}",            .{ tool_call.id.len, @errorName(err) },        );        retainCommandReplay(arena, candidate, &prepared.memory);        return;    }) orelse {        retainCommandReplay(arena, candidate, &prepared.memory);        return;    };    defer ordinary.deinit(arena);     if (command_output_content.eql(captured, ordinary)) {        candidate.discard(arena);        return;    }    retainCommandReplay(arena, candidate, &prepared.memory);} fn isCapturedCommandCall(arena: Allocator, call: ToolCall) bool {    if (std.mem.eql(u8, call.name, "run_command")) return true;    if (!std.mem.eql(u8, call.name, "terminal")) return false;    var parsed = std.json.parseFromSlice(std.json.Value, arena, call.arguments_json, .{}) catch return false;    defer parsed.deinit();    if (parsed.value != .object) return false;    const action = parsed.value.object.get("action") orelse return false;    return action == .string and std.mem.eql(u8, action.string, "exec");} fn selectedCommandSource(    arena: Allocator,    prepared: result_store.PreparedResult,    session_child_capability: ?*session_child_store.SessionChildCapability,    comparison_limit: usize,) !?[]u8 {    const source_limit = std.math.add(        usize,        comparison_limit,        command_output_content.max_foreground_result_envelope_bytes,    ) catch return null;    if (prepared.memory.output_handle) |handle| {        const capability = session_child_capability orelse return null;        var reader = try result_store.openReaderManaged(arena, capability, handle);        defer reader.deinit();        if (reader.size != prepared.memory.stored_output_bytes or            reader.size > source_limit) return null;         const source = try arena.alloc(u8, reader.size);        errdefer arena.free(source);        var offset: usize = 0;        while (offset < source.len) {            const page_len = @min(source.len - offset, 4 * 1024);            const page = try reader.readPage(arena, offset, page_len);            defer arena.free(page);            if (page.len != page_len) return error.UnexpectedEndOfResult;            @memcpy(source[offset..][0..page.len], page);            offset += page.len;        }        return source;    }    if (prepared.model_output.len > source_limit) return null;    const source = try execution_memory_helpers.redactText(arena, prepared.model_output);    if (source.len > source_limit) {        arena.free(source);        return null;    }    return source;} fn retainCommandReplay(    arena: Allocator,    candidate: *command_replay_store.Capture,    memory: *types.ToolResultMemory,) void {    memory.command_output_replay = candidate.retain(arena);} pub fn captureCommittedFilePresentation(    alloc: Allocator,    handoff: file_mutation.CommittedFileHandoff,) !types.CommittedFilePresentation {    const path = try alloc.dupe(u8, handoff.preview.path);    errdefer alloc.free(path);    const lines = try alloc.alloc(types.CommittedFilePresentationLine, handoff.preview.lines.len);    errdefer alloc.free(lines);    var copied_lines: usize = 0;    errdefer {        for (lines[0..copied_lines]) |line| alloc.free(@constCast(line.text));    }    for (handoff.preview.lines, 0..) |line, index| {        lines[index] = .{            .kind = switch (line.op) {                .context => .context,                .addition => .addition,                .deletion => .deletion,                .elision => .elision,                .notice => .notice,            },            .old_line = line.old_line,            .new_line = line.new_line,            .text = try alloc.dupe(u8, line.text),        };        copied_lines += 1;    }    const full_view = handoff.full_view;    const previous_content = if (full_view != null) if (handoff.tracker.previous_content) |content|        try alloc.dupe(u8, content)    else        null else null;    errdefer if (previous_content) |content| alloc.free(content);    const after_content = if (full_view) |full|        try alloc.dupe(u8, full.after_content)    else        null;    errdefer if (after_content) |content| alloc.free(content);    const lifecycle_id: ?types.ToolLifecycleId = if (full_view) |full| .{        .turn_id = full.lifecycle_id.turn_id,        .call_id = try alloc.dupe(u8, full.lifecycle_id.call_id),    } else null;    errdefer if (lifecycle_id) |id| alloc.free(@constCast(id.call_id));    return .{        .path = path,        .kind = switch (handoff.tracker.kind) {            .write => .added,            .edit => .edited,        },        .lines = lines,        .additions = handoff.preview.additions,        .deletions = handoff.preview.deletions,        .truncated = handoff.preview.truncated,        .previous_content = previous_content,        .after_content = after_content,        .lifecycle_id = lifecycle_id,    };} fn toolCall(id: []const u8, name: []const u8, args: []const u8) ToolCall {    return .{ .id = id, .name = name, .arguments_json = args };} test "command sidebands merge without file-view metadata" {    var prepared: types.ToolResultMemory = .{};    applyToolResultMemory(&prepared, .{        .command_output_replay = .unavailable,        .command_process_presentation = .{ .signal = 9 },    });    switch (prepared.command_output_replay.?) {        .unavailable => {},        .available => return error.TestExpectedUnavailableReplay,    }    try std.testing.expectEqual(        types.CommandProcessPresentation{ .signal = 9 },        prepared.command_process_presentation.?,    );    try std.testing.expect(prepared.model_view_covers_full_file == null);} test "exact command sources delete replay and missing handles retain it" {    const alloc = std.testing.allocator;    var tmp = std.testing.tmpDir(.{});    defer tmp.cleanup();    try tmp.dir.createDir(        io_mod.getIo(),        "session",        std.Io.File.Permissions.fromMode(0o700),    );    var session_dir = try tmp.dir.openDir(io_mod.getIo(), "session", .{        .iterate = true,        .follow_symlinks = false,    });    defer session_dir.close(io_mod.getIo());    const display_path = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "session");    defer alloc.free(display_path);    var capability = try session_child_store.SessionChildCapability.initForTesting(        alloc,        session_dir,        display_path,        .writable,        .{},    );    defer capability.deinit();    var arena_state = std.heap.ArenaAllocator.init(alloc);    defer arena_state.deinit();    const arena = arena_state.allocator();     const capture = try command_replay_store.Capture.create(arena, 46, &capability);    capture.appendAccepted(arena, .stdout, "o");    capture.appendAccepted(arena, .stdout, "n");    capture.appendAccepted(arena, .stdout, "e");    capture.appendAccepted(arena, .stdout, "\n");    capture.seal(arena);    var before = try capability.iterate(alloc, .command_artifacts);    defer before.deinit();    try std.testing.expectEqual(@as(usize, 1), before.names.len);     var cancel_flag = std.atomic.Value(bool).init(false);    var prepared = result_store.PreparedResult{        .model_output = "exit_code=0\n<stdout>\none\n</stdout>\n",        .memory = .{            .output_bytes = 42,            .stored_output_bytes = 42,        },    };    finalizeCommandReplay(        arena,        toolCall("command_exact", "run_command", "{}"),        &prepared,        &capability,        capture,    );     try std.testing.expect(prepared.memory.command_output_replay == null);    var after = try capability.iterate(alloc, .command_artifacts);    defer after.deinit();    try std.testing.expectEqual(@as(usize, 0), after.names.len);

Lines 1–500 of 821.