const std = @import("std");const hooks = @import("../../hooks/hooks.zig");const types = @import("../../shared/types.zig");const debug_trace = @import("../../shared/debug_trace.zig");const deps_mod = @import("deps.zig");const execution_memory = @import("execution_memory.zig");const lifecycle_runtime = @import("lifecycle.zig");const telemetry = @import("telemetry.zig");const worker_runtime = @import("../worker_runtime.zig"); const Allocator = std.mem.Allocator;const AgentRuntimeDeps = deps_mod.AgentRuntimeDeps;const ChatMessage = types.ChatMessage;const HistoryTurn = types.HistoryTurn;const LifecycleContext = lifecycle_runtime.LifecycleContext;const QueuedPrompt = worker_runtime.QueuedPrompt;const TraceContext = debug_trace.TraceContext;const TurnSummaryAccumulator = telemetry.TurnSummaryAccumulator; pub const PromptFinishTrace = struct { ctx: TraceContext, emitted: bool = false, pub fn finish(self: *PromptFinishTrace, outcome: []const u8) void { if (self.emitted) return; self.emitted = true; debug_trace.eventf("agent", "prompt_finish", self.ctx, "outcome_kind={s}", .{outcome}); }}; pub const TurnFinalizationGuard = struct { pub const State = enum { open, emitted, fatal, }; deps: *const AgentRuntimeDeps, turn_id: u64, lifecycle: LifecycleContext, state: State = .open, pub fn init( deps: *const AgentRuntimeDeps, turn_id: u64, lifecycle_context: LifecycleContext, ) TurnFinalizationGuard { std.debug.assert(turn_id != 0); return .{ .deps = deps, .turn_id = turn_id, .lifecycle = lifecycle_context, }; } pub fn finish( self: *TurnFinalizationGuard, outcome: types.TurnPresentationOutcome, disposition: ?types.ProviderCompletionDisposition, finished_prompt: ?types.FinishedPrompt, ) !void { if (self.state != .open) { if (finished_prompt) |finished| { types.freeFinishedPrompt(std.heap.c_allocator, finished); } debug_trace.logf( "agent", "duplicate turn finalization ignored turn_id={d} state={s}", .{ self.turn_id, @tagName(self.state) }, ); return; } self.deps.finalize_turn(self.deps.ctx, self.turn_id, outcome, disposition) catch |err| { self.state = .fatal; if (finished_prompt) |finished| { types.freeFinishedPrompt(std.heap.c_allocator, finished); } return err; }; self.state = .emitted; defer lifecycle_runtime.dispatchPostTurnEndCheckpoint(self.lifecycle, .{ .turn_id = self.turn_id, .outcome = outcome, .provider_disposition = disposition, }); if (finished_prompt) |finished| { var qualified_finished = finished; qualified_finished.terminal_outcome = outcome; try self.deps.push_event(self.deps.ctx, .{ .finish_prompt = qualified_finished }); } }}; pub fn finishAssistantTerminalWithExecution( deps: *const AgentRuntimeDeps, finalization: *TurnFinalizationGuard, job: QueuedPrompt, execution: types.ExecutionMemory, summary: *TurnSummaryAccumulator, assistant_text: []const u8, outcome: types.TurnPresentationOutcome, disposition: ?types.ProviderCompletionDisposition, finish_trace: *PromptFinishTrace, trace_outcome: []const u8,) !void { const turn: HistoryTurn = .{ .assistant = .{ .user = .{ .text = job.prompt, .images = job.images }, .assistant = @constCast(assistant_text), .execution = execution, } }; const finished = try types.dupeFinishedPrompt( std.heap.c_allocator, .{ .turn = turn, .summary = summary.finish(), }, ); var propagation_error: ?anyerror = null; deps.propagate_history_turn(deps.ctx, turn) catch |err| { propagation_error = err; }; try finalization.finish(outcome, disposition, finished); finish_trace.finish(trace_outcome); if (propagation_error) |err| return err;} pub fn finishExecutionOnlyFailureIfNeeded( deps: *const AgentRuntimeDeps, finalization: *TurnFinalizationGuard, arena: Allocator, job: QueuedPrompt, current_turn_messages: []const ChatMessage, summary: *TurnSummaryAccumulator, finish_trace: *PromptFinishTrace, terminal_materializing: *bool, trace_outcome: []const u8,) !bool { const execution = try execution_memory.buildExecutionMemory( arena, current_turn_messages, ); if (execution.isEmpty()) return false; terminal_materializing.* = true; try finishAssistantTerminalWithExecution( deps, finalization, job, execution, summary, "", .failed, null, finish_trace, trace_outcome, ); return true;} pub fn finalizeRetainedCandidateFailure( deps: *const AgentRuntimeDeps, finalization: *TurnFinalizationGuard, arena: Allocator, job: QueuedPrompt, current_turn_messages: []const ChatMessage, summary: *TurnSummaryAccumulator, finish_trace: *PromptFinishTrace, retained_candidate: ?[]const u8, latest_partial: ?[]const u8, terminal_materializing: *bool,) !void { terminal_materializing.* = true; const assistant_text = try hooks.prompt.joinVisibleSegments( arena, retained_candidate, latest_partial, ); const execution = try execution_memory.buildExecutionMemory( arena, current_turn_messages, ); try finishAssistantTerminalWithExecution( deps, finalization, job, execution, summary, assistant_text, .failed, null, finish_trace, "error", );}
const std = @import("std");
const hooks = @import("../../hooks/hooks.zig");
const types = @import("../../shared/types.zig");
const debug_trace = @import("../../shared/debug_trace.zig");
const deps_mod = @import("deps.zig");
const execution_memory = @import("execution_memory.zig");
const lifecycle_runtime = @import("lifecycle.zig");
const telemetry = @import("telemetry.zig");
const worker_runtime = @import("../worker_runtime.zig");
const Allocator = std.mem.Allocator;
const AgentRuntimeDeps = deps_mod.AgentRuntimeDeps;
const ChatMessage = types.ChatMessage;
const HistoryTurn = types.HistoryTurn;
const LifecycleContext = lifecycle_runtime.LifecycleContext;
const QueuedPrompt = worker_runtime.QueuedPrompt;
const TraceContext = debug_trace.TraceContext;
const TurnSummaryAccumulator = telemetry.TurnSummaryAccumulator;
pub const PromptFinishTrace = struct {
ctx: TraceContext,
emitted: bool = false,
pub fn finish(self: *PromptFinishTrace, outcome: []const u8) void {
if (self.emitted) return;
self.emitted = true;
debug_trace.eventf("agent", "prompt_finish", self.ctx, "outcome_kind={s}", .{outcome});
}
};
pub const TurnFinalizationGuard = struct {
pub const State = enum {
open,
emitted,
fatal,
};
deps: *const AgentRuntimeDeps,
turn_id: u64,
lifecycle: LifecycleContext,
state: State = .open,
pub fn init(
deps: *const AgentRuntimeDeps,
turn_id: u64,
lifecycle_context: LifecycleContext,
) TurnFinalizationGuard {
std.debug.assert(turn_id != 0);
return .{
.deps = deps,
.turn_id = turn_id,
.lifecycle = lifecycle_context,
};
}
pub fn finish(
self: *TurnFinalizationGuard,
outcome: types.TurnPresentationOutcome,
disposition: ?types.ProviderCompletionDisposition,
finished_prompt: ?types.FinishedPrompt,
) !void {
if (self.state != .open) {
if (finished_prompt) |finished| {
types.freeFinishedPrompt(std.heap.c_allocator, finished);
}
debug_trace.logf(
"agent",
"duplicate turn finalization ignored turn_id={d} state={s}",
.{ self.turn_id, @tagName(self.state) },
);
return;
}
self.deps.finalize_turn(self.deps.ctx, self.turn_id, outcome, disposition) catch |err| {
self.state = .fatal;
if (finished_prompt) |finished| {
types.freeFinishedPrompt(std.heap.c_allocator, finished);
}
return err;
};
self.state = .emitted;
defer lifecycle_runtime.dispatchPostTurnEndCheckpoint(self.lifecycle, .{
.turn_id = self.turn_id,
.outcome = outcome,
.provider_disposition = disposition,
});
if (finished_prompt) |finished| {
var qualified_finished = finished;
qualified_finished.terminal_outcome = outcome;
try self.deps.push_event(self.deps.ctx, .{ .finish_prompt = qualified_finished });
}
}
};
pub fn finishAssistantTerminalWithExecution(
deps: *const AgentRuntimeDeps,
finalization: *TurnFinalizationGuard,
job: QueuedPrompt,
execution: types.ExecutionMemory,
summary: *TurnSummaryAccumulator,
assistant_text: []const u8,
outcome: types.TurnPresentationOutcome,
disposition: ?types.ProviderCompletionDisposition,
finish_trace: *PromptFinishTrace,
trace_outcome: []const u8,
) !void {
const turn: HistoryTurn = .{ .assistant = .{
.user = .{ .text = job.prompt, .images = job.images },
.assistant = @constCast(assistant_text),
.execution = execution,
} };
const finished = try types.dupeFinishedPrompt(
std.heap.c_allocator,
.{
.turn = turn,
.summary = summary.finish(),
},
);
var propagation_error: ?anyerror = null;
deps.propagate_history_turn(deps.ctx, turn) catch |err| {
propagation_error = err;
};
try finalization.finish(outcome, disposition, finished);
finish_trace.finish(trace_outcome);
if (propagation_error) |err| return err;
}
pub fn finishExecutionOnlyFailureIfNeeded(
deps: *const AgentRuntimeDeps,
finalization: *TurnFinalizationGuard,
arena: Allocator,
job: QueuedPrompt,
current_turn_messages: []const ChatMessage,
summary: *TurnSummaryAccumulator,
finish_trace: *PromptFinishTrace,
terminal_materializing: *bool,
trace_outcome: []const u8,
) !bool {
const execution = try execution_memory.buildExecutionMemory(
arena,
current_turn_messages,
);
if (execution.isEmpty()) return false;
terminal_materializing.* = true;
try finishAssistantTerminalWithExecution(
deps,
finalization,
job,
execution,
summary,
"",
.failed,
null,
finish_trace,
trace_outcome,
);
return true;
}
pub fn finalizeRetainedCandidateFailure(
deps: *const AgentRuntimeDeps,
finalization: *TurnFinalizationGuard,
arena: Allocator,
job: QueuedPrompt,
current_turn_messages: []const ChatMessage,
summary: *TurnSummaryAccumulator,
finish_trace: *PromptFinishTrace,
retained_candidate: ?[]const u8,
latest_partial: ?[]const u8,
terminal_materializing: *bool,
) !void {
terminal_materializing.* = true;
const assistant_text = try hooks.prompt.joinVisibleSegments(
arena,
retained_candidate,
latest_partial,
);
const execution = try execution_memory.buildExecutionMemory(
arena,
current_turn_messages,
);
try finishAssistantTerminalWithExecution(
deps,
finalization,
job,
execution,
summary,
assistant_text,
.failed,
null,
finish_trace,
"error",
);
}