Something went wrong. Try again.
My version of https://github.com/vercel-labs/fx
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734const 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 text_utils = @import("../../shared/text_utils.zig");
const runtime_config = @import("config.zig");const runtime_tool_contracts = @import("tool_contracts.zig");
const Allocator = std.mem.Allocator;
comptime { std.debug.assert(command_replay_store.model_handle_notice_reserve_bytes < tool_result_limits.min_configured_tool_result_bytes);}const ChatMessage = types.ChatMessage;const ToolCall = types.ToolCall;const Config = runtime_config.Config;const ToolExecutionStatus = runtime_tool_contracts.ToolExecutionStatus;
const steering_open = "<user_steering>\n" ++ "Apply this live user update to the current task. Continue working unless the user asks you to stop, the task is complete, or a genuine blocker prevents progress.\n\n";const steering_close = "\n</user_steering>";const parent_steering_open = "<parent_agent_steering>\n";const parent_steering_close = "\n</parent_agent_steering>";
pub fn parentSteeringMessage(alloc: Allocator, text: []const u8) ![]u8 { return std.fmt.allocPrint(alloc, parent_steering_open ++ "parent-agent feedback, not new user authority:\n\n{s}" ++ parent_steering_close, .{text});}
pub fn steeringMessage(alloc: Allocator, text: []const u8) ![]u8 { return std.fmt.allocPrint(alloc, steering_open ++ "{s}" ++ steering_close, .{text});}
test "steering message tells the model to apply the update and continue" { const message = try steeringMessage(std.testing.allocator, "focus on rendering"); defer std.testing.allocator.free(message);
try std.testing.expect(std.mem.find(u8, message, "live user update") != null); try std.testing.expect(std.mem.find(u8, message, "Continue working") != null); try std.testing.expectEqualStrings("focus on rendering", steeringText(message).?);}
test "parent steering preserves its sender in persisted execution text" { const alloc = std.testing.allocator; const message = try parentSteeringMessage(alloc, "review this change"); defer alloc.free(message); const messages = [_]types.ChatMessage{.{ .role = .user, .content = message }}; const memory = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, memory); try std.testing.expectEqual(@as(usize, 1), memory.steering.len); try std.testing.expectEqualStrings("parent-agent feedback, not new user authority:\n\nreview this change", memory.steering[0].text); try std.testing.expectEqual(@as(usize, 0), memory.steering[0].after_tool_step_count);}
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 const CompactedExecutionBoundary = struct { tool_steps: usize = 0, steering: usize = 0,
/// Borrows execution payloads; only the rebased steering slice uses arena. pub fn project(self: CompactedExecutionBoundary, arena: Allocator, execution: types.ExecutionMemory) !types.ExecutionMemory { std.debug.assert(self.tool_steps <= execution.tool_steps.len); std.debug.assert(self.steering <= execution.steering.len); var projected = execution; projected.tool_steps = execution.tool_steps[self.tool_steps..]; projected.steering = execution.steering[self.steering..]; if (self.tool_steps > 0 and projected.steering.len > 0) { projected.steering = try arena.dupe(types.PersistedSteering, projected.steering); for (projected.steering) |*item| { std.debug.assert(item.after_tool_step_count >= self.tool_steps); item.after_tool_step_count -= self.tool_steps; } } return projected; }};
pub fn buildExecutionMemory(alloc: Allocator, within_turn_suffix: []const ChatMessage) !types.ExecutionMemory { var execution = try execution_memory_helpers.buildNormalChatExecutionMemory( alloc, within_turn_suffix, ); errdefer types.freeExecutionMemory(alloc, execution);
var steering: std.ArrayList(types.PersistedSteering) = .empty; errdefer { for (steering.items) |item| { alloc.free(item.text); if (item.assistant_prefix) |prefix| alloc.free(prefix); } steering.deinit(alloc); } var tool_step_count: usize = 0; var assistant_prefix: ?[]const u8 = null; for (within_turn_suffix, 0..) |message, index| { if (startsPersistedToolStep(within_turn_suffix, index)) { tool_step_count += 1; assistant_prefix = null; } else if (message.role == .assistant) { assistant_prefix = message.content; } if (message.role != .user) continue; const content = message.content orelse continue; const text = steeringText(content) orelse { assistant_prefix = null; continue; }; const copy = try alloc.dupe(u8, text); const prefix_copy = if (assistant_prefix) |prefix| alloc.dupe(u8, prefix) catch |err| { alloc.free(copy); return err; } else null; steering.append(alloc, .{ .text = copy, .assistant_prefix = prefix_copy, .after_tool_step_count = tool_step_count, }) catch |err| { alloc.free(copy); if (prefix_copy) |prefix| alloc.free(prefix); return err; }; assistant_prefix = null; } std.debug.assert(tool_step_count == execution.tool_steps.len); execution.steering = try steering.toOwnedSlice(alloc); return execution;}
fn startsPersistedToolStep(messages: []const ChatMessage, assistant_index: usize) bool { const assistant = messages[assistant_index]; if (assistant.role != .assistant) return false; if (assistant.tool_calls.len == 0) return assistant.provider_replay != null or assistant.standalone_response; var result_index = assistant_index + 1; while (result_index < messages.len and messages[result_index].role == .tool) : (result_index += 1) { const result_call_id = messages[result_index].tool_call_id orelse continue; for (assistant.tool_calls) |call| { if (std.mem.eql(u8, call.id, result_call_id)) return true; } } return false;}
/// Locate the same complete-exchange cut in the original wire messages. Do not/// rebuild retained tool results: their content and metadata remain untouched.pub fn retainedMessageOffset(messages: []const ChatMessage, cut: CompactedExecutionBoundary) !usize { if (cut.tool_steps == 0 and cut.steering == 0) return 0; var steps: usize = 0; var steering: usize = 0; var prefix_start: usize = 0; for (messages, 0..) |message, index| { if (startsPersistedToolStep(messages, index)) { if (steps == cut.tool_steps and steering == cut.steering) return prefix_start; steps += 1; // Keep following continuation prompts, but not the completed reply. if (message.tool_calls.len == 0) prefix_start = index + 1; } else if (message.role == .user and steeringText(message.content orelse "") != null) { if (steps == cut.tool_steps and steering == cut.steering) return prefix_start; steering += 1; prefix_start = index + 1; } if (message.role == .tool) prefix_start = index + 1; } if (steps != cut.tool_steps or steering != cut.steering) return error.InvalidContextHistoryStart; return messages.len;}
test "retained context keeps non-tool continuation messages at a zero cut" { const messages = [_]ChatMessage{.{ .role = .assistant, .content = "Current continuation text" }}; try std.testing.expectEqual(@as(usize, 0), try retainedMessageOffset(&messages, .{}));}
test "retained standalone cut rebuilds exactly the selected execution suffix" { const alloc = std.testing.allocator; const call = toolCall("retained", "read_file", "{\"path\":\"a.txt\"}"); for ([_]bool{ false, true }) |with_replay| { const messages = [_]ChatMessage{ .{ .role = .assistant, .content = if (with_replay) null else "candidate", .standalone_response = !with_replay, .provider_replay = if (with_replay) .{ .source = .{ .provider = .gateway, .model = "test" }, .parts_json = "[{\"type\":\"reasoning\",\"text\":\"private\"}]", } else null, }, .{ .role = .user, .content = "Continue the turn. fx hook context:\nverify" }, .{ .role = .assistant, .tool_calls = &.{call} }, .{ .role = .tool, .content = "result", .tool_call_id = call.id, .tool_name = call.name, .tool_result_status = .success }, }; const offset = try retainedMessageOffset(&messages, .{ .tool_steps = 1 }); try std.testing.expectEqual(@as(usize, 1), offset); const retained = try buildExecutionMemory(alloc, messages[offset..]); defer types.freeExecutionMemory(alloc, retained); try std.testing.expectEqual(@as(usize, 1), retained.tool_steps.len); try std.testing.expectEqualStrings(call.id, retained.tool_steps[0].tool_calls[0].id); try std.testing.expect(retained.tool_steps[0].provider_replay == null); }}
fn steeringText(content: []const u8) ?[]const u8 { if (std.mem.startsWith(u8, content, parent_steering_open) and std.mem.endsWith(u8, content, parent_steering_close)) { // Keep the sender label in persisted text and ordinary history replay. return content[parent_steering_open.len .. content.len - parent_steering_close.len]; } if (!std.mem.startsWith(u8, content, steering_open) or !std.mem.endsWith(u8, content, steering_close)) return null; return content[steering_open.len .. content.len - steering_close.len];}
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 active_group_index: ?usize = null; if (active_tool_call) |active| { for (current_turn_messages, 0..) |message, index| { for (message.tool_calls) |call| { if (std.mem.eql(u8, call.id, active.id)) active_group_index = index; } } } 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) { try filtered.append(alloc, item); 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 (active_group_index == i and std.mem.eql(u8, call.id, active.id)) continue; } if (hasToolResultForCall(result_messages, call.id)) { completed_count += 1; } }
if (item.provider_replay != null and completed_count != item.tool_calls.len) { debug_trace.logf("session", "provider replay omitted reason=interrupted_incomplete_association source_calls={d} completed_calls={d}", .{ item.tool_calls.len, completed_count }); } 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 (active_group_index == i and 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; if (completed_count != item.tool_calls.len) projected.provider_replay = null; 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);}
pub fn retainCancelledCommandReplay( arena: Allocator, result_memory: ?types.ToolResultMemory, capture: ?*command_replay_store.Capture,) ?types.CancelledCommandPresentation { if (capture) |candidate| { const replay: ?types.CommandOutputReplay = switch (candidate.policy()) { .required => blk: { const descriptor = candidate.retainRequired(arena) catch |err| { debug_trace.logf( "session", "cancelled command replay retention unavailable err={s}", .{@errorName(err)}, ); break :blk .unavailable; } orelse break :blk null; break :blk .{ .available = descriptor }; }, .best_effort => candidate.retain(arena), }; if (replay) |retained| return .{ .output_replay = retained }; } const replay = if (result_memory) |memory| memory.command_output_replay else null; return if (replay) |value| .{ .output_replay = value } else null;}
test "interrupted execution keeps replay only for an unchanged completed batch" { const alloc = std.testing.allocator; const call = toolCall("completed", "read_file", "{\"path\":\"a.txt\"}"); const replay: types.ProviderReplay = .{ .source = .{ .provider = .gateway, .model = "test" }, .parts_json = "[{\"type\":\"reasoning\",\"text\":\"kept\"},{\"type\":\"tool-call\",\"toolCallId\":\"completed\"}]", }; const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = &.{call}, .provider_replay = replay }, .{ .role = .tool, .content = "result", .tool_call_id = call.id, .tool_name = call.name, .tool_result_status = .success }, }; const completed = try buildInterruptedExecutionMemory(alloc, &messages, null); defer types.freeExecutionMemory(alloc, completed); try std.testing.expectEqual(@as(usize, 1), completed.tool_steps.len); try std.testing.expectEqualStrings(replay.parts_json, completed.tool_steps[0].provider_replay.?.parts_json); const cancelled = try buildInterruptedExecutionMemory(alloc, &messages, call); defer types.freeExecutionMemory(alloc, cancelled); try std.testing.expectEqual(@as(usize, 0), cancelled.tool_steps.len); try std.testing.expectEqualStrings(replay.parts_json, messages[0].provider_replay.?.parts_json);}
test "interrupted execution preserves a compacted prefix when later calls reuse an id" { const alloc = std.testing.allocator; const guidance = try steeringMessage(alloc, "Keep the original constraint."); defer alloc.free(guidance); const call = toolCall("reused", "read_file", "{\"path\":\"first.txt\"}"); const messages = [_]ChatMessage{ .{ .role = .user, .content = guidance }, .{ .role = .assistant, .tool_calls = &.{call} }, .{ .role = .tool, .content = "completed", .tool_call_id = call.id, .tool_name = call.name, .tool_result_status = .success }, .{ .role = .assistant, .tool_calls = &.{call} }, }; const interrupted = try buildInterruptedExecutionMemory(alloc, &messages, call); defer types.freeExecutionMemory(alloc, interrupted); try std.testing.expectEqual(@as(usize, 1), interrupted.tool_steps.len); try std.testing.expectEqual(@as(usize, 1), interrupted.steering.len); var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const boundary = CompactedExecutionBoundary{ .tool_steps = 1, .steering = 1 }; const suffix = try boundary.project(arena.allocator(), interrupted); try std.testing.expectEqual(@as(usize, 0), suffix.tool_steps.len); try std.testing.expectEqual(@as(usize, 0), suffix.steering.len);}
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 = "custom 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 prepareToolExecutionOutput( arena: Allocator, config: Config, tool_call: ToolCall, execution: runtime_tool_contracts.ToolExecutionResult, capture: ?*command_replay_store.Capture,) !result_store.PreparedResult { if (execution.model_content_kind != .complete_skill or execution.status != .success) { return prepareCapturedToolModelOutput(arena, config, tool_call, execution.model_output, capture); } if (capture != null) return error.InvalidSkillContentResult; const full = try tool_result_limits.prepareSanitizedOutput(arena, execution.model_output); errdefer arena.free(full); if (full.len > config.max_tool_result_bytes) return error.SkillContentLimitExceeded; var prepared = try prepareCapturedToolModelOutput(arena, config, tool_call, full, null); arena.free(prepared.model_output); prepared.model_output = full; prepared.memory.truncated = false; return prepared;}
pub fn prepareToolModelOutput( arena: Allocator, config: Config, tool_call: ToolCall, raw_output: []const u8,) !result_store.PreparedResult { return prepareCapturedToolModelOutput( arena, config, tool_call, raw_output, null, );}
pub fn prepareCapturedToolModelOutput( arena: Allocator, config: Config, tool_call: ToolCall, raw_output: []const u8, capture: ?*command_replay_store.Capture,) !result_store.PreparedResult { if (std.mem.eql(u8, tool_call.name, "read_tool_result")) { const prepared = try tool_result_limits.prepareModelOutputWithTruncation( arena, tool_call.name, raw_output, config.max_tool_result_bytes, ); errdefer arena.free(prepared.model_output); var memory: types.ToolResultMemory = .{ .output_bytes = raw_output.len, .stored_output_bytes = prepared.model_output.len, .truncated = prepared.truncated, }; if (prepared.truncated and (config.session_child_capability != null or config.tool_result_dir != null)) { const complete_output = try text_utils.sanitizeModelText(arena, raw_output); defer if (complete_output.ptr != raw_output.ptr) arena.free(complete_output); memory.output_handle = if (config.session_child_capability) |capability| try result_store.storeLargeResultManaged(arena, capability, tool_call.id, tool_call.name, complete_output) else try result_store.storeLargeResult(arena, config.tool_result_dir.?, tool_call.id, tool_call.name, complete_output); memory.stored_output_bytes = complete_output.len; } return .{ .model_output = prepared.model_output, .memory = memory, }; } const required_command_replay = if (capture) |candidate| candidate.policy() == .required else false; if (!required_command_replay and (config.session_child_capability != null or config.tool_result_dir != null)) { const sanitized_output = try tool_result_limits.prepareSanitizedOutput( 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, sanitized_output, config.max_tool_result_bytes, ); } return result_store.prepare( arena, config.tool_result_dir, tool_call.id, tool_call.name, raw_output.len, sanitized_output, config.max_tool_result_bytes, ); } const model_output_budget = if (required_command_replay) config.max_tool_result_bytes -| command_replay_store.model_handle_notice_reserve_bytes else config.max_tool_result_bytes; const prepared = try tool_result_limits.prepareModelOutputWithTruncation( arena, tool_call.name, raw_output, model_output_budget, ); return .{ .model_output = prepared.model_output, .memory = .{ .output_bytes = raw_output.len, .stored_output_bytes = prepared.model_output.len, .truncated = prepared.truncated, }, };}
pub fn retainToolImages(arena: Allocator, config: Config, call: ToolCall, prepared: *result_store.PreparedResult) !void { const memory = &prepared.memory; if (memory.tool_images.len == 0 or memory.tool_image_handle != null) return; const capability = config.session_child_capability orelse return; const handle = result_store.storeToolImages(arena, capability, call.id, call.name, memory.tool_images) catch |err| { if (err == error.OutOfMemory) return error.OutOfMemory; debug_trace.logf("session", "tool images remain inline because artifact storage failed call_id={s} err={s}", .{ call.id, @errorName(err) }); return; }; memory.tool_image_handle = handle; const notice = try std.fmt.allocPrint(arena, "Saved images: {s}. Use read_tool_result with this handle to load them again.\n", .{handle}); const limit = config.max_tool_result_bytes -| notice.len; const keep = @import("../../config/context_limits.zig").utf8PrefixLength(prepared.model_output, limit); if (keep < prepared.model_output.len) memory.truncated = true; prepared.model_output = try std.mem.concat(arena, u8, &.{ notice, prepared.model_output[0..keep] });}
pub fn materializeToolImages(arena: Allocator, config: Config, messages: []const ChatMessage) ![]const ChatMessage { const needed = for (messages) |message| { if (message.tool_result_memory) |memory| if (memory.tool_images.len == 0 and memory.tool_image_handle != null) break true; } else false; if (!needed) return messages; const materialized = try arena.dupe(ChatMessage, messages); for (materialized) |*message| { if (message.tool_result_memory) |*memory| { if (memory.tool_images.len > 0) continue; const handle = memory.tool_image_handle orelse continue; if (config.session_child_capability) |capability| { memory.tool_images = result_store.loadToolImages(arena, capability, handle) catch |err| { if (err == error.OutOfMemory) return error.OutOfMemory; const notice = try std.fmt.allocPrint(arena, "[Stored tool image unavailable: {s}]\n", .{@errorName(err)}); message.content = try prependImageNotice(arena, notice, message.content orelse "", config.max_tool_result_bytes); continue; }; } else { message.content = try prependImageNotice(arena, "[Stored tool image is unavailable in this session.]\n", message.content orelse "", config.max_tool_result_bytes); } } } return materialized;}
fn prependImageNotice(alloc: Allocator, notice: []const u8, content: []const u8, limit: usize) Allocator.Error![]u8 { const keep = @import("../../config/context_limits.zig").utf8PrefixLength(content, limit -| notice.len); return std.mem.concat(alloc, u8, &.{ notice[0..@min(notice.len, limit)], content[0..keep] });}
pub fn applyToolResultMemory( prepared: *types.ToolResultMemory, source: ?types.ToolResultMemory,) void { const source_memory = source orelse return; prepared.review_feedback = source_memory.review_feedback; prepared.tool_images = source_memory.tool_images; prepared.tool_image_handle = source_memory.tool_image_handle; prepared.command_output_replay = source_memory.command_output_replay; prepared.command_process_presentation = source_memory.command_process_presentation; prepared.terminal_action_presentation = source_memory.terminal_action_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;}
/// Finalizes tentative command capture after bounded model output is prepared./// Required native exec always retains one authoritative replay and publishes/// its handle; legacy exact round trips keep the existing discard optimization.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 (!candidate.hasOutput()) { candidate.discard(arena); return; } if (candidate.policy() == .required) { const descriptor = (try candidate.retainRequired(arena)) orelse return; prepared.memory.command_output_replay = .{ .available = descriptor }; prepared.model_output = try command_replay_store.appendModelHandleNotice( arena, prepared.model_output, descriptor.handle, ); prepared.memory.stored_output_bytes = prepared.model_output.len; 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 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 arena.dupe(u8, 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 }, .terminal_action_presentation = .{ .returned = .safety_ceiling }, }); 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.expectEqual( types.TerminalActionPresentation{ .returned = .safety_ceiling }, prepared.terminal_action_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, }, }; try 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);
var body: std.ArrayList(u8) = .empty; defer body.deinit(arena); try body.appendNTimes(arena, 'x', 80 * 1024); const stored_source = try std.fmt.allocPrint( arena, "exit_code=0\n<stdout>\n{s}\n</stdout>\n", .{body.items}, ); const stored_capture = try command_replay_store.Capture.create( arena, 1024, &capability, ); stored_capture.setComparisonLimit(body.items.len); stored_capture.appendAccepted(arena, .stdout, body.items); stored_capture.seal(arena); var stored_prepared = try prepareCapturedToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel_flag, .session_child_capability = &capability, }, toolCall("command_stored_exact", "run_command", "{}"), stored_source, stored_capture, ); try std.testing.expect(stored_prepared.memory.output_handle != null); try finalizeCommandReplay( arena, toolCall("command_stored_exact", "run_command", "{}"), &stored_prepared, &capability, stored_capture, ); try std.testing.expect(stored_prepared.memory.command_output_replay == null); var after_stored = try capability.iterate(alloc, .command_artifacts); defer after_stored.deinit(); try std.testing.expectEqual(@as(usize, 0), after_stored.names.len);
const TransformCase = struct { raw: []const u8, projected: []const u8, }; const transform_cases = [_]TransformCase{ .{ .raw = "\x00\n", .projected = "\\x00" }, .{ .raw = "\xff\n", .projected = "\\xff" }, .{ .raw = "\xc2\x80\n", .projected = "\\u{0080}" }, .{ .raw = "\x1b[31mred\x1b[0m\n", .projected = "red" }, .{ .raw = "old\rnew\n", .projected = "new" }, }; for (transform_cases) |case| { const transformed_capture = try command_replay_store.Capture.create( arena, 256, &capability, ); transformed_capture.appendAccepted(arena, .stdout, case.raw); transformed_capture.seal(arena); const projected_source = try std.fmt.allocPrint( arena, "exit_code=0\n<stdout>\n{s}\n</stdout>\n", .{case.projected}, ); var transformed_prepared = result_store.PreparedResult{ .model_output = projected_source, .memory = .{ .output_bytes = projected_source.len, .stored_output_bytes = projected_source.len, }, }; try finalizeCommandReplay( arena, toolCall("command_transformed", "run_command", "{}"), &transformed_prepared, &capability, transformed_capture, ); const transformed_replay = transformed_prepared.memory.command_output_replay orelse return error.TestExpectedReplay; switch (transformed_replay) { .available => {}, .unavailable => return error.TestExpectedReplay, } }
var before_literal = try capability.iterate(alloc, .command_artifacts); defer before_literal.deinit(); const literal_capture = try command_replay_store.Capture.create( arena, 1, &capability, ); literal_capture.setComparisonLimit(256); literal_capture.appendAccepted(arena, .stdout, "\\x00\n"); literal_capture.seal(arena); var literal_prepared = result_store.PreparedResult{ .model_output = "exit_code=0\n<stdout>\n\\x00\n</stdout>\n", .memory = .{ .output_bytes = 40, .stored_output_bytes = 40, }, }; try finalizeCommandReplay( arena, toolCall("command_literal", "run_command", "{}"), &literal_prepared, &capability, literal_capture, ); try std.testing.expect(literal_prepared.memory.command_output_replay == null); var after_literal = try capability.iterate(alloc, .command_artifacts); defer after_literal.deinit(); try std.testing.expectEqual(before_literal.names.len, after_literal.names.len);
const missing_capture = try command_replay_store.Capture.create( arena, 64 * 1024, &capability, ); missing_capture.appendAccepted(arena, .stdout, "one\n"); missing_capture.seal(arena); var missing_prepared = result_store.PreparedResult{ .model_output = "stored result preview", .memory = .{ .output_handle = "result-run-command-missing.txt", .output_bytes = 42, .stored_output_bytes = 42, .truncated = true, }, }; try finalizeCommandReplay( arena, toolCall("command_missing", "run_command", "{}"), &missing_prepared, &capability, missing_capture, ); const retained = missing_prepared.memory.command_output_replay orelse return error.TestExpectedReplay; switch (retained) { .available => {}, .unavailable => return error.TestExpectedReplay, } var after_missing = try capability.iterate(alloc, .command_artifacts); defer after_missing.deinit(); try std.testing.expectEqual(transform_cases.len + 1, after_missing.names.len);}
test "required terminal exec retains exact replay and publishes its handle" { 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, 64 * 1024, &capability); capture.setPolicyBeforeCapture(.required); capture.appendAccepted(arena, .stdout, "one\n"); capture.seal(arena); var prepared = result_store.PreparedResult{ .model_output = "exit_code=0\n<stdout>\none\n</stdout>\n", .memory = .{ .output_bytes = 42, .stored_output_bytes = 42, }, };
try finalizeCommandReplay( arena, toolCall( "terminal_exact", "terminal", "{\"action\":\"exec\",\"command\":\"printf one\",\"timeout_ms\":600000}", ), &prepared, &capability, capture, );
const replay = prepared.memory.command_output_replay orelse return error.TestExpectedReplay; const descriptor = switch (replay) { .available => |value| value, .unavailable => return error.TestExpectedReplay, }; try std.testing.expect(std.mem.find(u8, prepared.model_output, descriptor.handle) != null); capture.releaseRetained(arena);}
test "required terminal exec stores large output only as replay" { 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(); var cancel = std.atomic.Value(bool).init(false); const body = try arena.alloc(u8, result_store.large_result_threshold_bytes + 1024); @memset(body, 'x'); const raw_output = try std.fmt.allocPrint( arena, "exit_code=0\n<stdout>\n{s}\n</stdout>\n", .{body}, ); const tool_call = toolCall( "terminal_large", "terminal", "{}", ); const capture = try command_replay_store.Capture.create(arena, 1024, &capability); capture.setPolicyBeforeCapture(.required); try capture.appendAcceptedRequired(arena, .stdout, body); var prepared = try prepareCapturedToolModelOutput(arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.min_configured_tool_result_bytes, .cancel_flag = &cancel, .session_child_capability = &capability, }, tool_call, raw_output, capture); try std.testing.expect(prepared.memory.output_handle == null); try finalizeCommandReplay( arena, tool_call, &prepared, &capability, capture, ); defer capture.releaseRetained(arena); try std.testing.expect( prepared.model_output.len <= tool_result_limits.min_configured_tool_result_bytes, ); try std.testing.expect(std.mem.find(u8, prepared.model_output, "<command_output_handle>") != null);
var command_artifacts = try capability.iterate(alloc, .command_artifacts); defer command_artifacts.deinit(); try std.testing.expectEqual(@as(usize, 1), command_artifacts.names.len); var tool_results = try capability.iterate(alloc, .tool_results); defer tool_results.deinit(); try std.testing.expectEqual(@as(usize, 0), tool_results.names.len);}
test "saved preparation stores complete output verbatim on cap loss" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(result_dir); var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var cancel = std.atomic.Value(bool).init(false); const raw = "MY_UNIT_TOKEN=abcdefgh" ** 47; try std.testing.expect(raw.len > tool_result_limits.min_configured_tool_result_bytes);
const prepared = try prepareToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.min_configured_tool_result_bytes, .cancel_flag = &cancel, .tool_result_dir = result_dir, }, toolCall("call_expanded_output", "read_file", "{}"), raw, );
const handle = prepared.memory.output_handle orelse return error.TestExpectedStoredResult; try std.testing.expect(prepared.memory.truncated); const stored = try result_store.readByRange( alloc, result_dir, handle, 1, result_store.read_max_bytes, ); defer alloc.free(stored); try std.testing.expect(std.mem.find(u8, stored, "MY_UNIT_TOKEN=abcdefgh") != null); try std.testing.expect(std.mem.find(u8, stored, "[redacted]") == null);}
test "no-save preparation preserves capped output verbatim without a result handle" { const alloc = std.testing.allocator; var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var cancel = std.atomic.Value(bool).init(false); const raw = "MY_UNIT_TOKEN=abcdefgh" ** 47;
const prepared = try prepareToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.min_configured_tool_result_bytes, .cancel_flag = &cancel, }, toolCall("call_no_save_output", "read_file", "{}"), raw, );
try std.testing.expect(prepared.memory.output_handle == null); try std.testing.expect(prepared.memory.truncated); try std.testing.expect(std.mem.find(u8, prepared.model_output, "tool result truncated") != null); try std.testing.expect(std.mem.find(u8, prepared.model_output, "MY_UNIT_TOKEN=abcdefgh") != null); try std.testing.expect(std.mem.find(u8, prepared.model_output, "[redacted]") == null);}
test "saved read_tool_result preparation preserves exact secret-like output" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(result_dir); var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var cancel = std.atomic.Value(bool).init(false); const raw = "<command_output_query handle=\"fixture.bin\">\n" ++ "query: \"TOOL_DATA_TOKEN=\"\n" ++ "[stdout]\nTOOL_DATA_TOKEN=0123456789abcdef01234567\n[/stdout]\n" ++ "</command_output_query>";
const prepared = try prepareToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.default_max_tool_result_bytes, .cancel_flag = &cancel, .tool_result_dir = result_dir, }, toolCall("read_exact_output", "read_tool_result", "{}"), raw, );
try std.testing.expectEqualStrings(raw, prepared.model_output); try std.testing.expect(std.mem.find(u8, prepared.model_output, "[redacted]") == null); try std.testing.expect(!prepared.memory.truncated); try std.testing.expect(prepared.memory.output_handle == null);}
test "retrieved output remains backed across the inline cap" { const compaction = @import("context_compaction.zig"); const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(dir); var capability = try session_child_store.SessionChildCapability.initLegacyRoute(alloc, dir, .tool_results, .writable); defer capability.deinit(); var cancel = std.atomic.Value(bool).init(false); const stores = [_]enum { legacy, managed, unavailable }{ .legacy, .managed, .unavailable }; const cases = [_]struct { cap: usize, bytes: usize }{ .{ .cap = 65536, .bytes = 65536 }, .{ .cap = 65536, .bytes = 65537 }, .{ .cap = 65536, .bytes = 65689 }, .{ .cap = 1024, .bytes = 1025 }, }; for (stores) |storage| for (cases) |case| { var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); const raw = try arena.alloc(u8, case.bytes); @memset(raw, 'x'); const head = "PROBE_TOKEN=0123456789abcdef\n"; const tail = "\nRETRIEVAL_COMPLETE_TAIL"; @memcpy(raw[0..head.len], head); @memcpy(raw[raw.len - tail.len ..], tail); const prepared = try prepareToolModelOutput(arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel, .max_tool_result_bytes = case.cap, .tool_result_dir = if (storage == .legacy) dir else null, .session_child_capability = if (storage == .managed) &capability else null, }, toolCall("retrieval-cap", "read_tool_result", "{}"), raw); try std.testing.expectEqual(case.bytes > case.cap, prepared.memory.truncated); try std.testing.expectEqual(@min(case.bytes, case.cap), prepared.model_output.len); try std.testing.expect(std.mem.startsWith(u8, prepared.model_output, head)); try std.testing.expect(std.mem.find(u8, prepared.model_output, "[redacted]") == null); var messages = [_]ChatMessage{.{ .role = .tool, .tool_call_id = "retrieval-cap", .tool_name = "read_tool_result", .content = prepared.model_output, .tool_result_memory = prepared.memory }}; if (case.bytes > case.cap and storage != .unavailable) { const handle = prepared.memory.output_handle orelse return error.TestExpectedStoredRetrieval; try std.testing.expectEqual(case.bytes, prepared.memory.stored_output_bytes); const stored = try result_store.readForReplayManaged(arena, &capability, handle, case.bytes); try std.testing.expectEqualStrings(raw, stored); try compaction.promoteMessageResults(arena, &messages, .{ .managed = &capability }, 0); try std.testing.expectEqualStrings(handle, messages[0].tool_result_memory.?.output_handle.?); } else { try std.testing.expect(prepared.memory.output_handle == null); if (case.bytes > case.cap) { try std.testing.expectError(error.IncompleteCompactionResult, compaction.promoteMessageResults(arena, &messages, .unavailable, 0)); } else { try std.testing.expectEqualStrings(raw, prepared.model_output); } } };}
test "retrieved output storage failure does not publish an unbacked result" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(dir); var writable = try session_child_store.SessionChildCapability.initLegacyRoute(alloc, dir, .tool_results, .writable); defer writable.deinit(); var readonly = try session_child_store.SessionChildCapability.initLegacyRoute(alloc, dir, .tool_results, .read_only); defer readonly.deinit(); var cancel = std.atomic.Value(bool).init(false); try std.testing.expectError(error.SessionChildReadOnly, prepareToolModelOutput(alloc, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel, .max_tool_result_bytes = 1024, .session_child_capability = &readonly, }, toolCall("retrieval-store-error", "read_tool_result", "{}"), "x" ** 1025));}
test "saved tool output preparation keeps builtins and dynamic tools compactable" { const builtins = @import("../../../builtins/tools.zig"); const compaction = @import("context_compaction.zig"); const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(dir); var cancel = std.atomic.Value(bool).init(false); for (0..builtins.all.len + 1) |index| { const name = if (index < builtins.all.len) builtins.all[index].name else "mcp_fixture_lookup"; var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); const raw = "COMPLETE_TOOL_BODY\n" ++ "x" ** 8192; const prepared = try prepareToolModelOutput(arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel, .max_tool_result_bytes = 1024, .tool_result_dir = dir, }, toolCall("tool-preparation", name, "{}"), raw); const handle = prepared.memory.output_handle orelse return error.TestExpectedStoredOutput; try std.testing.expectEqual(raw.len, prepared.memory.stored_output_bytes); var messages = [_]ChatMessage{.{ .role = .tool, .tool_call_id = "tool-preparation", .tool_name = name, .content = prepared.model_output, .tool_result_memory = prepared.memory }}; try compaction.promoteMessageResults(arena, &messages, .{ .legacy_dir = dir }, 0); try std.testing.expectEqualStrings(handle, messages[0].tool_result_memory.?.output_handle.?); const stored = try result_store.readByRange(arena, dir, handle, 1, 16384); try std.testing.expect(std.mem.find(u8, stored, raw) != null); }}
test "saved preparation externalizes complete output verbatim without cap loss" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(result_dir); var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var cancel = std.atomic.Value(bool).init(false); const raw = "AI_GATEWAY_API_KEY=abcdefghijklmnop";
const prepared = try prepareToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.default_max_tool_result_bytes, .cancel_flag = &cancel, .tool_result_dir = result_dir, }, toolCall("call_external_output", "read_file", "{}"), raw, );
try std.testing.expect(prepared.memory.output_handle != null); try std.testing.expectEqualStrings(raw, prepared.memory.preview.?); try std.testing.expect(!prepared.memory.truncated); try std.testing.expectEqualStrings(raw, prepared.model_output);}
test "saved inline read retains full file evidence with its external copy" { const alloc = std.testing.allocator; var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(result_dir); var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var cancel = std.atomic.Value(bool).init(false); const call = toolCall("complete-read", "read_file", "{\"path\":\"small.txt\"}"); var prepared = try prepareToolModelOutput(arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel, .tool_result_dir = result_dir, }, call, "all file contents"); applyToolResultMemory(&prepared.memory, .{ .model_view_covers_full_file = true }); try std.testing.expect(prepared.memory.output_handle != null); try std.testing.expectEqualStrings("all file contents", prepared.model_output); const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = &.{call} }, .{ .role = .tool, .content = prepared.model_output, .tool_call_id = call.id, .tool_name = call.name, .tool_result_status = .success, .tool_result_memory = prepared.memory, }, }; const execution = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, execution); try std.testing.expectEqual(@as(usize, 1), execution.files.len); try std.testing.expect(execution.files[0].model_view_covers_full_file);}
test "common execution memory does not mark stored read previews as full" { const session_runtime = @import("../../session/session.zig"); const alloc = std.testing.allocator; var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const result_dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(result_dir); var raw: std.ArrayList(u8) = .empty; defer raw.deinit(alloc); try raw.appendNTimes( alloc, 'x', result_store.large_result_threshold_bytes + 128, ); var cancel_flag = std.atomic.Value(bool).init(false); const prepared = try prepareToolModelOutput( arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .cancel_flag = &cancel_flag, .tool_result_dir = result_dir, }, toolCall( "call_large_read", "read_file", "{\"path\":\"large.txt\"}", ), raw.items, ); try std.testing.expect(prepared.memory.output_handle != null); try std.testing.expect(prepared.memory.truncated);
var calls = [_]ToolCall{.{ .id = "call_large_read", .name = "read_file", .arguments_json = "{\"path\":\"large.txt\"}", }}; const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = calls[0..] }, .{ .role = .tool, .content = prepared.model_output, .tool_call_id = "call_large_read", .tool_name = "read_file", .tool_result_status = .success, .tool_result_memory = prepared.memory, }, };
const execution = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, execution);
try std.testing.expectEqual(@as(usize, 1), execution.tool_steps.len); try std.testing.expect(execution.tool_steps[0].tool_results[0].truncated); try std.testing.expectEqual(@as(usize, 1), execution.files.len); try std.testing.expect(!execution.files[0].model_view_covers_full_file);
const replay = try session_runtime.formatExecutionFileContext(alloc, execution.files); defer alloc.free(replay); try std.testing.expect(std.mem.find(u8, replay, "model_view=full") == null);}
test "execution memory persists secret argument values verbatim" { const alloc = std.testing.allocator; const arguments_json = "{\"command\":\"echo ok\",\"api_key\":\"secret-value-123456\"}"; var calls = [_]ToolCall{.{ .id = "call_secret", .name = "run_command", .arguments_json = arguments_json, }}; const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = calls[0..] }, .{ .role = .tool, .content = "ok", .tool_call_id = "call_secret", .tool_name = "run_command", .tool_result_status = .success }, };
const memory = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, memory); try std.testing.expectEqual(@as(usize, 1), memory.tool_steps.len); const args = memory.tool_steps[0].tool_calls[0].arguments_json; try std.testing.expectEqualStrings(arguments_json, args); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, args, .{}); defer parsed.deinit(); try std.testing.expectEqualStrings("secret-value-123456", parsed.value.object.get("api_key").?.string); try std.testing.expectEqualStrings("echo ok", parsed.value.object.get("command").?.string);}
test "execution memory persists credentialed web_fetch url arguments verbatim" { const alloc = std.testing.allocator; const url = "https://user:pass@example.com/docs?token=querytoken123"; const arguments_json = "{\"url\":\"https://user:pass@example.com/docs?token=querytoken123\"}"; var calls = [_]ToolCall{.{ .id = "call_fetch", .name = "web_fetch", .arguments_json = arguments_json, }}; const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = calls[0..] }, .{ .role = .tool, .content = "Web fetch result.", .tool_call_id = "call_fetch", .tool_name = "web_fetch", .tool_result_status = .success }, };
const memory = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, memory); const args = memory.tool_steps[0].tool_calls[0].arguments_json; try std.testing.expectEqualStrings(arguments_json, args); var parsed = try std.json.parseFromSlice(std.json.Value, alloc, args, .{}); defer parsed.deinit(); try std.testing.expect(parsed.value.object.get("prompt") == null); try std.testing.expectEqualStrings(url, parsed.value.object.get("url").?.string);}
test "large result storage persists secret-bearing output verbatim in preview and on disk" { const alloc = std.testing.allocator; var arena_state = std.heap.ArenaAllocator.init(alloc); defer arena_state.deinit(); const arena = arena_state.allocator(); var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); const dir = try io_mod.dirRealpathAlloc(alloc, tmp.dir, "."); defer alloc.free(dir);
var raw: std.ArrayList(u8) = .empty; defer raw.deinit(alloc); try raw.appendSlice(alloc, "api_key=super-secret-value" ++ "\n"); try raw.appendNTimes(alloc, 'x', result_store.large_result_threshold_bytes + 64);
var cancel_flag = std.atomic.Value(bool).init(false); const prepared = try prepareToolModelOutput(arena, .{ .system_prompt = "", .gateway_retry_count = 0, .gateway_chat_url = "", .agent_step_limit = 1, .max_tool_result_bytes = tool_result_limits.default_max_tool_result_bytes, .cancel_flag = &cancel_flag, .tool_result_dir = dir, }, toolCall("call_secret_large", "run_command", "{}"), raw.items);
try std.testing.expect(prepared.memory.output_handle != null); try std.testing.expect(std.mem.find(u8, prepared.model_output, "api_key=super-secret-value") != null); try std.testing.expect(std.mem.find(u8, prepared.model_output, "[redacted]") == null);
const stored = try result_store.readByRange(alloc, dir, prepared.memory.output_handle.?, 1, 512); defer alloc.free(stored); try std.testing.expect(std.mem.find(u8, stored, "api_key=super-secret-value") != null); try std.testing.expect(std.mem.find(u8, stored, "[redacted]") == null);}
test "execution memory persists consumed steering without protocol wrappers" { const alloc = std.testing.allocator; const first = try steeringMessage(alloc, "focus on rendering"); defer alloc.free(first); const second = try steeringMessage(alloc, "run the focused test"); defer alloc.free(second); const messages = [_]ChatMessage{ .{ .role = .user, .content = "ordinary user context" }, .{ .role = .user, .content = first }, .{ .role = .assistant, .content = "continuing" }, .{ .role = .user, .content = second }, };
const execution = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, execution);
try std.testing.expectEqual(@as(usize, 2), execution.steering.len); try std.testing.expectEqualStrings("focus on rendering", execution.steering[0].text); try std.testing.expectEqualStrings("run the focused test", execution.steering[1].text); try std.testing.expectEqual(@as(usize, 0), execution.steering[0].after_tool_step_count); try std.testing.expectEqual(@as(usize, 0), execution.steering[1].after_tool_step_count);}
test "execution memory records each steering tool boundary" { const alloc = std.testing.allocator; const first_steering = try steeringMessage(alloc, "after first"); defer alloc.free(first_steering); const second_steering = try steeringMessage(alloc, "after second"); defer alloc.free(second_steering); var first_calls = [_]ToolCall{.{ .id = "call_first", .name = "read_file", .arguments_json = "{\"path\":\"first\"}", }}; var second_calls = [_]ToolCall{.{ .id = "call_second", .name = "read_file", .arguments_json = "{\"path\":\"second\"}", }}; const messages = [_]ChatMessage{ .{ .role = .assistant, .tool_calls = &first_calls }, .{ .role = .tool, .content = "first result", .tool_call_id = "call_first", .tool_name = "read_file", .tool_result_status = .success }, .{ .role = .user, .content = first_steering }, .{ .role = .assistant, .tool_calls = &second_calls }, .{ .role = .tool, .content = "second result", .tool_call_id = "call_second", .tool_name = "read_file", .tool_result_status = .success }, .{ .role = .user, .content = second_steering }, };
const execution = try buildExecutionMemory(alloc, &messages); defer types.freeExecutionMemory(alloc, execution);
try std.testing.expectEqual(@as(usize, 2), execution.tool_steps.len); try std.testing.expectEqual(@as(usize, 2), execution.steering.len); try std.testing.expectEqualStrings("after first", execution.steering[0].text); try std.testing.expectEqual(@as(usize, 1), execution.steering[0].after_tool_step_count); try std.testing.expectEqualStrings("after second", execution.steering[1].text); try std.testing.expectEqual(@as(usize, 2), execution.steering[1].after_tool_step_count);
var arena = std.heap.ArenaAllocator.init(alloc); defer arena.deinit(); const boundary = CompactedExecutionBoundary{ .tool_steps = 1, .steering = 1 }; const remaining = try boundary.project(arena.allocator(), execution); try std.testing.expectEqual(@as(usize, 1), remaining.tool_steps.len); try std.testing.expectEqualStrings("call_second", remaining.tool_steps[0].tool_calls[0].id); try std.testing.expectEqual(@as(usize, 1), remaining.steering.len); try std.testing.expectEqualStrings("after second", remaining.steering[0].text); try std.testing.expectEqual(@as(usize, 1), remaining.steering[0].after_tool_step_count); try std.testing.expectEqual(@as(usize, 2), remaining.files.len); try std.testing.expect(remaining.files.ptr == execution.files.ptr); try std.testing.expectEqual(@as(usize, 2), execution.steering[1].after_tool_step_count);}
test "transcript does not mark native web_search as provider resource placeholder" { const alloc = std.testing.allocator; const record = try execution_memory_helpers.makePersistedToolResult( alloc, "call_search", "web_search", .success, "bounded search output", null, ); const records = try alloc.alloc(types.PersistedToolResult, 1); records[0] = record; defer types.freePersistedToolResults(alloc, records);
try std.testing.expect(!records[0].provider_native); try std.testing.expectEqualStrings("bounded search output", records[0].output);}