@tangle-network/agent-runtime 0.316.0 → 0.317.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/testing.js CHANGED
@@ -8,7 +8,7 @@ import { SANDBOX_SIZE_PRESET_NAMES, canonicalAgentProfileDigest } from "@tangle-
8
8
  //#region src/testing/fixtures/agent-improvement-proposal.json
9
9
  var agent_improvement_proposal_default = {
10
10
  changedSurfaces: ["prompt"],
11
- digest: "sha256:a9a15f7a7a0d46045f8ec065659d19154e192e4e158a56869dd4f6d68f7b0cd5",
11
+ digest: "sha256:40c75af57cbc94d8bf20bc17d7b9b66616511a41c88a69cd9f97072c1edbd4e6",
12
12
  evaluation: {
13
13
  "decision": {
14
14
  "contributingChecks": [
@@ -4579,7 +4579,7 @@ var agent_improvement_proposal_default = {
4579
4579
  ],
4580
4580
  "metadata": {
4581
4581
  "fixture": "agent-improvement-proposal",
4582
- "runtimeVersion": "0.316.0"
4582
+ "runtimeVersion": "0.317.0"
4583
4583
  },
4584
4584
  "objectives": [
4585
4585
  {
@@ -4690,8 +4690,8 @@ var agent_improvement_proposal_default = {
4690
4690
  "baselineContentHash": "sha256:5c21ee53e513fc604cb09754e21c392b24a424da0ef37dbf8f1ee4a8a0b08f09",
4691
4691
  "candidateContentHash": "sha256:60fcbb1c728194bd51d7d19cb732d1c3f1881dce7e0a6266b41c8b98cfd65693",
4692
4692
  "kind": "agent-eval-loop",
4693
- "recordDigest": "sha256:2220856da7165d815cd6182b0d333b3db190ac767616f2493011b3656223d3da",
4694
- "runId": "agent-runtime-0.316.0-proposal-fixture",
4693
+ "recordDigest": "sha256:b9d6d950b9b367f7851b8b5c53a2da9170d04ee5edd4730fd4389b77866713bc",
4694
+ "runId": "agent-runtime-0.317.0-proposal-fixture",
4695
4695
  "schema": "agent-candidate-experiment"
4696
4696
  }
4697
4697
  },
@@ -4714,13 +4714,13 @@ var agent_improvement_proposal_default = {
4714
4714
  }],
4715
4715
  kind: "agent-improvement-proposal",
4716
4716
  proposedAt: "2026-07-10T01:00:00.000Z",
4717
- runId: "agent-runtime-0.316.0-proposal-fixture"
4717
+ runId: "agent-runtime-0.317.0-proposal-fixture"
4718
4718
  };
4719
4719
  //#endregion
4720
4720
  //#region src/testing/fixtures/agent-profile-improvement-proposal.json
4721
4721
  var agent_profile_improvement_proposal_default = {
4722
4722
  changedSurfaces: ["prompt", "skills"],
4723
- digest: "sha256:9ac9d2ca85137901e2368e4b8829392b658a026c0ec9b6ca4a74805acd7a9da2",
4723
+ digest: "sha256:2eba6b28fac0c9892249b1f51cae0d4ca55fc0d4de3d0da19fd008ab781c3645",
4724
4724
  evaluation: {
4725
4725
  "decision": {
4726
4726
  "contributingChecks": [
@@ -6354,7 +6354,7 @@ var agent_profile_improvement_proposal_default = {
6354
6354
  ],
6355
6355
  "metadata": {
6356
6356
  "fixture": "agent-profile-improvement-proposal",
6357
- "runtimeVersion": "0.316.0"
6357
+ "runtimeVersion": "0.317.0"
6358
6358
  },
6359
6359
  "objectives": [
6360
6360
  {
@@ -6465,7 +6465,7 @@ var agent_profile_improvement_proposal_default = {
6465
6465
  "baselineContentHash": "sha256:21c495a37c418c10bde64fbaa188beddeed31f1f051ea60a6a6582a9ee0db704",
6466
6466
  "candidateContentHash": "sha256:103f77bc8481601eef1ad5fe6ba84a40dffabc3a44f421f8c8559121edab84e9",
6467
6467
  "kind": "agent-eval-loop",
6468
- "recordDigest": "sha256:3b5bd31ff4221182c11d47ab2ddcfc34ed49c05b3790eead4226cc89a5a0972a",
6468
+ "recordDigest": "sha256:8b488cae23def1fa3cd468acec0aa57d4c483b5b13cb7b90ba3476e8168bfc39",
6469
6469
  "runId": "profile-improvement-1",
6470
6470
  "schema": "agent-profile-improvement-experiment"
6471
6471
  }
@@ -4,6 +4,13 @@ interface ToolLoopCall {
4
4
  toolCallId?: string;
5
5
  toolName: string;
6
6
  args: Record<string, unknown>;
7
+ /**
8
+ * Provider-specific fields the model attached to this call, echoed back on
9
+ * its assistant `tool_calls` entry in the next request. Gemini 3 returns
10
+ * `extra_content: { google: { thought_signature } }` on each call and answers
11
+ * HTTP 400 when the next request omits it.
12
+ */
13
+ providerFields?: Record<string, unknown>;
7
14
  }
8
15
  /** Outcome of one tool dispatch — structurally compatible with a hub/integration
9
16
  * tool-outcome union, so callers can fold either through the loop. */
@@ -16,8 +23,10 @@ type ToolCallOutcome = {
16
23
  message: string;
17
24
  status?: number;
18
25
  };
19
- /** One OpenAI-shaped tool-call entry carried on an assistant message. */
26
+ /** One OpenAI-shaped tool-call entry carried on an assistant message, plus any
27
+ * provider-specific fields the model attached to the call (`ToolLoopCall.providerFields`). */
20
28
  interface ToolLoopAssistantToolCall {
29
+ [providerField: string]: unknown;
21
30
  id: string;
22
31
  type: 'function';
23
32
  function: {
package/dist/tool-loop.js CHANGED
@@ -27,6 +27,7 @@ function assistantToolCallMessage(turnText, pending) {
27
27
  role: "assistant",
28
28
  content: turnText.trim() || null,
29
29
  tool_calls: pending.map((call) => ({
30
+ ...call.providerFields,
30
31
  id: toolCallId(call),
31
32
  type: "function",
32
33
  function: {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-loop.js","names":[],"sources":["../src/tool-loop.ts"],"sourcesContent":["/**\n * Bounded turn-level tool-dispatch loop.\n *\n * `runAgentTaskStream` runs ONE model turn; `runAgentRounds` orchestrates DELEGATED\n * multi-agent topologies (refine / fanout-vote). Neither is the everyday\n * interactive shape: a chat turn where the model may emit tool calls, each is\n * executed, the results are folded back, and the turn re-runs until the model\n * stops (or a turn cap). Every agent app hand-rolls that loop — this is it,\n * as a reusable primitive.\n *\n * Substrate-neutral by design: the caller supplies `streamTurn` (wrapping\n * whatever backend / `runAgentTaskStream` it uses) and `executeToolCall`\n * (routing to its executors). This module owns the LOOP; the caller owns the\n * model and the executors. `Raw` (streaming variant) is the caller's own\n * event type. The only imported contract is the runtime hook type: hooks are\n * execution-scoped observers, not part of the agent profile.\n */\n\nimport type { RuntimeDecisionEvidenceRef, RuntimeHooks } from './runtime-hooks'\nimport { notifyRuntimeDecisionPoint, notifyRuntimeHookEvent } from './runtime-hooks'\n\nexport interface ToolLoopCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n}\n\n/** Outcome of one tool dispatch — structurally compatible with a hub/integration\n * tool-outcome union, so callers can fold either through the loop. */\nexport type ToolCallOutcome =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string; status?: number }\n\n/** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set\n * far above any legitimate workflow — this is a watchdog, not a policy cap.\n * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */\nconst RUNAWAY_BACKSTOP_TURNS = 200\nconst DEFAULT_DECISION_CONTEXT_CHARS = 12_000\nconst FAILURE_RECOVERY_ACTIONS = ['retry', 'verify', 'continue', 'stop']\n/** Consecutive identical calls (same tool + canonical-JSON args) that trigger\n * stuck-loop detection. The window resets on any different call. */\nconst STUCK_LOOP_THRESHOLD = 3\n\n/** One OpenAI-shaped tool-call entry carried on an assistant message. */\nexport interface ToolLoopAssistantToolCall {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\n/**\n * A message in the running conversation the loop sends to `streamTurn`.\n *\n * The base `{ role, content }` covers `system` / `user` / plain `assistant`\n * turns. Two optional fields carry the OpenAI function-calling contract so a\n * strict model (Claude, and any OpenAI-compatible provider that validates tool\n * history) reads its own tool use back instead of re-issuing the same call:\n *\n * - an assistant turn that emitted tool calls carries `tool_calls`, and its\n * `content` is `null` when the turn was tool-only;\n * - each tool result is its own `{ role: 'tool', tool_call_id, content }`\n * message keyed to the call that produced it.\n *\n * Widening is additive: a `streamTurn` that reads only `role` + `content` still\n * works; one that forwards the whole message to an OpenAI-compatible endpoint\n * now sends correct tool history.\n */\nexport type ToolLoopMessage = {\n role: string\n content: string | null\n tool_calls?: ToolLoopAssistantToolCall[]\n tool_call_id?: string\n}\n\n/** A tool-call id is required to key a `role: 'tool'` result back to its call.\n * When the model omitted one, derive a stable id from the tool name so the\n * assistant `tool_calls` entry and its `tool` result still match. */\nfunction toolCallId(call: ToolLoopCall): string {\n return call.toolCallId ?? `call_${call.toolName}`\n}\n\n/** The assistant turn that emitted `pending`, in OpenAI shape: text content\n * (null when the turn was tool-only) plus its `tool_calls` array. */\nfunction assistantToolCallMessage(turnText: string, pending: ToolLoopCall[]): ToolLoopMessage {\n return {\n role: 'assistant',\n content: turnText.trim() || null,\n tool_calls: pending.map((call) => ({\n id: toolCallId(call),\n type: 'function',\n function: { name: call.toolName, arguments: JSON.stringify(call.args) },\n })),\n }\n}\n\n/** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */\nfunction toolResultMessage(call: ToolLoopCall, content: string): ToolLoopMessage {\n return { role: 'tool', tool_call_id: toolCallId(call), content }\n}\n\n/**\n * One progress report from a running tool, shaped like an MCP `notifications/progress`\n * payload: a free-text `message`, a monotonic `progress` count with an optional\n * `total`, and any structured `data` the caller's UI understands.\n */\nexport interface ToolProgressUpdate {\n message?: string\n progress?: number\n total?: number\n data?: unknown\n}\n\n/** What the loop hands a running tool besides its call. */\nexport interface ToolCallContext {\n /**\n * Report progress while the call runs. The streaming loop yields each update as a\n * `tool_progress` step, in order, before the call's `tool_result`; updates sent after\n * the call settles are dropped.\n */\n progress: (update: ToolProgressUpdate) => void\n}\n\n/** Executes one tool call. Implementations that take only the call stay valid. */\nexport type ToolCallExecutor = (\n call: ToolLoopCall,\n context: ToolCallContext,\n) => Promise<ToolCallOutcome>\n\nfunction defaultRender(label: string, outcome: ToolCallOutcome): string {\n if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`\n return `- ${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n// ── Awaitable variant (drain-only callers, tests) ──────────────────────────\n\nexport type ToolLoopEvent =\n | { type: 'text'; text: string }\n | { type: 'tool_call'; call: ToolLoopCall }\n | { type: 'other'; event: unknown }\n\n/** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =\n * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the\n * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs\n * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /\n * resource outcomes — eval scoring must distinguish them from capability failure. */\nexport type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget'\n\nexport interface ToolLoopResult {\n finalText: string\n toolResults: Array<{ call: ToolLoopCall; label: string; outcome: ToolCallOutcome }>\n turns: number\n stopReason: ToolLoopStopReason\n /** @deprecated Use `stopReason !== 'completed'` instead. */\n cappedOut: boolean\n}\n\nexport interface RunToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<ToolLoopEvent>\n executeToolCall: ToolCallExecutor\n /** Receives each progress update a running tool reports, in order. */\n onToolProgress?: (call: ToolLoopCall, update: ToolProgressUpdate) => void\n isExecutableTool: (toolName: string) => boolean\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits, use `maxCostUsd` or `deadlineMs` instead. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the\n * loop stops with stopReason `deadline`. */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Run the bounded tool loop and return the final text + every executed tool\n * outcome. Awaitable — callers needing to stream events to a UI use\n * {@link streamToolLoop}. */\nexport async function runToolLoop(opts: RunToolLoopOptions): Promise<ToolLoopResult> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n let accumulatedCostUsd = 0\n // Stuck-loop detection: track the last canonical-JSON call signature and how\n // many consecutive times we've seen it.\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n\n // Wall-clock deadline check — before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'deadline' })\n return { finalText, toolResults, turns, stopReason: 'deadline', cappedOut: true }\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: 0,\n finalTextChars: finalText.length,\n })\n break\n }\n\n // Runaway backstop — the model keeps emitting calls past the safety ceiling.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'backstop' })\n return { finalText, toolResults, turns, stopReason: 'backstop', cappedOut: true }\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection: hash the first pending call each turn (a model\n // stuck in a loop re-issues the same call repeatedly, not alternating calls).\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'stuck-loop' })\n return { finalText, toolResults, turns, stopReason: 'stuck-loop', cappedOut: true }\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n let settled = false\n const outcome = await executeGuarded(opts.executeToolCall, call, (update) => {\n if (!settled) opts.onToolProgress?.(call, update)\n })\n settled = true\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'budget' })\n return { finalText, toolResults, turns, stopReason: 'budget', cappedOut: true }\n }\n }\n\n const label = labelFor(call)\n const rendered = render(label, outcome)\n toolResults.push({ call, label, outcome })\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'completed' })\n return { finalText, toolResults, turns, stopReason: 'completed', cappedOut: false }\n}\n\n// ── Streaming variant (SSE chat runtimes + per-event telemetry) ────────────\n\nexport type StreamToolLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | {\n kind: 'tool_progress'\n toolName: string\n toolCallId?: string\n update: ToolProgressUpdate\n }\n | {\n kind: 'tool_result'\n toolName: string\n toolCallId?: string\n label: string\n outcome: ToolCallOutcome\n }\n | { kind: 'capped'; pending: number; stopReason: Exclude<ToolLoopStopReason, 'completed'> }\n\nexport interface StreamToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<Raw>\n extractText: (event: Raw) => string\n extractToolCall: (event: Raw) => ToolLoopCall | null\n isExecutableTool: (toolName: string) => boolean\n executeToolCall: ToolCallExecutor\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Streaming bounded tool loop: yields each raw turn event (the caller maps +\n * telemetries + re-emits it), each `tool_progress` a running tool reports, and\n * each executed `tool_result`; emits one `capped` if it stops for any\n * non-completed reason with calls still pending. */\nexport async function* streamToolLoop<Raw>(\n opts: StreamToolLoopOptions<Raw>,\n): AsyncGenerator<StreamToolLoopYield<Raw>, void, unknown> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'deadline' })\n yield { kind: 'capped', pending: 0, stopReason: 'deadline' }\n return\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'completed' })\n return\n }\n\n // Runaway backstop.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'backstop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'backstop' }\n return\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'stuck-loop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'stuck-loop' }\n return\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n // Progress arrives while the call is in flight; yield it as it lands, in order,\n // and stop accepting it once the call settles so it never trails the result.\n const progress: ToolProgressUpdate[] = []\n let wake: (() => void) | undefined\n let settled = false\n const running = executeGuarded(opts.executeToolCall, call, (update) => {\n if (settled) return\n progress.push(update)\n wake?.()\n }).then((result) => {\n settled = true\n wake?.()\n return result\n })\n while (!settled || progress.length > 0) {\n const update = progress.shift()\n if (update !== undefined) {\n yield {\n kind: 'tool_progress',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n update,\n }\n continue\n }\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n wake = undefined\n }\n const outcome = await running\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'budget' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'budget' }\n return\n }\n }\n\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n const rendered = render(label, outcome)\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n}\n\n/** Run one call; a thrown executor becomes an `executor_error` outcome. */\nasync function executeGuarded(\n execute: ToolCallExecutor,\n call: ToolLoopCall,\n progress: (update: ToolProgressUpdate) => void,\n): Promise<ToolCallOutcome> {\n try {\n return await execute(call, { progress })\n } catch (err) {\n return {\n ok: false,\n code: 'executor_error',\n message: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\ninterface ExecutedToolCall {\n call: ToolLoopCall\n label: string\n outcome: ToolCallOutcome\n rendered: string\n}\n\ninterface NotifyToolFailureRecoveryOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n stepIndex: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n}\n\ninterface NotifyToolLoopEventOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n target: 'agent.run' | 'agent.turn' | 'agent.tool_call'\n phase: 'before' | 'after' | 'error' | 'event'\n id?: string\n stepIndex?: number\n parentId?: string\n payload?: Record<string, unknown>\n metadata?: Record<string, unknown>\n}\n\ninterface ToolLoopObserver {\n loopBefore(maxToolTurns: number, messageCount: number): void\n loopAfter(payload: Record<string, unknown>): void\n turnBefore(toolTurn: number, messageCount: number): string\n turnAfter(toolTurn: number, turnEventId: string, payload: Record<string, unknown>): void\n toolCallBefore(\n toolTurn: number,\n turnEventId: string,\n callIndex: number,\n call: ToolLoopCall,\n ): string\n toolCallAfter(\n toolTurn: number,\n callEventId: string,\n call: ToolLoopCall,\n outcome: ToolCallOutcome,\n ): void\n failureRecovery(options: {\n toolTurn: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n }): void\n}\n\nfunction createToolLoopObserver(\n hooks: RuntimeHooks | undefined,\n runId: string,\n scenarioId: string | undefined,\n): ToolLoopObserver {\n const loopEventId = `${runId}:agent.run`\n return {\n loopBefore: (maxToolTurns, messageCount) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'before',\n id: `${loopEventId}:before`,\n payload: { maxToolTurns, messageCount },\n })\n },\n loopAfter: (payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'after',\n id: `${loopEventId}:after`,\n payload,\n })\n },\n turnBefore: (toolTurn, messageCount) => {\n const turnEventId = `${loopEventId}:${toolTurn}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'before',\n id: turnEventId,\n stepIndex: toolTurn,\n parentId: loopEventId,\n payload: { messageCount },\n })\n return turnEventId\n },\n turnAfter: (toolTurn, turnEventId, payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'after',\n id: `${turnEventId}:after`,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload,\n })\n },\n toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {\n const callEventId = `${turnEventId}:tool-call:${callIndex}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'before',\n id: callEventId,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload: toolCallPayload(call),\n })\n return callEventId\n },\n toolCallAfter: (toolTurn, callEventId, call, outcome) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'after',\n id: `${callEventId}:after`,\n stepIndex: toolTurn,\n parentId: callEventId,\n payload: { ...toolCallPayload(call), outcome: outcomePayload(outcome) },\n })\n },\n failureRecovery: (options) => {\n notifyToolFailureRecovery({\n hooks,\n runId,\n scenarioId,\n stepIndex: options.toolTurn,\n messages: options.messages,\n turnText: options.turnText,\n outcomes: options.outcomes,\n })\n },\n }\n}\n\nfunction notifyToolLoopEvent(options: NotifyToolLoopEventOptions): void {\n notifyRuntimeHookEvent(options.hooks, {\n id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n target: options.target,\n phase: options.phase,\n timestamp: Date.now(),\n stepIndex: options.stepIndex,\n parentId: options.parentId,\n payload: options.payload,\n metadata: { producer: 'tool-loop', ...options.metadata },\n })\n}\n\nfunction notifyToolFailureRecovery(options: NotifyToolFailureRecoveryOptions): void {\n const failed = options.outcomes.filter((item) => !item.outcome.ok)\n if (failed.length === 0) return\n\n const evidence: RuntimeDecisionEvidenceRef[] = []\n for (const item of failed) {\n const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`\n evidence.push({\n source: 'tool_call',\n id,\n detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2_000)}`,\n metadata: { toolName: item.call.toolName, label: item.label },\n })\n evidence.push({\n source: 'tool_result',\n id: `${id}:result`,\n detail: item.rendered,\n metadata: failureMetadata(item.outcome),\n })\n }\n\n notifyRuntimeDecisionPoint(options.hooks, {\n id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n stepIndex: options.stepIndex,\n kind: 'retry',\n candidateActions: [...FAILURE_RECOVERY_ACTIONS],\n context: renderDecisionContext(options.messages, options.turnText, options.outcomes),\n evidence,\n metadata: {\n target: 'failure-recovery',\n source: 'agent.turn',\n failedToolCount: failed.length,\n toolNames: failed.map((item) => item.call.toolName),\n },\n })\n}\n\nfunction toolCallPayload(call: ToolLoopCall): Record<string, unknown> {\n return {\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n argsPreview: stringifySafe(call.args, 2_000),\n }\n}\n\nfunction outcomePayload(outcome: ToolCallOutcome): Record<string, unknown> {\n if (!outcome.ok) {\n return {\n ok: false,\n code: outcome.code,\n message: trimText(outcome.message, 2_000),\n status: outcome.status,\n }\n }\n return {\n ok: true,\n resultPreview: stringifySafe(outcome.result, 2_000),\n }\n}\n\nfunction failureMetadata(outcome: ToolCallOutcome): Record<string, unknown> | undefined {\n if (outcome.ok) return undefined\n return {\n code: outcome.code,\n message: outcome.message,\n status: outcome.status,\n }\n}\n\nfunction renderDecisionContext(\n messages: ToolLoopMessage[],\n turnText: string,\n outcomes: ExecutedToolCall[],\n): string {\n const recent = messages.slice(-6).map((message) => `[${message.role}]\\n${message.content ?? ''}`)\n const assistant = turnText.trim() ? [`[assistant]\\n${turnText}`] : []\n const toolResults = [`[tool results]\\n${outcomes.map((item) => item.rendered).join('\\n')}`]\n return trimText(\n [...recent, ...assistant, ...toolResults].join('\\n\\n'),\n DEFAULT_DECISION_CONTEXT_CHARS,\n )\n}\n\n/** Canonical identifier for a tool call used by stuck-loop detection.\n * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */\nfunction canonicalCallHash(call: ToolLoopCall): string {\n const sortedArgs = Object.fromEntries(\n Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)),\n )\n return `${call.toolName}:${JSON.stringify(sortedArgs)}`\n}\n\nfunction stringifySafe(value: unknown, max: number): string {\n let text: string\n try {\n text = JSON.stringify(value) ?? String(value)\n } catch {\n text = String(value)\n }\n return trimText(text, max)\n}\n\nfunction trimText(text: string, max: number): string {\n if (text.length <= max) return text\n return `${text.slice(0, max)}…`\n}\n\nfunction randomSuffix(len = 8): string {\n return Math.random()\n .toString(36)\n .slice(2, 2 + len)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tool-loop.js","names":[],"sources":["../src/tool-loop.ts"],"sourcesContent":["/**\n * Bounded turn-level tool-dispatch loop.\n *\n * `runAgentTaskStream` runs ONE model turn; `runAgentRounds` orchestrates DELEGATED\n * multi-agent topologies (refine / fanout-vote). Neither is the everyday\n * interactive shape: a chat turn where the model may emit tool calls, each is\n * executed, the results are folded back, and the turn re-runs until the model\n * stops (or a turn cap). Every agent app hand-rolls that loop — this is it,\n * as a reusable primitive.\n *\n * Substrate-neutral by design: the caller supplies `streamTurn` (wrapping\n * whatever backend / `runAgentTaskStream` it uses) and `executeToolCall`\n * (routing to its executors). This module owns the LOOP; the caller owns the\n * model and the executors. `Raw` (streaming variant) is the caller's own\n * event type. The only imported contract is the runtime hook type: hooks are\n * execution-scoped observers, not part of the agent profile.\n */\n\nimport type { RuntimeDecisionEvidenceRef, RuntimeHooks } from './runtime-hooks'\nimport { notifyRuntimeDecisionPoint, notifyRuntimeHookEvent } from './runtime-hooks'\n\nexport interface ToolLoopCall {\n toolCallId?: string\n toolName: string\n args: Record<string, unknown>\n /**\n * Provider-specific fields the model attached to this call, echoed back on\n * its assistant `tool_calls` entry in the next request. Gemini 3 returns\n * `extra_content: { google: { thought_signature } }` on each call and answers\n * HTTP 400 when the next request omits it.\n */\n providerFields?: Record<string, unknown>\n}\n\n/** Outcome of one tool dispatch — structurally compatible with a hub/integration\n * tool-outcome union, so callers can fold either through the loop. */\nexport type ToolCallOutcome =\n | { ok: true; result: unknown }\n | { ok: false; code: string; message: string; status?: number }\n\n/** Runaway-backstop: stops an infinite tool loop where cost is unmetered. Set\n * far above any legitimate workflow — this is a watchdog, not a policy cap.\n * Legitimate per-call budgets come from `maxCostUsd` + `costOf`. */\nconst RUNAWAY_BACKSTOP_TURNS = 200\nconst DEFAULT_DECISION_CONTEXT_CHARS = 12_000\nconst FAILURE_RECOVERY_ACTIONS = ['retry', 'verify', 'continue', 'stop']\n/** Consecutive identical calls (same tool + canonical-JSON args) that trigger\n * stuck-loop detection. The window resets on any different call. */\nconst STUCK_LOOP_THRESHOLD = 3\n\n/** One OpenAI-shaped tool-call entry carried on an assistant message, plus any\n * provider-specific fields the model attached to the call (`ToolLoopCall.providerFields`). */\nexport interface ToolLoopAssistantToolCall {\n [providerField: string]: unknown\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n}\n\n/**\n * A message in the running conversation the loop sends to `streamTurn`.\n *\n * The base `{ role, content }` covers `system` / `user` / plain `assistant`\n * turns. Two optional fields carry the OpenAI function-calling contract so a\n * strict model (Claude, and any OpenAI-compatible provider that validates tool\n * history) reads its own tool use back instead of re-issuing the same call:\n *\n * - an assistant turn that emitted tool calls carries `tool_calls`, and its\n * `content` is `null` when the turn was tool-only;\n * - each tool result is its own `{ role: 'tool', tool_call_id, content }`\n * message keyed to the call that produced it.\n *\n * Widening is additive: a `streamTurn` that reads only `role` + `content` still\n * works; one that forwards the whole message to an OpenAI-compatible endpoint\n * now sends correct tool history.\n */\nexport type ToolLoopMessage = {\n role: string\n content: string | null\n tool_calls?: ToolLoopAssistantToolCall[]\n tool_call_id?: string\n}\n\n/** A tool-call id is required to key a `role: 'tool'` result back to its call.\n * When the model omitted one, derive a stable id from the tool name so the\n * assistant `tool_calls` entry and its `tool` result still match. */\nfunction toolCallId(call: ToolLoopCall): string {\n return call.toolCallId ?? `call_${call.toolName}`\n}\n\n/** The assistant turn that emitted `pending`, in OpenAI shape: text content\n * (null when the turn was tool-only) plus its `tool_calls` array. */\nfunction assistantToolCallMessage(turnText: string, pending: ToolLoopCall[]): ToolLoopMessage {\n return {\n role: 'assistant',\n content: turnText.trim() || null,\n tool_calls: pending.map((call) => ({\n ...call.providerFields,\n id: toolCallId(call),\n type: 'function',\n function: { name: call.toolName, arguments: JSON.stringify(call.args) },\n })),\n }\n}\n\n/** One `role: 'tool'` result message keyed to its call by `tool_call_id`. */\nfunction toolResultMessage(call: ToolLoopCall, content: string): ToolLoopMessage {\n return { role: 'tool', tool_call_id: toolCallId(call), content }\n}\n\n/**\n * One progress report from a running tool, shaped like an MCP `notifications/progress`\n * payload: a free-text `message`, a monotonic `progress` count with an optional\n * `total`, and any structured `data` the caller's UI understands.\n */\nexport interface ToolProgressUpdate {\n message?: string\n progress?: number\n total?: number\n data?: unknown\n}\n\n/** What the loop hands a running tool besides its call. */\nexport interface ToolCallContext {\n /**\n * Report progress while the call runs. The streaming loop yields each update as a\n * `tool_progress` step, in order, before the call's `tool_result`; updates sent after\n * the call settles are dropped.\n */\n progress: (update: ToolProgressUpdate) => void\n}\n\n/** Executes one tool call. Implementations that take only the call stay valid. */\nexport type ToolCallExecutor = (\n call: ToolLoopCall,\n context: ToolCallContext,\n) => Promise<ToolCallOutcome>\n\nfunction defaultRender(label: string, outcome: ToolCallOutcome): string {\n if (outcome.ok) return `- ${label} → ok: ${JSON.stringify(outcome.result)}`\n return `- ${label} → failed (${outcome.code}): ${outcome.message}`\n}\n\n// ── Awaitable variant (drain-only callers, tests) ──────────────────────────\n\nexport type ToolLoopEvent =\n | { type: 'text'; text: string }\n | { type: 'tool_call'; call: ToolLoopCall }\n | { type: 'other'; event: unknown }\n\n/** Why the loop stopped. `completed` = model finished naturally; `stuck-loop` =\n * ≥3 consecutive identical tool calls (same tool + args); `backstop` = hit the\n * runaway-backstop cap (200 by default); `deadline` = wall-clock deadlineMs\n * exceeded; `budget` = maxCostUsd exhausted. Non-`completed` stops are infra /\n * resource outcomes — eval scoring must distinguish them from capability failure. */\nexport type ToolLoopStopReason = 'completed' | 'stuck-loop' | 'backstop' | 'deadline' | 'budget'\n\nexport interface ToolLoopResult {\n finalText: string\n toolResults: Array<{ call: ToolLoopCall; label: string; outcome: ToolCallOutcome }>\n turns: number\n stopReason: ToolLoopStopReason\n /** @deprecated Use `stopReason !== 'completed'` instead. */\n cappedOut: boolean\n}\n\nexport interface RunToolLoopOptions {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<ToolLoopEvent>\n executeToolCall: ToolCallExecutor\n /** Receives each progress update a running tool reports, in order. */\n onToolProgress?: (call: ToolLoopCall, update: ToolProgressUpdate) => void\n isExecutableTool: (toolName: string) => boolean\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow.\n * For per-workflow limits, use `maxCostUsd` or `deadlineMs` instead. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). When exceeded the\n * loop stops with stopReason `deadline`. */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Run the bounded tool loop and return the final text + every executed tool\n * outcome. Awaitable — callers needing to stream events to a UI use\n * {@link streamToolLoop}. */\nexport async function runToolLoop(opts: RunToolLoopOptions): Promise<ToolLoopResult> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n const toolResults: ToolLoopResult['toolResults'] = []\n let finalText = ''\n let turns = 0\n let accumulatedCostUsd = 0\n // Stuck-loop detection: track the last canonical-JSON call signature and how\n // many consecutive times we've seen it.\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n turns++\n\n // Wall-clock deadline check — before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'deadline' })\n return { finalText, toolResults, turns, stopReason: 'deadline', cappedOut: true }\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const ev of opts.streamTurn([...messages])) {\n if (ev.type === 'text') {\n turnText += ev.text\n finalText += ev.text\n } else if (ev.type === 'tool_call' && opts.isExecutableTool(ev.call.toolName)) {\n pending.push(ev.call)\n }\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: 0,\n finalTextChars: finalText.length,\n })\n break\n }\n\n // Runaway backstop — the model keeps emitting calls past the safety ceiling.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'backstop' })\n return { finalText, toolResults, turns, stopReason: 'backstop', cappedOut: true }\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection: hash the first pending call each turn (a model\n // stuck in a loop re-issues the same call repeatedly, not alternating calls).\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'stuck-loop' })\n return { finalText, toolResults, turns, stopReason: 'stuck-loop', cappedOut: true }\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n let settled = false\n const outcome = await executeGuarded(opts.executeToolCall, call, (update) => {\n if (!settled) opts.onToolProgress?.(call, update)\n })\n settled = true\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n toolResults.push({ call, label, outcome })\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'budget' })\n return { finalText, toolResults, turns, stopReason: 'budget', cappedOut: true }\n }\n }\n\n const label = labelFor(call)\n const rendered = render(label, outcome)\n toolResults.push({ call, label, outcome })\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n observer.loopAfter({ turns, toolResults: toolResults.length, stopReason: 'completed' })\n return { finalText, toolResults, turns, stopReason: 'completed', cappedOut: false }\n}\n\n// ── Streaming variant (SSE chat runtimes + per-event telemetry) ────────────\n\nexport type StreamToolLoopYield<Raw> =\n | { kind: 'event'; event: Raw }\n | {\n kind: 'tool_progress'\n toolName: string\n toolCallId?: string\n update: ToolProgressUpdate\n }\n | {\n kind: 'tool_result'\n toolName: string\n toolCallId?: string\n label: string\n outcome: ToolCallOutcome\n }\n | { kind: 'capped'; pending: number; stopReason: Exclude<ToolLoopStopReason, 'completed'> }\n\nexport interface StreamToolLoopOptions<Raw> {\n systemPrompt: string\n userMessage: string\n priorMessages?: ToolLoopMessage[]\n streamTurn: (messages: ToolLoopMessage[]) => AsyncIterable<Raw>\n extractText: (event: Raw) => string\n extractToolCall: (event: Raw) => ToolLoopCall | null\n isExecutableTool: (toolName: string) => boolean\n executeToolCall: ToolCallExecutor\n /** Runaway-backstop cap. Default 200 — set far above any legitimate workflow. */\n maxToolTurns?: number\n /** Wall-clock deadline in ms since epoch (Date.now()-based). */\n deadlineMs?: number\n /** Maximum total cost in USD. Requires `costOf` to meter each tool call. */\n maxCostUsd?: number\n /** Return the USD cost of one outcome. Required for `maxCostUsd` to work. */\n costOf?: (call: ToolLoopCall, outcome: ToolCallOutcome) => number\n renderResult?: (label: string, outcome: ToolCallOutcome) => string\n labelFor?: (call: ToolLoopCall) => string\n runId?: string\n scenarioId?: string\n hooks?: RuntimeHooks\n}\n\n/** Streaming bounded tool loop: yields each raw turn event (the caller maps +\n * telemetries + re-emits it), each `tool_progress` a running tool reports, and\n * each executed `tool_result`; emits one `capped` if it stops for any\n * non-completed reason with calls still pending. */\nexport async function* streamToolLoop<Raw>(\n opts: StreamToolLoopOptions<Raw>,\n): AsyncGenerator<StreamToolLoopYield<Raw>, void, unknown> {\n const backstop = opts.maxToolTurns ?? RUNAWAY_BACKSTOP_TURNS\n const render = opts.renderResult ?? defaultRender\n const labelFor = opts.labelFor ?? ((c: ToolLoopCall) => c.toolName)\n const runId = opts.runId ?? `agent-run-${randomSuffix()}`\n const messages: ToolLoopMessage[] = [\n { role: 'system', content: opts.systemPrompt },\n ...(opts.priorMessages ?? []),\n { role: 'user', content: opts.userMessage },\n ]\n const observer = createToolLoopObserver(opts.hooks, runId, opts.scenarioId)\n let accumulatedCostUsd = 0\n let lastCallHash: string | null = null\n let consecutiveCount = 0\n\n observer.loopBefore(backstop, messages.length)\n\n for (let toolTurn = 0; ; toolTurn++) {\n // Wall-clock deadline check before every new turn.\n if (opts.deadlineMs !== undefined && Date.now() >= opts.deadlineMs) {\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'deadline' })\n yield { kind: 'capped', pending: 0, stopReason: 'deadline' }\n return\n }\n\n let turnText = ''\n const pending: ToolLoopCall[] = []\n const turnEventId = observer.turnBefore(toolTurn, messages.length)\n for await (const event of opts.streamTurn([...messages])) {\n yield { kind: 'event', event }\n turnText += opts.extractText(event)\n const call = opts.extractToolCall(event)\n if (call && opts.isExecutableTool(call.toolName)) pending.push(call)\n }\n if (pending.length === 0) {\n observer.turnAfter(toolTurn, turnEventId, { pendingToolCalls: 0 })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'completed' })\n return\n }\n\n // Runaway backstop.\n if (toolTurn >= backstop) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'backstop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'backstop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'backstop' }\n return\n }\n\n // The assistant turn that emitted the calls, carrying its tool_calls array,\n // so a strict model reads its own tool use back in OpenAI shape.\n messages.push(assistantToolCallMessage(turnText, pending))\n const outcomes: ExecutedToolCall[] = []\n for (const [callIndex, call] of pending.entries()) {\n // Stuck-loop detection.\n const callHash = canonicalCallHash(call)\n if (callHash === lastCallHash) {\n consecutiveCount++\n } else {\n lastCallHash = callHash\n consecutiveCount = 1\n }\n if (consecutiveCount >= STUCK_LOOP_THRESHOLD) {\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'stuck-loop',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'stuck-loop' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'stuck-loop' }\n return\n }\n\n const callEventId = observer.toolCallBefore(toolTurn, turnEventId, callIndex, call)\n // Progress arrives while the call is in flight; yield it as it lands, in order,\n // and stop accepting it once the call settles so it never trails the result.\n const progress: ToolProgressUpdate[] = []\n let wake: (() => void) | undefined\n let settled = false\n const running = executeGuarded(opts.executeToolCall, call, (update) => {\n if (settled) return\n progress.push(update)\n wake?.()\n }).then((result) => {\n settled = true\n wake?.()\n return result\n })\n while (!settled || progress.length > 0) {\n const update = progress.shift()\n if (update !== undefined) {\n yield {\n kind: 'tool_progress',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n update,\n }\n continue\n }\n await new Promise<void>((resolve) => {\n wake = resolve\n })\n wake = undefined\n }\n const outcome = await running\n\n // Budget check after each tool call.\n if (opts.maxCostUsd !== undefined && opts.costOf !== undefined) {\n accumulatedCostUsd += opts.costOf(call, outcome)\n if (accumulatedCostUsd >= opts.maxCostUsd) {\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n messages.push(toolResultMessage(call, render(label, outcome)))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n stopReason: 'budget',\n })\n observer.loopAfter({ turns: toolTurn + 1, stopReason: 'budget' })\n yield { kind: 'capped', pending: pending.length, stopReason: 'budget' }\n return\n }\n }\n\n const label = labelFor(call)\n yield {\n kind: 'tool_result',\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n label,\n outcome,\n }\n const rendered = render(label, outcome)\n outcomes.push({ call, label, outcome, rendered })\n // One role:'tool' message per result, keyed to its call by tool_call_id.\n messages.push(toolResultMessage(call, rendered))\n observer.toolCallAfter(toolTurn, callEventId, call, outcome)\n }\n observer.failureRecovery({\n toolTurn,\n messages,\n turnText,\n outcomes,\n })\n observer.turnAfter(toolTurn, turnEventId, {\n pendingToolCalls: pending.length,\n toolResults: outcomes.map((item) => ({\n toolName: item.call.toolName,\n toolCallId: item.call.toolCallId,\n ok: item.outcome.ok,\n })),\n failedToolCalls: outcomes.filter((item) => !item.outcome.ok).length,\n })\n }\n}\n\n/** Run one call; a thrown executor becomes an `executor_error` outcome. */\nasync function executeGuarded(\n execute: ToolCallExecutor,\n call: ToolLoopCall,\n progress: (update: ToolProgressUpdate) => void,\n): Promise<ToolCallOutcome> {\n try {\n return await execute(call, { progress })\n } catch (err) {\n return {\n ok: false,\n code: 'executor_error',\n message: err instanceof Error ? err.message : String(err),\n }\n }\n}\n\ninterface ExecutedToolCall {\n call: ToolLoopCall\n label: string\n outcome: ToolCallOutcome\n rendered: string\n}\n\ninterface NotifyToolFailureRecoveryOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n stepIndex: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n}\n\ninterface NotifyToolLoopEventOptions {\n hooks?: RuntimeHooks\n runId: string\n scenarioId?: string\n target: 'agent.run' | 'agent.turn' | 'agent.tool_call'\n phase: 'before' | 'after' | 'error' | 'event'\n id?: string\n stepIndex?: number\n parentId?: string\n payload?: Record<string, unknown>\n metadata?: Record<string, unknown>\n}\n\ninterface ToolLoopObserver {\n loopBefore(maxToolTurns: number, messageCount: number): void\n loopAfter(payload: Record<string, unknown>): void\n turnBefore(toolTurn: number, messageCount: number): string\n turnAfter(toolTurn: number, turnEventId: string, payload: Record<string, unknown>): void\n toolCallBefore(\n toolTurn: number,\n turnEventId: string,\n callIndex: number,\n call: ToolLoopCall,\n ): string\n toolCallAfter(\n toolTurn: number,\n callEventId: string,\n call: ToolLoopCall,\n outcome: ToolCallOutcome,\n ): void\n failureRecovery(options: {\n toolTurn: number\n messages: ToolLoopMessage[]\n turnText: string\n outcomes: ExecutedToolCall[]\n }): void\n}\n\nfunction createToolLoopObserver(\n hooks: RuntimeHooks | undefined,\n runId: string,\n scenarioId: string | undefined,\n): ToolLoopObserver {\n const loopEventId = `${runId}:agent.run`\n return {\n loopBefore: (maxToolTurns, messageCount) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'before',\n id: `${loopEventId}:before`,\n payload: { maxToolTurns, messageCount },\n })\n },\n loopAfter: (payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.run',\n phase: 'after',\n id: `${loopEventId}:after`,\n payload,\n })\n },\n turnBefore: (toolTurn, messageCount) => {\n const turnEventId = `${loopEventId}:${toolTurn}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'before',\n id: turnEventId,\n stepIndex: toolTurn,\n parentId: loopEventId,\n payload: { messageCount },\n })\n return turnEventId\n },\n turnAfter: (toolTurn, turnEventId, payload) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.turn',\n phase: 'after',\n id: `${turnEventId}:after`,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload,\n })\n },\n toolCallBefore: (toolTurn, turnEventId, callIndex, call) => {\n const callEventId = `${turnEventId}:tool-call:${callIndex}`\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'before',\n id: callEventId,\n stepIndex: toolTurn,\n parentId: turnEventId,\n payload: toolCallPayload(call),\n })\n return callEventId\n },\n toolCallAfter: (toolTurn, callEventId, call, outcome) => {\n notifyToolLoopEvent({\n hooks,\n runId,\n scenarioId,\n target: 'agent.tool_call',\n phase: 'after',\n id: `${callEventId}:after`,\n stepIndex: toolTurn,\n parentId: callEventId,\n payload: { ...toolCallPayload(call), outcome: outcomePayload(outcome) },\n })\n },\n failureRecovery: (options) => {\n notifyToolFailureRecovery({\n hooks,\n runId,\n scenarioId,\n stepIndex: options.toolTurn,\n messages: options.messages,\n turnText: options.turnText,\n outcomes: options.outcomes,\n })\n },\n }\n}\n\nfunction notifyToolLoopEvent(options: NotifyToolLoopEventOptions): void {\n notifyRuntimeHookEvent(options.hooks, {\n id: options.id ?? `${options.runId}:${options.target}:${options.phase}`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n target: options.target,\n phase: options.phase,\n timestamp: Date.now(),\n stepIndex: options.stepIndex,\n parentId: options.parentId,\n payload: options.payload,\n metadata: { producer: 'tool-loop', ...options.metadata },\n })\n}\n\nfunction notifyToolFailureRecovery(options: NotifyToolFailureRecoveryOptions): void {\n const failed = options.outcomes.filter((item) => !item.outcome.ok)\n if (failed.length === 0) return\n\n const evidence: RuntimeDecisionEvidenceRef[] = []\n for (const item of failed) {\n const id = item.call.toolCallId ?? `${options.stepIndex}:${item.label}`\n evidence.push({\n source: 'tool_call',\n id,\n detail: `${item.call.toolName} ${stringifySafe(item.call.args, 2_000)}`,\n metadata: { toolName: item.call.toolName, label: item.label },\n })\n evidence.push({\n source: 'tool_result',\n id: `${id}:result`,\n detail: item.rendered,\n metadata: failureMetadata(item.outcome),\n })\n }\n\n notifyRuntimeDecisionPoint(options.hooks, {\n id: `${options.runId}:agent.turn:${options.stepIndex}:failure-recovery`,\n runId: options.runId,\n scenarioId: options.scenarioId,\n stepIndex: options.stepIndex,\n kind: 'retry',\n candidateActions: [...FAILURE_RECOVERY_ACTIONS],\n context: renderDecisionContext(options.messages, options.turnText, options.outcomes),\n evidence,\n metadata: {\n target: 'failure-recovery',\n source: 'agent.turn',\n failedToolCount: failed.length,\n toolNames: failed.map((item) => item.call.toolName),\n },\n })\n}\n\nfunction toolCallPayload(call: ToolLoopCall): Record<string, unknown> {\n return {\n toolName: call.toolName,\n toolCallId: call.toolCallId,\n argsPreview: stringifySafe(call.args, 2_000),\n }\n}\n\nfunction outcomePayload(outcome: ToolCallOutcome): Record<string, unknown> {\n if (!outcome.ok) {\n return {\n ok: false,\n code: outcome.code,\n message: trimText(outcome.message, 2_000),\n status: outcome.status,\n }\n }\n return {\n ok: true,\n resultPreview: stringifySafe(outcome.result, 2_000),\n }\n}\n\nfunction failureMetadata(outcome: ToolCallOutcome): Record<string, unknown> | undefined {\n if (outcome.ok) return undefined\n return {\n code: outcome.code,\n message: outcome.message,\n status: outcome.status,\n }\n}\n\nfunction renderDecisionContext(\n messages: ToolLoopMessage[],\n turnText: string,\n outcomes: ExecutedToolCall[],\n): string {\n const recent = messages.slice(-6).map((message) => `[${message.role}]\\n${message.content ?? ''}`)\n const assistant = turnText.trim() ? [`[assistant]\\n${turnText}`] : []\n const toolResults = [`[tool results]\\n${outcomes.map((item) => item.rendered).join('\\n')}`]\n return trimText(\n [...recent, ...assistant, ...toolResults].join('\\n\\n'),\n DEFAULT_DECISION_CONTEXT_CHARS,\n )\n}\n\n/** Canonical identifier for a tool call used by stuck-loop detection.\n * Keys are sorted so `{b:1,a:2}` and `{a:2,b:1}` produce the same hash. */\nfunction canonicalCallHash(call: ToolLoopCall): string {\n const sortedArgs = Object.fromEntries(\n Object.entries(call.args).sort(([a], [b]) => a.localeCompare(b)),\n )\n return `${call.toolName}:${JSON.stringify(sortedArgs)}`\n}\n\nfunction stringifySafe(value: unknown, max: number): string {\n let text: string\n try {\n text = JSON.stringify(value) ?? String(value)\n } catch {\n text = String(value)\n }\n return trimText(text, max)\n}\n\nfunction trimText(text: string, max: number): string {\n if (text.length <= max) return text\n return `${text.slice(0, max)}…`\n}\n\nfunction randomSuffix(len = 8): string {\n return Math.random()\n .toString(36)\n .slice(2, 2 + len)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-runtime",
3
- "version": "0.316.0",
3
+ "version": "0.317.0",
4
4
  "description": "Shared task-lifecycle skeleton for agents: a recursive loop kernel for chat turns, one-shot tasks, and multi-attempt loops, with trace capture and eval-gated self-improvement. Domain behavior lives in adapters; scoring and ship-gates in @tangle-network/agent-eval.",
5
5
  "homepage": "https://github.com/tangle-network/agent-runtime#readme",
6
6
  "repository": {