@sema-agent/core
Version:
Stateless, task-oriented AI agent core
1,619 lines • 68 kB
JavaScript
import { findToolByName, validateToolArguments } from "../llm/index.js";
import { truncateError } from "../../core/tool-errors.js";
import { judgeToolNameMiss, mountedToolNames } from "../../core/tool-name-aliases.js";
import { INTERRUPTED_BY_USER_FOR_TOOL_USE_MARKER, INTERRUPTED_BY_USER_MARKER, } from "../../core/session-reconcile.js";
import { resolveAgentCoreStreamFn } from "./runtime-deps.js";
import { isErrorLike, readMember, thrownMarksOf } from "../../core/thrown-value.js";
import { egressThrownText, egressValue } from "../../core/untrusted-egress.js";
import { admitToolResultMedia } from "../../core/image-admission.js";
function appendTextDeltaToAssistantMessage(message, contentIndex, delta) {
const content = [...message.content];
const currentContent = content[contentIndex];
content[contentIndex] =
currentContent?.type === "text"
? { ...currentContent, text: currentContent.text + delta }
: { type: "text", text: delta };
return { ...message, content };
}
function resolveAssistantMessageUpdate(event, currentMessage) {
if ("partial" in event && event.partial) {
return event.partial;
}
if (event.type === "text_delta") {
return appendTextDeltaToAssistantMessage(currentMessage, event.contentIndex, event.delta);
}
return currentMessage;
}
export function findUnansweredToolBatch(messages) {
for (let i = messages.length - 1; i >= 0; i--) {
const m = messages[i];
if (m.role !== "assistant")
continue;
return unansweredToolCallsOf(m, messages.slice(i + 1)).length > 0 ? m : undefined;
}
return undefined;
}
function unansweredToolCallsOf(assistantMessage, after) {
const answered = new Set(after.filter((m) => m.role === "toolResult").map((m) => m.toolCallId));
const seen = new Set();
const unanswered = [];
for (const c of assistantMessage.content) {
if (c.type !== "toolCall" || answered.has(c.id) || seen.has(c.id))
continue;
seen.add(c.id);
unanswered.push(c);
}
return unanswered;
}
export async function settleUnansweredToolCalls(context, assistantMessage, config, signal, emit, halt) {
const at = context.messages.lastIndexOf(assistantMessage);
const toolCalls = unansweredToolCallsOf(assistantMessage, at >= 0 ? context.messages.slice(at + 1) : context.messages);
if (toolCalls.length === 0)
return { messages: [], terminate: false };
if (halt !== undefined) {
const messages = [];
for (const toolCall of toolCalls) {
if (signal?.aborted)
break;
messages.push((await settleHaltedToolCall(toolCall, halt, emit)).message);
}
return { messages, terminate: true };
}
return config.toolExecution === "sequential"
? executeToolCallsSequential(context, assistantMessage, toolCalls, config, signal, emit)
: executeToolCallsPartitioned(context, assistantMessage, toolCalls, config, signal, emit);
}
export async function runAgentLoop(prompts, context, config, emit, signal, streamFn, runtime, trace, seams) {
const state = await createLoopState({ ...context, messages: [...context.messages] }, config, []);
await emit({ type: "agent_start" });
await emit({ type: "turn_start" });
if (seams?.settle !== undefined && (await settleTrailingBatchTurn(state, signal, emit, seams.settle)) === "ended") {
return state.newMessages;
}
const delivered = typeof prompts === "function" ? await prompts() : prompts;
if (delivered.length > 0)
state.hasMoreToolCalls = true;
state.context.messages.push(...delivered);
state.newMessages.push(...delivered);
seams?.onPromptsDelivered?.();
for (const prompt of delivered) {
await emit({ type: "message_start", message: prompt });
await emit({ type: "message_end", message: prompt });
}
await runLoop(state, signal, emit, streamFn, runtime, trace);
return state.newMessages;
}
async function settleTrailingBatchTurn(state, signal, emit, settle) {
const tail = findUnansweredToolBatch(state.context.messages);
if (tail === undefined)
return "continue";
const turnController = new AbortController();
const onRunAbort = () => turnController.abort();
if (signal !== undefined) {
if (signal.aborted)
turnController.abort();
else
signal.addEventListener("abort", onRunAbort, { once: true });
}
state.config.publishTurnInterruptSeat?.(turnController);
let batch;
try {
batch = await settleUnansweredToolCalls(state.context, tail, state.config, turnController.signal, emit, settle.halt);
}
finally {
state.config.publishTurnInterruptSeat?.(undefined);
signal?.removeEventListener("abort", onRunAbort);
}
state.context.messages.push(...batch.messages);
state.newMessages.push(...batch.messages);
let ended = signal?.aborted === true;
let terminalEmitted = false;
if (!ended && turnController.signal.aborted) {
state.config.noteTurnCutConsumed?.(turnController);
const pendingBeforeSettlement = state.pendingMessages;
const outcome = await settleInterruptedTurn(state, tail, new StreamToolExecutor(state.context, state.config, turnController.signal, emit), batch.messages, signal, emit);
ended = outcome.kind === "terminal";
if (!ended)
state.pendingMessages.unshift(...pendingBeforeSettlement);
terminalEmitted = ended;
}
else if (!ended) {
const boundary = { message: tail, toolResults: batch.messages, context: state.context, newMessages: state.newMessages };
adoptNextTurnSnapshot(state, await state.config.prepareNextTurn?.(boundary));
ended = (await state.config.shouldStopAfterTurn?.(boundary)) === true || signal?.aborted === true;
if (!ended)
state.pendingMessages.push(...((await state.config.getSteeringMessages?.()) || []));
}
settle.report({ assistantMessage: tail, settled: batch.messages.length, aborted: ended });
if (ended && !terminalEmitted)
await emit({ type: "agent_end", messages: state.newMessages });
return ended ? "ended" : "continue";
}
const PROMPT_TOO_LONG_RE = /context.{0,3}length|context_length|context window (?:limit|exceeded)|reached its context window|prompt is too long|too many tokens|maximum context|exceeds? .{0,30}(?:context|token)|input is too long|\b413\b/i;
function isBlankFailureContent(message) {
return message.content.every((c) => c.type === "text" ? c.text.trim() === "" : c.type === "thinking" ? c.thinking.trim() === "" : false);
}
function defaultDetectPromptTooLong(message) {
if (message.stopReason !== "error")
return false;
if (message.errorKind === "input_too_long")
return true;
if (message.inputTooLongRuledOut === true)
return false;
if (message.errorBodyIncomplete === true)
return false;
return PROMPT_TOO_LONG_RE.test(message.errorMessage ?? "");
}
function createPtlWithholdBuffer(emit, detect) {
const held = [];
let liveStartEmitted = false;
const isCandidate = (message) => message.role === "assistant" &&
message.stopReason === "error" &&
detect(message) &&
isBlankFailureContent(message);
return {
sink: async (event) => {
if (event.type === "message_start" && isCandidate(event.message)) {
held.push(event);
return;
}
if (event.type === "message_end" && !liveStartEmitted && isCandidate(event.message)) {
held.push(event);
return;
}
if (event.type === "message_start")
liveStartEmitted = true;
await emit(event);
},
async flush() {
for (const event of held.splice(0))
await emit(event);
},
discard() {
held.length = 0;
},
};
}
function createMalformedToolUseNudge() {
return {
role: "custom",
customType: "loop:malformed-tool-use-retry",
provenance: "engine-note",
content: "Your previous response indicated tool use but contained no valid tool call. Re-issue the tool call(s) now, with complete and valid JSON arguments — provide exactly one JSON value per parameter, and do not truncate.",
display: false,
timestamp: Date.now(),
};
}
function createThinkingOnlyNudge() {
return {
role: "custom",
customType: "loop:thinking-only-retry",
provenance: "engine-note",
content: "Your previous response had no visible output — it contained only internal reasoning. Respond now with your answer as visible text (or make the required tool calls); do not end the turn with reasoning alone.",
display: false,
timestamp: Date.now(),
};
}
function createMidstreamPartialContinueNudge() {
return {
role: "custom",
customType: "loop:midstream-partial-continue",
provenance: "engine-note",
content: "Your previous response was cut off mid-stream by a connection failure — the content above may be incomplete. Continue EXACTLY where you left off; do not repeat any content you already produced, do not re-introduce or summarize.",
display: false,
timestamp: Date.now(),
};
}
function createDegenerateRecoveryNudge() {
return {
role: "custom",
customType: "loop:degenerate-output-recovery",
provenance: "engine-note",
content: "Your previous reply degenerated into repetition and was cut off. Do NOT repeat or continue that text. " +
"Take a DIFFERENT approach: state your next concrete step or conclusion in one short sentence, then proceed normally.",
display: false,
timestamp: Date.now(),
};
}
function createReasoningCutContinueNudge() {
return {
role: "custom",
customType: "loop:truncated-output-continue",
provenance: "engine-note",
content: "Your previous response hit the output-token limit before producing any visible text (the budget went to internal reasoning). " +
"Do NOT restart the long reasoning: state your current best answer or next concrete action directly as visible text (or make the required tool call) now.",
display: false,
timestamp: Date.now(),
};
}
function createContinueNudge() {
return {
role: "custom",
customType: "loop:truncated-output-continue",
provenance: "engine-note",
content: "Your previous response was cut off because it hit the output-token limit. Continue EXACTLY where you left off — do not repeat any content you already produced, do not re-introduce or summarize.",
display: false,
timestamp: Date.now(),
};
}
async function createLoopState(context, config, newMessages) {
return {
context,
config,
newMessages,
firstTurn: true,
pendingMessages: (await config.getSteeringMessages?.()) || [],
hasMoreToolCalls: true,
truncatedOutputContinues: 0,
staticReasoningCutRecoveries: context.messages.filter((m) => m.role === "assistant" && m.staticReasoningCut === true).length,
degenerateContinues: 0,
malformedToolUseRetries: 0,
thinkingOnlyRetries: 0,
midstreamPartialContinues: 0,
};
}
async function runLoop(state, signal, emit, streamFn, runtime, trace) {
while (true) {
if (!state.hasMoreToolCalls && state.pendingMessages.length === 0) {
const followUpMessages = (await state.config.getFollowUpMessages?.()) || [];
if (followUpMessages.length === 0) {
if ((state.config.pendingInjectionCount?.() ?? 0) > 0) {
state.pendingMessages = (await state.config.getSteeringMessages?.()) || [];
if (state.pendingMessages.length > 0) {
trace?.({ kind: "continue", reason: "steer_injected" });
}
continue;
}
trace?.({ kind: "terminal", reason: "completed" });
break;
}
state.pendingMessages = followUpMessages;
trace?.({ kind: "continue", reason: "follow_up_injected" });
}
const outcome = await runSingleTurn(state, signal, emit, streamFn, runtime, trace);
if (outcome.kind === "terminal") {
trace?.({ kind: "terminal", reason: outcome.reason });
return;
}
if (outcome.recovered) {
trace?.({ kind: "continue", reason: outcome.recovered });
}
else if (state.pendingMessages.length > 0) {
trace?.({ kind: "continue", reason: "steer_injected" });
}
else if (state.hasMoreToolCalls) {
trace?.({ kind: "continue", reason: "next_turn" });
}
}
await emit({ type: "agent_end", messages: state.newMessages });
}
const MAX_STATIC_REASONING_CUT_RECOVERIES = 2;
async function runSingleTurn(state, signal, emit, streamFn, runtime, trace) {
if (signal?.aborted) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "aborted_before_stream" };
}
if (state.config.shouldHaltBeforeTurn?.() === true) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "stop_requested" };
}
if (!state.firstTurn) {
await emit({ type: "turn_start" });
if (state.config.shouldHaltBeforeTurn?.() === true) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "stop_requested" };
}
}
else {
state.firstTurn = false;
}
const turnController = new AbortController();
const onRunAbort = () => turnController.abort();
if (signal !== undefined) {
if (signal.aborted)
turnController.abort();
else
signal.addEventListener("abort", onRunAbort, { once: true });
}
let seatLive = false;
const publishSeat = () => {
seatLive = true;
state.config.publishTurnInterruptSeat?.(turnController);
};
const retractSeat = () => {
if (!seatLive)
return;
seatLive = false;
state.config.publishTurnInterruptSeat?.(undefined);
};
try {
return await runTurnPhases(state, signal, turnController, publishSeat, retractSeat, emit, streamFn, runtime, trace);
}
finally {
retractSeat();
signal?.removeEventListener("abort", onRunAbort);
}
}
async function runTurnPhases(state, signal, turnController, publishSeat, retractSeat, emit, streamFn, runtime, trace) {
publishSeat();
if (state.config.recheckImmediateInjections) {
state.pendingMessages = await state.config.recheckImmediateInjections(state.pendingMessages);
}
if (state.pendingMessages.length > 0) {
for (const message of state.pendingMessages) {
await emit({ type: "message_start", message });
await emit({ type: "message_end", message });
state.context.messages.push(message);
state.newMessages.push(message);
}
}
let executor = new StreamToolExecutor(state.context, state.config, turnController.signal, emit);
const staticReasoningCutDowngrade = state.config.recovery?.truncatedOutput !== undefined &&
state.staticReasoningCutRecoveries < MAX_STATIC_REASONING_CUT_RECOVERIES;
const ptl = state.config.recovery?.promptTooLong;
const ptlDetect = ptl?.detect ?? defaultDetectPromptTooLong;
const withholdEnabled = ptl !== undefined && (ptl.withholdErrorEvents ?? true);
let withhold = withholdEnabled ? createPtlWithholdBuffer(emit, ptlDetect) : undefined;
let message;
let cutAtFinalSettle = false;
try {
({ message, cutAtSettle: cutAtFinalSettle } = await streamAssistantResponse(state.context, state.config, turnController.signal, withhold?.sink ?? emit, streamFn, runtime, executor, staticReasoningCutDowngrade));
}
finally {
await executor.settle();
}
raiseFatalCancellation(executor);
let interruptedInRecovery = false;
{
if (ptl) {
const detect = ptlDetect;
const maxRetries = ptl.maxRetries ?? 2;
let attempt = 0;
while (message.stopReason === "error" &&
!signal?.aborted &&
detect(message) &&
isBlankFailureContent(message) &&
executor.admittedCount === 0 &&
attempt < maxRetries) {
attempt++;
if (state.context.messages[state.context.messages.length - 1] === message) {
state.context.messages.pop();
}
let replaced;
try {
replaced = await ptl.recover(state.context.messages, attempt, turnController.signal);
}
catch {
replaced = undefined;
}
if (!replaced) {
state.context.messages.push(message);
interruptedInRecovery = turnController.signal.aborted && !signal?.aborted;
break;
}
withhold?.discard();
withhold = withholdEnabled ? createPtlWithholdBuffer(emit, ptlDetect) : undefined;
if (Array.isArray(replaced)) {
state.context.messages = replaced;
}
else {
adoptRebuiltContext(state.context, replaced);
}
if (turnController.signal.aborted && !signal?.aborted) {
state.context.messages.push(message);
interruptedInRecovery = true;
break;
}
trace?.({ kind: "continue", reason: "reactive_compact_retry" });
executor = new StreamToolExecutor(state.context, state.config, turnController.signal, emit);
try {
({ message, cutAtSettle: cutAtFinalSettle } = await streamAssistantResponse(state.context, state.config, turnController.signal, withhold?.sink ?? emit, streamFn, runtime, executor, staticReasoningCutDowngrade));
}
finally {
await executor.settle();
}
raiseFatalCancellation(executor);
}
}
}
const unrecoveredPtlFailureUnderInterrupt = ptl !== undefined &&
message.stopReason === "error" &&
ptlDetect(message) &&
isBlankFailureContent(message) &&
executor.admittedCount === 0;
const cutAtPtlLaneExit = turnController.signal.aborted;
const interruptOwnsTurn = ((message.stopReason === "aborted" && cutAtFinalSettle) || (unrecoveredPtlFailureUnderInterrupt && cutAtPtlLaneExit) || interruptedInRecovery) &&
!signal?.aborted;
if (interruptOwnsTurn)
state.config.noteTurnCutConsumed?.(turnController);
if (interruptOwnsTurn && message.stopReason === "error")
withhold?.discard();
await withhold?.flush();
state.newMessages.push(message);
if (message.staticReasoningCut === true)
state.staticReasoningCutRecoveries++;
if (interruptOwnsTurn) {
retractSeat();
return await settleInterruptedTurn(state, message, executor, [], signal, emit);
}
{
const degen = state.config.recovery?.degenerateOutput;
if (degen &&
message.stopReason === "error" &&
!signal?.aborted &&
degen.detect(message) &&
executor.admittedCount === 0 &&
message.content.every((b) => b.type !== "toolCall") &&
state.degenerateContinues < (degen.maxContinues ?? 2)) {
retractSeat();
state.degenerateContinues++;
await emit({ type: "turn_end", message, toolResults: [] });
const drained = (await state.config.getSteeringMessages?.()) || [];
state.pendingMessages = drained.length > 0 ? drained : [createDegenerateRecoveryNudge()];
return { kind: "ran", recovered: "degenerate_output_recovery" };
}
if (message.stopReason !== "error")
state.degenerateContinues = 0;
}
if (message.stopReason === "error" || message.stopReason === "aborted") {
retractSeat();
const errorFinalResults = await harvestExecutorOnErrorFinal(executor, state, message, turnController.signal, emit);
await emit({ type: "turn_end", message, toolResults: errorFinalResults });
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "assistant_error" };
}
const toolCalls = message.content.filter((c) => c.type === "toolCall");
const toolResults = [];
state.hasMoreToolCalls = false;
if (toolCalls.length > 0) {
const executedToolBatch = await executeToolCalls(state.context, message, state.config, turnController.signal, emit, executor);
toolResults.push(...executedToolBatch.messages);
state.hasMoreToolCalls = !executedToolBatch.terminate;
for (const result of toolResults) {
state.context.messages.push(result);
state.newMessages.push(result);
}
}
else {
for (const orphan of executor.remaining()) {
executor.take(orphan.toolCall.id);
await closeOrphanedStreamEntry(orphan, emit);
}
}
const cutAtBatchSettle = turnController.signal.aborted;
retractSeat();
if (toolCalls.length > 0 && cutAtBatchSettle && !signal?.aborted) {
state.config.noteTurnCutConsumed?.(turnController);
return await settleInterruptedTurn(state, message, executor, toolResults, signal, emit);
}
await emit({ type: "turn_end", message, toolResults });
const nextTurnContext = {
message,
toolResults,
context: state.context,
newMessages: state.newMessages,
};
const nextTurnSnapshot = await state.config.prepareNextTurn?.(nextTurnContext);
adoptNextTurnSnapshot(state, nextTurnSnapshot);
if (await state.config.shouldStopAfterTurn?.({
message,
toolResults,
context: state.context,
newMessages: state.newMessages,
})) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "stop_requested" };
}
state.pendingMessages = (await state.config.getSteeringMessages?.()) || [];
const truncated = state.config.recovery?.truncatedOutput;
if (message.stopReason === "length") {
if (truncated && toolCalls.length === 0 && state.pendingMessages.length === 0) {
if (state.truncatedOutputContinues < (truncated.maxContinues ?? 3)) {
state.truncatedOutputContinues++;
state.midstreamPartialContinues = 0;
const noVisibleText = !message.content.some((c) => c.type === "text" && c.text.trim() !== "");
state.pendingMessages = [...state.pendingMessages, noVisibleText ? createReasoningCutContinueNudge() : createContinueNudge()];
return { kind: "ran", recovered: "max_output_tokens_recovery" };
}
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "truncated_output_exhausted" };
}
}
else {
state.truncatedOutputContinues = 0;
}
if (message.stopReason === "stop" && message.partialFinalized === true) {
if (toolCalls.length === 0 &&
state.pendingMessages.length === 0 &&
state.midstreamPartialContinues < 3) {
state.midstreamPartialContinues++;
state.pendingMessages = [createMidstreamPartialContinueNudge()];
return { kind: "ran", recovered: "midstream_partial_recovery" };
}
}
else {
state.midstreamPartialContinues = 0;
}
const malformed = state.config.recovery?.malformedToolUse;
if (malformed && message.stopReason === "toolUse" && toolCalls.length === 0 && state.pendingMessages.length === 0) {
if (state.malformedToolUseRetries < (malformed.maxRetries ?? 1)) {
state.malformedToolUseRetries++;
state.pendingMessages = [createMalformedToolUseNudge()];
return { kind: "ran", recovered: "malformed_tool_use_retry" };
}
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "malformed_tool_use_exhausted" };
}
const thinkingOnly = state.config.recovery?.thinkingOnly;
if (thinkingOnly &&
message.stopReason === "stop" &&
toolCalls.length === 0 &&
state.pendingMessages.length === 0 &&
state.thinkingOnlyRetries < (thinkingOnly.maxRetries ?? 1) &&
message.content.some((c) => c.type === "thinking") &&
!message.content.some((c) => c.type === "text" && c.text.trim() !== "")) {
state.thinkingOnlyRetries++;
state.pendingMessages = [createThinkingOnlyNudge()];
return { kind: "ran", recovered: "thinking_only_retry" };
}
return { kind: "ran" };
}
function adoptRebuiltContext(context, adopted) {
context.messages = adopted.messages;
if (adopted.systemPrompt === undefined)
return;
context.systemPrompt = adopted.systemPrompt;
if (adopted.systemBlocks !== undefined) {
context.systemBlocks = adopted.systemBlocks;
}
else {
delete context.systemBlocks;
}
}
function adoptNextTurnSnapshot(state, nextTurnSnapshot) {
if (!nextTurnSnapshot)
return;
state.context = nextTurnSnapshot.context ?? state.context;
state.config = Object.assign({}, state.config, {
model: nextTurnSnapshot.model ?? state.config.model,
reasoning: nextTurnSnapshot.thinkingLevel === undefined
? state.config.reasoning
: nextTurnSnapshot.thinkingLevel,
});
}
const TURN_INTERRUPTED_TOOL_TEXT = "[INTERRUPTED] The user interrupted this turn before this tool call started. It was never executed and " +
"had no side effects. Address the user's interjection first; re-issue the call afterwards if it is still needed.";
async function settleInterruptedTurn(state, message, executor, committedResults, signal, emit) {
const toolResults = [...committedResults];
toolResults.push(...(await harvestExecutorOnErrorFinal(executor, state, message, signal, emit)));
const turnCalls = message.content.filter((c) => c.type === "toolCall");
const priorResults = state.context.messages.slice(state.context.messages.lastIndexOf(message) + 1).filter((m) => m.role === "toolResult");
const answered = new Set([...priorResults, ...toolResults].map((r) => r.toolCallId));
for (const call of turnCalls) {
if (answered.has(call.id))
continue;
await emit({
type: "tool_execution_start",
toolCallId: call.id,
toolName: call.name,
args: call.arguments,
});
const finalized = {
toolCall: call,
result: {
content: [{ type: "text", text: TURN_INTERRUPTED_TOOL_TEXT }],
details: { errorKind: "interrupted_never_started" },
},
isError: true,
};
await emit({
type: "tool_execution_end",
toolCallId: call.id,
toolName: call.name,
result: finalized.result,
isError: true,
notExecuted: true,
});
const toolResultMessage = createToolResultMessage(finalized);
await emitToolResultMessage(toolResultMessage, emit);
state.context.messages.push(toolResultMessage);
state.newMessages.push(toolResultMessage);
toolResults.push(toolResultMessage);
}
if (turnCalls.length === 0 &&
isBlankFailureContent(message) &&
state.context.messages[state.context.messages.length - 1] === message) {
state.context.messages.pop();
if (state.newMessages[state.newMessages.length - 1] === message)
state.newMessages.pop();
}
const marker = {
role: "user",
content: [
{
type: "text",
text: turnCalls.length > 0 ? INTERRUPTED_BY_USER_FOR_TOOL_USE_MARKER : INTERRUPTED_BY_USER_MARKER,
},
],
provenance: "engine-note",
timestamp: Date.now(),
};
await emit({ type: "message_start", message: marker });
await emit({ type: "message_end", message: marker });
state.context.messages.push(marker);
state.newMessages.push(marker);
state.hasMoreToolCalls = false;
await emit({ type: "turn_end", message, toolResults });
if (signal?.aborted) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "aborted_before_stream" };
}
const nextTurnSnapshot = await state.config.prepareNextTurn?.({
message,
toolResults,
context: state.context,
newMessages: state.newMessages,
});
adoptNextTurnSnapshot(state, nextTurnSnapshot);
if (await state.config.shouldStopAfterTurn?.({
message,
toolResults,
context: state.context,
newMessages: state.newMessages,
})) {
await emit({ type: "agent_end", messages: state.newMessages });
return { kind: "terminal", reason: "stop_requested" };
}
state.pendingMessages = (await state.config.getSteeringMessages?.()) || [];
return { kind: "ran" };
}
async function streamAssistantResponse(context, config, signal, emit, streamFn, runtime, executor, staticReasoningCutDowngrade) {
let messages = context.messages;
if (config.transformContext) {
const transformed = await config.transformContext(messages, signal);
if (Array.isArray(transformed)) {
messages = transformed;
}
else {
if (transformed.adoptedContext !== undefined) {
adoptRebuiltContext(context, transformed.adoptedContext);
}
messages = transformed.messages;
}
}
const llmMessages = await config.convertToLlm(messages);
const llmContext = {
systemPrompt: context.systemPrompt,
...(context.systemBlocks ? { systemBlocks: context.systemBlocks } : {}),
messages: llmMessages,
tools: context.tools,
};
const streamFunction = resolveAgentCoreStreamFn(runtime, streamFn);
const resolvedApiKey = (config.getApiKey ? await config.getApiKey(config.model.provider) : undefined) || config.apiKey;
const perCallMaxTokens = config.maxTokensPerCall?.();
const perCallStallTimeouts = config.stallTimeoutsPerCall?.();
if (signal?.aborted) {
const abortedBeforeDispatch = {
role: "assistant",
content: [{ type: "text", text: "" }],
api: config.model.api,
provider: config.model.provider,
model: config.model.id,
stopReason: "aborted",
timestamp: Date.now(),
usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } },
usageMissing: true,
};
context.messages.push(abortedBeforeDispatch);
await emit({ type: "message_start", message: { ...abortedBeforeDispatch } });
await emit({ type: "message_end", message: abortedBeforeDispatch });
return { message: abortedBeforeDispatch, cutAtSettle: true };
}
const response = await streamFunction(config.model, llmContext, {
...config,
...(perCallMaxTokens !== undefined ? { maxTokens: perCallMaxTokens, maxTokensDynamic: true } : {}),
...(perCallStallTimeouts !== undefined ? { stallTimeouts: perCallStallTimeouts } : {}),
...(staticReasoningCutDowngrade === true ? { staticReasoningCutDowngrade: true } : {}),
apiKey: resolvedApiKey,
signal,
});
let partialMessage = null;
let addedPartial = false;
for await (const event of response) {
switch (event.type) {
case "start": {
const message = event.partial;
partialMessage = message;
context.messages.push(message);
addedPartial = true;
await emit({ type: "message_start", message: { ...message } });
break;
}
case "text_start":
case "text_delta":
case "text_end":
case "thinking_start":
case "thinking_delta":
case "thinking_end":
case "toolcall_start":
case "toolcall_delta":
case "toolcall_end":
if (partialMessage) {
const message = resolveAssistantMessageUpdate(event, partialMessage);
partialMessage = message;
context.messages[context.messages.length - 1] = message;
await emit({
type: "message_update",
assistantMessageEvent: event,
message: { ...message },
});
}
if (event.type === "toolcall_end" && executor) {
executor.maybeAdmit(event.toolCall, partialMessage);
}
break;
case "done":
case "error": {
const finalMessage = await response.result();
const cutAtSettle = signal?.aborted === true;
if (executor && (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
mergeBackAdmittedCalls(finalMessage, executor);
}
if (addedPartial) {
context.messages[context.messages.length - 1] = finalMessage;
}
else {
context.messages.push(finalMessage);
}
if (!addedPartial) {
await emit({ type: "message_start", message: { ...finalMessage } });
}
await emit({ type: "message_end", message: finalMessage });
return { message: finalMessage, cutAtSettle };
}
}
}
const finalMessage = await response.result();
const cutAtSettle = signal?.aborted === true;
if (executor && (finalMessage.stopReason === "error" || finalMessage.stopReason === "aborted")) {
mergeBackAdmittedCalls(finalMessage, executor);
}
if (addedPartial) {
context.messages[context.messages.length - 1] = finalMessage;
}
else {
context.messages.push(finalMessage);
await emit({ type: "message_start", message: { ...finalMessage } });
}
await emit({ type: "message_end", message: finalMessage });
return { message: finalMessage, cutAtSettle };
}
async function executeToolCalls(currentContext, assistantMessage, config, signal, emit, executor) {
const toolCalls = assistantMessage.content.filter((c) => c.type === "toolCall");
if (config.toolExecution === "sequential") {
return executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit);
}
return executeToolCallsPartitioned(currentContext, assistantMessage, toolCalls, config, signal, emit, executor);
}
async function executeToolCallsSequential(currentContext, assistantMessage, toolCalls, config, signal, emit) {
const finalizedCalls = [];
const messages = [];
for (let i = 0; i < toolCalls.length; i++) {
const toolCall = toolCalls[i];
await emit({
type: "tool_execution_start",
toolCallId: toolCall.id,
toolName: toolCall.name,
args: toolCall.arguments,
});
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
let finalized;
let halt;
if (preparation.kind === "immediate") {
finalized = {
toolCall,
result: preparation.result,
isError: preparation.isError,
};
halt = preparation.halt;
}
else {
finalized = await executePreparedWithDisclosure(currentContext, assistantMessage, preparation, config, signal, emit);
}
await emitToolExecutionEnd(finalized, emit);
const toolResultMessage = createToolResultMessage(finalized);
await emitToolResultMessage(toolResultMessage, emit);
finalizedCalls.push(finalized);
messages.push(toolResultMessage);
if (halt !== undefined) {
for (const remaining of toolCalls.slice(i + 1)) {
if (signal?.aborted)
break;
const settled = await settleHaltedToolCall(remaining, halt, emit);
finalizedCalls.push(settled.finalized);
messages.push(settled.message);
}
return { messages, terminate: true };
}
if (signal?.aborted) {
break;
}
}
return {
messages,
terminate: shouldTerminateToolBatch(finalizedCalls),
};
}
async function settleHaltedToolCall(toolCall, halt, emit) {
await emit({
type: "tool_execution_start",
toolCallId: toolCall.id,
toolName: toolCall.name,
args: toolCall.arguments,
});
const finalized = {
toolCall,
result: {
content: [{ type: "text", text: truncateError(halt.reason) }],
details: halt.details !== undefined ? { ...halt.details } : {},
},
isError: true,
};
await emit({
type: "tool_execution_end",
toolCallId: finalized.toolCall.id,
toolName: finalized.toolCall.name,
result: finalized.result,
isError: true,
notExecuted: true,
});
const message = createToolResultMessage(finalized);
await emitToolResultMessage(message, emit);
return { finalized, message };
}
function isCallConcurrencySafe(tool, args) {
if (!tool)
return false;
if (tool.executionMode === "parallel")
return true;
if (!tool.isConcurrencySafe)
return false;
try {
return tool.isConcurrencySafe(args) === true;
}
catch {
return false;
}
}
function isBatchSafeToolCall(tool, toolCall) {
if (!tool)
return false;
let args;
try {
args = validateToolArguments(tool, prepareToolCallArguments(tool, toolCall));
}
catch {
return false;
}
return isCallConcurrencySafe(tool, args);
}
function partitionToolCalls(toolCalls, tools) {
const batches = [];
for (const tc of toolCalls) {
const safe = isBatchSafeToolCall(findToolByName(tools, tc.name), tc);
const last = batches[batches.length - 1];
if (safe && last?.safe) {
last.calls.push(tc);
}
else {
batches.push({ safe, calls: [tc] });
}
}
return batches;
}
function resolveToolConcurrency(configured) {
if (configured === undefined)
return 10;
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 1;
}
async function runCapped(thunks, limit) {
const results = new Array(thunks.length);
const failures = new Array(thunks.length);
let next = 0;
const width = Number.isFinite(limit) ? Math.max(1, Math.min(limit, thunks.length)) : 1;
const workers = Array.from({ length: width }, async () => {
while (next < thunks.length) {
const i = next++;
try {
results[i] = await thunks[i]();
}
catch (error) {
failures[i] = { error };
}
}
});
await Promise.all(workers);
const firstFailure = failures.find((failure) => failure !== undefined);
if (firstFailure !== undefined) {
throw firstFailure.error;
}
return results;
}
async function executeToolCallsPartitioned(currentContext, assistantMessage, toolCalls, config, signal, emit, executor) {
const cap = resolveToolConcurrency(config.maxToolConcurrency);
const allFinalized = [];
const messages = [];
const remainingCalls = [];
let halt;
for (const toolCall of toolCalls) {
const held = executor?.take(toolCall.id);
if (!held) {
remainingCalls.push(toolCall);
continue;
}
const finalized = await finalizeStreamEntry(currentContext, assistantMessage, held, config, signal);
await emitToolExecutionEnd(finalized, emit);
const toolResultMessage = createToolResultMessage(finalized);
await emitToolResultMessage(toolResultMessage, emit);
allFinalized.push(finalized);
messages.push(toolResultMessage);
if (held.immediate?.halt !== undefined)
halt ??= held.immediate.halt;
}
if (executor) {
for (const orphan of executor.remaining()) {
executor.take(orphan.toolCall.id);
await closeOrphanedStreamEntry(orphan, emit);
}
}
if (halt === undefined) {
const batches = partitionToolCalls(remainingCalls, currentContext.tools);
outer: for (const batch of batches) {
if (signal?.aborted)
break;
const entries = [];
for (const toolCall of batch.calls) {
await emit({
type: "tool_execution_start",
toolCallId: toolCall.id,
toolName: toolCall.name,
args: toolCall.arguments,
});
const preparation = await prepareToolCall(currentContext, assistantMessage, toolCall, config, signal);
if (preparation.kind === "immediate") {
const finalized = { toolCall, result: preparation.result, isError: preparation.isError };
await emitToolExecutionEnd(finalized, emit);
entries.push({ finalized, toolCall });
if (preparation.halt !== undefined) {
halt = preparation.halt;
break;
}
}
else {
entries.push({ prepared: preparation, toolCall });
}
if (signal?.aborted) {
await settleBatch(entries, true);
break outer;
}
}
await settleBatch(entries, batch.safe);
if (halt !== undefined)
break;
}
}
if (halt !== undefined && !signal?.aborted) {
const settledIds = new Set(messages.map((m) => m.toolCallId));
for (const toolCall of toolCalls) {
if (settledIds.has(toolCall.id))
continue;
if (signal?.aborted)
break;
const settled = await settleHaltedToolCall(toolCall, halt, emit);
allFinalized.push(settled.finalized);
messages.push(settled.message);
}
return { messages, terminate: true };
}
return { messages, terminate: shouldTerminateToolBatch(allFinalized) };
async function settleBatch(entries, concurrent) {
const runOne = (entry) => async () => {
const prepared = entry.prepared;
if (!prepared) {
throw new Error(`tool call ${entry.toolCall.id} reached execution without a preparation`);
}
const finalized = await executePreparedWithDisclosure(currentContext, assistantMessage, prepared, config, signal, emit);
await emitToolExecutionEnd(finalized, emit);
return finalized;
};
const preparedEntries = entries.filter((e) => e.prepared);
let executedResults;
if (concurrent && preparedEntries.length > 1) {
executedResults = await runCapped(preparedEntries.map((e) => runOne(e)), cap);
}
else {
executedResults = [];
for (const e of preparedEntries) {
executedResults.push(await runOne(e)());
}
}
let idx = 0;
for (const e of entries) {
const finalized = e.finalized ?? executedResults[idx++];
if (!finalized)
continue;
allFinalized.push(finalized);
const toolResultMessage = createToolResultMessage(finalized);
await emitToolResultMessage(toolResultMessage, emit);
messages.push(toolResultMessage);
}
}
}
class StreamToolExecutor {
context;
config;
signal;
emit;
entries = new Map();
admittedOrder = [];
barrier = false;
inFlight = 0;
fatalCancellation;
constructor(context, config, signal, emit) {
this.context = context;
this.config = config;
this.signal = signal;
this.emit = emit;
}
get admittedCount() {
return this.admittedOrder.length;
}
admittedToolCalls() {
return this.admittedOrder.map((id) => this.entries.get(id)?.toolCall).filter((tc) => tc !== undefined);
}
maybeAdmit(toolCall, partialAssistant) {
if (this.barrier)
return;
if (this.config.streamingToolExecution !== true ||
this.config.toolExecution === "sequential" ||
this.signal?.aborted ||
partialAssistant === null ||
!isBatchSafeToolCall(findToolByName(this.context.tools, toolCall.name), toolCall) ||
this.inFlight >= resolveToolConcurrency(this.config.maxToolConcurrency) ||
this.entries.has(toolCall.id)) {
this.barrier = true;
return;
}
const entry = { toolCall, promise: Promise.resolve() };
this.entries.set(toolCall.id, entry);
this.admittedOrder.push(toolCall.id);
entry.promise = this.runOne(entry, partialAssistant);
}
async runOne(entry, partialAssistant) {
this.inFlight++;
try {
await this.emit({
type: "tool_execution_start",
toolCallId: entry.toolCall.id,
toolName: entry.toolCall.name,
args: entry.toolCall.arguments,
});
const preparation = await prepareToolCall(this.context, partialAssistant, entry.toolCall, this.config, this.signal);
if (preparation.kind === "immediate") {
entry.immediate = preparation;
if (preparation.halt !== undefined)
this.barrier = true;
return;
}
entry.prepared = preparation;
entry.executed = await executePreparedToolCall(preparation, this.signal, this.emit, this.config.abortResultDetails, (error) => this.retainCancellation(error));
}
catch (error) {
if (isAbortSemanticsError(error))
this.retainCancellation(error);
entry.immediate = {
kind: "immediate",
result: thrownErrorToolResult(error),
isError: true,
};
}
finally {
this.inFlight--;
}
}
async settle() {
this.barrier = true;
await Promise.all([...this.entries.values()].map((e) => e.promise));
}
retainCancellation(error) {
this.fatalCancellation ??= error;
this.barrier = true;
}
takeFatalCancellation() {
const cancellation = this.fatalCancellation;
this.fatalCancellation = undefined;
return cancellation;
}
take(id) {
const entry = this.entries.get(id);
if (entry)
this.entries.delete(id);
return entry;
}
remaining() {
return this.admittedOrder
.map((id) => this.entries.get(id))
.filter((e) => e !== undefined);
}
}
async function finalizeStreamEntry(currentContext, assistantMessage, entry, config, signal) {
if (entry.prepared && entry.executed) {
return finalizeExecutedToolCall(currentContext, assistantMessage, entry.prepared, entry.executed, config, signal);
}
const immediate = entry.immediate ?? {
kind: "immediate",
result: createErrorToolResult("in-stream execution produced no outcome"),
isError: true,
};
return { toolCall: entry.toolCall, result: immediate.result, isError: immediate.isError };
}
async function closeOrphanedStreamEntry(entry, emit) {
await emit({
type: "tool_execution_end",
toolCallId: entry.toolCall.id,
toolName: entry.toolCall.name,
result: createErrorToolResult("in-stream result dropped: the call is missing from the final assistant content (provider contract breach)"),
isError: true,
});
}
async function harvestExecutorOnErrorFinal(executor, state, finalMessage, signal, emit) {
const held = executor.remaining();
if (held.length === 0)
return [];
const results = [];
const persistedCallIds = new Set(finalMessage.content.filter((c) => c.type === "toolCall").map((c) => c.id));
for (const entry of held) {
executor.take(entry.toolCall.id);
if (!persistedCallIds.has(entry.toolCall.id)) {
await closeOrphanedStreamEntry(entry, emit);
continue;
}
const finalized = await finalizeStreamEntry(state.context, finalMessage, entry, state.config, signal);
await emitToolExecutionEnd(finalized, emit);
const toolResultMessage = createToolResultMessage(finalized);
await emitToolResultMessage(toolResultMessage, emit);
state.context.messages.push(toolResultMessage);
state.newMessages.push(toolResultMessage);
results.push(toolResultMessage);
}
return results;
}
function mergeBackAdmittedCalls(finalMessage, executor) {
const admitted = executor.admittedToolCalls();
if (admitted.length === 0)
return;
const present = new Set(finalMessage.content.filter((c) => c.type === "toolCall").map((c) => c.id));
const missing = admitted.filter((tc) => !present.has(tc.id));
if (missing.length === 0)
return;
finalMessage.content = [...finalMessage.content, ...missing];
}
function shouldTerminateToolBatch(finalizedCalls) {
return (finalizedCalls.length > 0 &&
finalizedCalls.every((finalized) => finalized.result.terminate === true));
}
function prepareToolCallArguments(tool, toolCall) {
if (!tool.prepareArguments) {
return toolCall;
}
const preparedArguments = tool.prepareArguments(toolCall.arguments);
if (preparedArguments === toolCall.arguments) {
return toolCall;
}
return {
...toolCall,
arguments: preparedArguments,
};
}
export function readToolCallArguments(tool, toolCall, args) {
const prepared = prepareToolCallArguments(tool, { ...toolCall, arguments: structuredClone(args) });
try {
return validateToolArguments(tool, prepared);
}
catch {
return args;
}
}
export const ROSTER_LISTING_MAX = 25;
export const ROSTER_SEARCH_HINT_NAME = "ToolSearch";
function formatRosterRecovery(availableTools) {
if (availableTools.length === 0)
return "";
const shown = availableTools.slice(0, ROSTER_LISTING_MAX);
const withheld = availableTools.length - shown.length;
const listing = withheld > 0 ? `${shown.join(", ")} … and ${withheld} more` : shown.join(", ");
const hint = availableTools.includes(ROSTER_SEARCH_HINT_NAME)
? `. Use ${ROSTER_SEARCH_HINT_NAME} to look up a tool by name.`
: "";
return ` Available tools: ${listing}${hint}`;
}
export const TOOL_VALIDATION_REFUSED_CODE = "tool.validation_refused";
export const TOOL_INPUT_VALIDATION_TIMEOUT_MS = 10_000;
async function runInputValidation(tool, args, toolCallId, signal, onFault) {
if (tool.validateInput === undefined)
return undefined;
try {
const verdict = await boundedValidatorCall(() => tool.validateInput(args, signal !== undefined ? { toolCallId, signal } : { toolCallId }), signal);
if (verdict !== null && typeof verdict === "object" && verdict.ok === false && typeof verdict.message === "string") {
return typeof verdict.code === "string" ? { message: verdict.message, code: verdict.code } : { message: verdict.message };
}
return undefined;
}
catch (error) {
if (signal?.aborted === true)
return undefined;
try {
const ret = onFault?.({ toolName: tool.name, toolCallId, error });
if (ret !== null && typeof ret === "object" && typeof ret.then === "function") {
ret.then(undefined, () => undefined);
}
}
catch {
}
return undefined;
}
}
function boundedValidatorCall(call, signal) {
return new Promise((resolve, reject) => {
let settled = false;
const finish = (fn) => {
if (settled)
return;
settled = true;
clearTimeout(timer);
signal?.removeEventListener("abort", onAbort);
fn();
};
const onAbort = () => finish(() => reject(Object.assign(new Error("tool input pre-validation aborted"), { name: "AbortError" })));
const timer = setTimeout(() => finish(() => reject(new Error(`tool input pre-validation did not answer within ${TOOL_INPUT_VALIDATION_TIMEOUT_MS}ms`))), TOOL_INPUT_VALIDATION_TIMEOUT_MS);
if (signal?.aborted === true) {
onAbort();
return;
}
signal?.addEventListener("abort", onAbort, { once: true });
let out;
try {
out = call();
}
catch (err) {
finish(() => reject(err));
return;
}
Promise.resolve(out).then((v) => finish(() => resolve(v)), (err) => finish(() => reject(err)));
});
}
function sameToolArgs(a, b) {
if (a === b)
return true;
const canon = (v) => JSON.stringify(v, (_k, x) => (x !== null && typeof x === "object" && !Array.isArray(x) ? Object.fromEntries(Object.keys(x).sort().map((k) => [k, x[k]])) : x));
return canon(a) === canon(b);
}
function validationRefusedOutcome(tool, refusal) {
return {
kind: "immediate",
result: createErrorToolResult(refusal.message, {
details: { code: TOOL_VALIDATION_REFUSED_CODE, toolName: tool.name, ...(refusal.code !== undefined ? { reason: refusal.code } : {}) },
}),
isError: true,
};
}
async function prepareToolCall(currentContext, assistantMessage, toolCall, config, signal) {
const tool = findToolByName(currentContext.tools, toolCall.name);
if (!tool) {
const tools = currentContext.tools ?? [];
const availableTools = tools.map((t) => t.name);
const miss = judgeToolNameMiss(toolCall.name, mountedToolNames(tools));
const retiredClause = miss.kind === "expected-absent" ? ` That name is retired: ${miss.detail}.` : "";
return {
kind: "immediate",
result: createErrorToolResult(`Tool ${toolCall.name} not found.${retiredClause}${formatRosterRecovery(availableTools)}`, {
details: {
code: "tool.not_found",
toolName: toolCall.name,
availableTools,
absence: miss.kind,
...(miss.kind === "expected-absent" ? { detail: miss.detail, ...(miss.successor !== undefined ? { successor: miss.successor } : {}) } : {}),
},
}),
isError: true,
};
}
try {
const preparedToolCall = prepareToolCallArguments(tool, toolCall);
let finalArgs = validateToolArguments(tool, preparedToolCall);
const refusal = await runInputValidation(tool, finalArgs, toolCall.id, signal, config.onToolInputValidationFault);
if (refusal !== undefined)
return validationRefusedOutcome(tool, refusal);
const validatedArgs = finalArgs;
if (config.beforeToolCall) {
const beforeResult = await config.beforeToolCall({
assistantMessage,
toolCall,
args: finalArgs,
context: currentContext,
readArguments: (args) => readToolCallArguments(tool, toolCall, args),
}, signal);
if (signal?.aborted) {
return {
kind: "immediate",
result: createErrorToolResult("Operation aborted", { details: safeAbortDetails(config.abortResultDetails) }),
isError: true,
};
}
if (beforeResult?.block) {
const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked", beforeResult.details !== undefined ? { details: beforeResult.details } : undefined);
if (beforeResult.haltRemaining !== undefined) {
return { kind: "immediate", result: blocked, isError: true, halt: beforeResult.haltRemaining };
}
return { kind: "immediate", result: blocked, isError: true };
}
const rewritten = beforeResult?.updatedInput;
if (rewritten !== undefined) {
const judged = structuredClone(rewritten);
finalArgs = validateToolArguments(tool, prepareToolCallArguments(tool, { ...preparedToolCall, arguments: structuredClone(judged) }));
if (!sameToolArgs(finalArgs, judged)) {
throw new Error(`the arguments cleared for "${tool.name}" are not the ones it would run: its reading of them (argument preparation, schema coercions) changes them, and that form was never judged — not executed`);
}
if (!sameToolArgs(validatedArgs, finalArgs)) {
const rewrittenRefusal = await runInputValidation(tool, finalArgs, toolCall.id, signal, config.onToolInputValidationFault);
if (rewrittenRefusal !== undefined)
return validationRefusedOutcome(tool, rewrittenRefusal);
}
}
}
if (signal?.aborted) {
return {
kind: "immediate",
result: createErrorToolResult("Operation aborted"),
isError: true,
};
}
return {
kind: "prepared",
toolCall,
tool,
args: finalArgs,
};
}
catch (error) {
return {
kind: "immediate",
result: thrownErrorToolResult(error),
isError: true,
};
}
}
async function executePreparedToolCall(prepared, signal, emit, abortResultDetails, onLateCancellation) {
const updateEvents = [];
let acceptingUpdates = true;
if (signal?.aborted) {
return { result: createErrorToolResult("operation aborted before execution", { details: safeAbortDetails(abortResultDetails) }), isError: true };
}
const work = (async () => {
let outcome;
try {
const result = await prepared.tool.execute(prepared.toolCall.id, prepared.args, signal, (partialResult) => {
if (!acceptingUpdates) {
return;
}
let delivery;
try {
delivery = Promise.resolve(emit({
type: "tool_execution_update",
toolCallId: prepared.toolCall.id,
toolName: prepared.toolCall.name,
args: prepared.toolCall.arguments,
partialResult,
}));
}
catch (error) {
delivery = Promise.reject(error);
}
void delivery.catch((reason) => {
if (isAbortSemanticsError(reason))
onLateCancellation?.(reason);
});
updateEvents.push(delivery);
});
outcome = { result, isError: result.isError === true };
}
catch (error) {
outcome = {
result: thrownErrorToolResult(error),
isError: true,
};
}
acceptingUpdates = false;
const deliveryFailure = await settleUpdateDeliveries(updateEvents);
if (!deliveryFailure)
return outcome;
const reason = deliveryFailure.reason;
const details = annotateDeliveryFailure(outcome.result, {
message: egressThrownText("same-principal", reason),
});
return details === UNANNOTATED ? outcome : { result: { ...outcome.result, details }, isError: outcome.isError };
})();
return work;
}
const TOOL_PROGRESS_DELIVERY_FAILED = "toolProgressDeliveryFailed";
const UNANNOTATED = Symbol("unannotated");
function annotateDeliveryFailure(result, mark) {
try {
const raw = result.details;
const prior = raw === undefined ? {} : raw;
const proto = prior === null || typeof prior !== "object" ? undefined : Object.getPrototypeOf(prior);
if (proto !== Object.prototype && proto !== null)
return UNANNOTATED;
const details = { ...prior };
if (TOOL_PROGRESS_DELIVERY_FAILED in details)
return UNANNOTATED;
details[TOOL_PROGRESS_DELIVERY_FAILED] = mark;
return details;
}
catch {
return UNANNOTATED;
}
}
function readDeliveryFailureMark(details) {
try {
if (details === null || typeof details !== "object")
return undefined;
const mark = details[TOOL_PROGRESS_DELIVERY_FAILED];
return mark;
}
catch {
return undefined;
}
}
async function settleUpdateDeliveries(updateEvents) {
if (updateEvents.length === 0)
return undefined;
let reportFailure;
const firstFailure = new Promise((resolve) => {
reportFailure = resolve;
});
for (const delivery of updateEvents) {
void delivery.catch((reason) => reportFailure({ reason }));
}
const allSettled = Promise.allSettled(updateEvents).then((entries) => {
for (const entry of entries) {
if (entry.status === "rejected")
return { reason: entry.reason };
}
return undefined;
});
const failure = await Promise.race([firstFailure, allSettled]);
if (failure && isAbortSemanticsError(failure.reason))
throw failure.reason;
return failure;
}
function raiseFatalCancellation(executor) {
const cancellation = executor.takeFatalCancellation();
if (cancellation !== undefined)
throw cancellation;
}
async function executePreparedWithDisclosure(currentContext, assistantMessage, prepared, config, signal, emit) {
let executed;
try {
executed = await executePreparedToolCall(prepared, signal, emit, config.abortResultDetails);
}
catch (error) {
if (isAbortSemanticsError(error))
throw error;
executed = {
result: thrownErrorToolResult(error, (words) => `[tool execution harness failed: ${words}]`),
isError: true,
};
}
return finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal);
}
function finalizeExecutedToolCall(currentContext, assistantMessage, prepared, executed, config, signal) {
return finalizeToolOutcome(prepared.toolCall, executed, config.afterToolCall === undefined
? undefined
: (result, isError) => config.afterToolCall({ assistantMessage, toolCall: prepared.toolCall, args: prepared.args, result, isError, context: currentContext }, signal), config.onToolMediaRefused);
}
export async function finalizeToolOutcome(toolCall, executed, after, onMediaRefused) {
let result = executed.result;
let isError = executed.isError;
const deliveryMark = readDeliveryFailureMark(executed.result.details);
if (after) {
try {
const afterResult = await after(result, isError);
if (afterResult) {
result = {
content: afterResult.content ?? result.content,
details: afterResult.details ?? result.details,
terminate: afterResult.terminate ?? result.terminate,
};
isError = afterResult.isError ?? isError;
}
}
catch (error) {
const note = truncateError(`[post-tool processing failed (the tool already executed): ${egressThrownText("same-principal", error)}]`);
result = { ...result, content: [...result.content, { type: "text", text: note }] };
}
if (deliveryMark !== undefined && readDeliveryFailureMark(result.details) === undefined) {
const details = annotateDeliveryFailure(result, deliveryMark);
if (details !== UNANNOTATED)
result = { ...result, details };
}
}
const admitting = admitToolResultMedia(result.content, toolCall.name);
if (admitting !== undefined) {
const admitted = await admitting;
result = { ...result, content: admitted.content };
if (admitted.refused.length > 0) {
try {
onMediaRefused?.({ toolName: toolCall.name, toolCallId: toolCall.id, refused: admitted.refused });
}
catch {
}
}
}
return { toolCall, result, isError };
}
function safeAbortDetails(fn) {
try {
return fn?.();
}
catch {
return undefined;
}
}
function isAbortSemanticsError(error) {
let cursor = error;
for (let depth = 0; depth < 8 && isErrorLike(cursor); depth++) {
if (readMember(cursor, "name") === "AbortError")
return true;
const next = readMember(cursor, "cause");
if (next === cursor)
break;
cursor = next;
}
return false;
}
function createErrorToolResult(message, source) {
const details = thrownMarksOf(source) ?? {};
return {
content: [{ type: "text", text: truncateError(message) }],
details,
};
}
function thrownErrorToolResult(error, frame = (words) => words) {
return createErrorToolResult(frame(egressThrownText("same-principal", error)), { details: egressValue("same-principal", thrownMarksOf(error)) });
}
async function emitToolExecutionEnd(finalized, emit) {
await emit({
type: "tool_execution_end",
toolCallId: finalized.toolCall.id,
toolName: finalized.toolCall.name,
result: finalized.result,
isError: finalized.isError,
});
}
function createToolResultMessage(finalized) {
return {
role: "toolResult",
toolCallId: finalized.toolCall.id,
toolName: finalized.toolCall.name,
content: finalized.result.content,
details: finalized.result.details,
isError: finalized.isError,
timestamp: Date.now(),
};
}
async function emitToolResultMessage(toolResultMessage, emit) {
await emit({ type: "message_start", message: toolResultMessage });
await emit({ type: "message_end", message: toolResultMessage });
}