@aparte/engine 0.22.0 → 0.24.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.
@@ -232,12 +232,24 @@ export interface StreamDeferredCall {
232
232
  input: unknown;
233
233
  reason: StreamDeferredReason;
234
234
  }
235
+ /**
236
+ * Who refused a call, for the row: the model reads one sentence whoever it was from, the
237
+ * person reading the transcript needs to know. `'earlier-refusal'` is a call the loop did
238
+ * not run because another call of the same step had been refused.
239
+ */
240
+ export type StreamRefusalOrigin = 'person' | 'policy' | 'earlier-refusal';
241
+ /**
242
+ * Why a call was aborted: a Stop, its handler's timeout, no handler for it here, or a gate
243
+ * with nobody to ask.
244
+ */
245
+ export type StreamAbortCause = 'stopped' | 'timeout' | 'no-handler' | 'no-resolver';
235
246
  /** The answer to a deferred call, handed to the run that resumes it. */
236
247
  export type StreamDeferredOutcome = {
237
248
  kind: 'decision';
238
249
  approved: boolean;
239
250
  reason?: string;
240
251
  instruction?: string;
252
+ by?: StreamRefusalOrigin;
241
253
  payload?: unknown;
242
254
  } | {
243
255
  kind: 'result';
@@ -280,6 +292,8 @@ export type StreamApprovalResolver = (call: {
280
292
  * write attributes the refusal to the user, which would be a lie here.
281
293
  */
282
294
  reason?: string;
295
+ /** Who refused, when a `reason` alone would say it wrong; without it a reason is a policy's. */
296
+ by?: StreamRefusalOrigin;
283
297
  }>;
284
298
  /**
285
299
  * High-level, DOM-free events isomorphic to the inline loop's `targetElement.*`
@@ -334,10 +348,17 @@ export type StreamRunEvent = {
334
348
  } | {
335
349
  type: 'tool-approved';
336
350
  toolCallId: string;
337
- } | {
351
+ }
352
+ /**
353
+ * `reason` is the sentence the model reads; `by` and `instruction` are for the row, which
354
+ * speaks to a person in their own language (optional for a runner that emits its own).
355
+ */
356
+ | {
338
357
  type: 'tool-rejected';
339
358
  toolCallId: string;
340
359
  reason: string;
360
+ by?: StreamRefusalOrigin;
361
+ instruction?: string;
341
362
  }
342
363
  /** The handler threw (not an abort): the row hears of the crash before the run ends on it. */
343
364
  | {
@@ -349,9 +370,16 @@ export type StreamRunEvent = {
349
370
  toolCallId: string;
350
371
  result: string;
351
372
  structuredResult?: unknown;
352
- } | {
373
+ }
374
+ /**
375
+ * The call did not run to an answer. `cause` says why, for the row: `'no-handler'` and
376
+ * `'no-resolver'` are the page's setup, not anything the person did. Absent on the calls
377
+ * a run ended before reaching.
378
+ */
379
+ | {
353
380
  type: 'tool-aborted';
354
381
  toolCallId: string;
382
+ cause?: StreamAbortCause;
355
383
  } | {
356
384
  type: 'turn-limit-exceeded';
357
385
  scope: 'global' | 'tool';
@@ -1 +1 @@
1
- {"version":3,"file":"stream-events.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stream-events.d.ts","sourceRoot":"","sources":["../../src/agent/stream-events.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,7 @@
19
19
  * product's, and a mode nothing in this repository emitted was a contract
20
20
  * maintained for nobody.
21
21
  */
22
- import type { StreamRunEmitter, StreamChatEvent, StreamChatRequest, StreamAgentMessage, StreamToolCall, StreamToolHandler, StreamToolConfig, StreamApprovalResolver, StreamUsage, StreamDeferral } from './stream-events.js';
22
+ import type { StreamRunEmitter, StreamChatEvent, StreamChatRequest, StreamAgentMessage, StreamToolCall, StreamToolHandler, StreamToolConfig, StreamApprovalResolver, StreamUsage, StreamDeferral, StreamRefusalOrigin } from './stream-events.js';
23
23
  export interface StreamRunOptions {
24
24
  /** Id of the assistant message being streamed (opaque; carried in events). */
25
25
  messageId: string;
@@ -137,4 +137,21 @@ export declare function runStreamAgent(opts: StreamRunOptions): Promise<StreamUs
137
137
  * first; a server checks them against the ticket it signed when the run paused.
138
138
  */
139
139
  export declare function unansweredTail(messages: readonly StreamAgentMessage[]): StreamToolCall[];
140
+ /**
141
+ * What a refusal says, and to whom. `reason` is the sentence the model reads: the refuser's
142
+ * own when it gave one, else the person's words quoted, else the fixed sentence. `by` and
143
+ * `instruction` are for the row, which tells a person who refused in their own language —
144
+ * a reason with no origin is a policy's, as {@link StreamApprovalResolver} defines it.
145
+ *
146
+ * Exported so a runner that answers an agent elsewhere writes the same sentence.
147
+ */
148
+ export declare function toolRefusal(decision: {
149
+ reason?: string;
150
+ instruction?: string;
151
+ by?: StreamRefusalOrigin;
152
+ }): {
153
+ reason: string;
154
+ by: StreamRefusalOrigin;
155
+ instruction?: string;
156
+ };
140
157
  //# sourceMappingURL=stream-run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"stream-run.d.ts","sourceRoot":"","sources":["../../src/agent/stream-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,EACX,cAAc,EAGjB,MAAM,oBAAoB,CAAC;AAO5B,MAAM,WAAW,gBAAgB;IAC7B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,EAAE,iBAAiB,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,CAAC;IAChG,uFAAuF;IACvF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAC5D,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,CAAC;IAClE,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA6Z7F;AAED;;;;;;;GAOG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GAAG,cAAc,EAAE,CAWxF"}
1
+ {"version":3,"file":"stream-run.d.ts","sourceRoot":"","sources":["../../src/agent/stream-run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACR,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,WAAW,EACX,cAAc,EAGd,mBAAmB,EAEtB,MAAM,oBAAoB,CAAC;AAO5B,MAAM,WAAW,gBAAgB;IAC7B,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,WAAW,EAAE,iBAAiB,CAAC;IAC/B;;;;;OAKG;IACH,aAAa,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC,aAAa,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,CAAC;IAChG,uFAAuF;IACvF,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,iBAAiB,GAAG,SAAS,CAAC;IAC5D,wEAAwE;IACxE,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,gBAAgB,GAAG,SAAS,CAAC;IAClE,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,+DAA+D;IAC/D,OAAO,EAAE,gBAAgB,CAAC;IAC1B,0EAA0E;IAC1E,MAAM,EAAE,WAAW,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACH,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACxD;;;;;OAKG;IACH,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA6Z7F;AAED;;;;;;;GAOG;AACH;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GAAG,cAAc,EAAE,CAWxF;AA8BD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,EAAE,CAAC,EAAE,mBAAmB,CAAA;CAAE,GAAG;IACxG,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,mBAAmB,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB,CAcA"}
package/dist/index.js CHANGED
@@ -79,13 +79,13 @@ async function runStreamAgent(opts) {
79
79
  emitter({ type: "tool-start", toolCallId: syntheticId, name: tc.name, input: tc.input });
80
80
  const handler = toolLookup(tc.name);
81
81
  if (!handler) {
82
- emitter({ type: "tool-aborted", toolCallId: syntheticId });
82
+ emitter({ type: "tool-aborted", toolCallId: syntheticId, cause: "no-handler" });
83
83
  continueLoop = false;
84
84
  continue;
85
85
  }
86
86
  const outcome = await invokeToolHandler(handler, { id: syntheticId, name: tc.name, input: tc.input }, signal, toolTimeoutMs);
87
87
  if (outcome.status === "aborted") {
88
- emitter({ type: "tool-aborted", toolCallId: syntheticId });
88
+ emitter(outcome.cause ? { type: "tool-aborted", toolCallId: syntheticId, cause: outcome.cause } : { type: "tool-aborted", toolCallId: syntheticId });
89
89
  continueLoop = false;
90
90
  continue;
91
91
  }
@@ -249,11 +249,17 @@ function queuedReason(call, lookup) {
249
249
  const gated = typeof cfg?.needsApproval === "function" ? cfg.needsApproval({ id: call.id, name: call.name, input: call.input }) : cfg?.needsApproval;
250
250
  return gated && gated !== "deny" ? "approval" : "queued";
251
251
  }
252
+ function toolRefusal(decision) {
253
+ const reason = decision.reason?.trim();
254
+ const by = decision.by ?? (reason ? "policy" : "person");
255
+ const sentence = reason || (by === "policy" ? "Tool execution was refused by the approval policy." : by === "earlier-refusal" ? "Not run: an earlier call of the same step was refused." : decision.instruction ? `The user rejected this tool call and said: ${decision.instruction}` : "Tool execution was rejected by the user.");
256
+ return decision.instruction ? { reason: sentence, by, instruction: decision.instruction } : { reason: sentence, by };
257
+ }
252
258
  function refuse(ctx, call, decision) {
253
- const rejection = decision.reason?.trim() || (decision.instruction ? `The user rejected this tool call and said: ${decision.instruction}` : "Tool execution was rejected by the user.");
254
- ctx.emitter({ type: "tool-rejected", toolCallId: call.id, reason: rejection });
259
+ const refusal = toolRefusal(decision);
260
+ ctx.emitter({ type: "tool-rejected", toolCallId: call.id, ...refusal });
255
261
  ctx.declareCall(call);
256
- ctx.append({ role: "tool", content: rejection, toolCallId: call.id, toolName: call.name });
262
+ ctx.append({ role: "tool", content: refusal.reason, toolCallId: call.id, toolName: call.name });
257
263
  return "stop-turn";
258
264
  }
259
265
  async function processCall(call, ctx) {
@@ -269,7 +275,7 @@ async function processCall(call, ctx) {
269
275
  }
270
276
  if (cfg?.external) {
271
277
  if (!ctx.deferral) {
272
- ctx.emitter({ type: "tool-aborted", toolCallId: call.id });
278
+ ctx.emitter({ type: "tool-aborted", toolCallId: call.id, cause: "no-handler" });
273
279
  return "stop-run";
274
280
  }
275
281
  if (!answer || answer.kind === "decision") return { defer: "external" };
@@ -284,7 +290,7 @@ async function processCall(call, ctx) {
284
290
  }
285
291
  const handler = ctx.toolLookup(call.name);
286
292
  if (!handler) {
287
- ctx.emitter({ type: "tool-aborted", toolCallId: call.id });
293
+ ctx.emitter({ type: "tool-aborted", toolCallId: call.id, cause: "no-handler" });
288
294
  return "stop-run";
289
295
  }
290
296
  let effectiveInput = call.input;
@@ -296,7 +302,7 @@ async function processCall(call, ctx) {
296
302
  }
297
303
  if (!provided && ctx.deferral && gated !== "deny") return { defer: "approval" };
298
304
  if (!provided && !ctx.approvalResolver) {
299
- ctx.emitter({ type: "tool-aborted", toolCallId: call.id });
305
+ ctx.emitter({ type: "tool-aborted", toolCallId: call.id, cause: "no-resolver" });
300
306
  return "stop-run";
301
307
  }
302
308
  let decision = provided;
@@ -311,7 +317,7 @@ async function processCall(call, ctx) {
311
317
  }
312
318
  }
313
319
  if (signal.aborted || !decision) {
314
- ctx.emitter({ type: "tool-aborted", toolCallId: call.id });
320
+ ctx.emitter({ type: "tool-aborted", toolCallId: call.id, cause: "stopped" });
315
321
  return "stop-run";
316
322
  }
317
323
  if (!decision.approved) {
@@ -330,7 +336,7 @@ async function processCall(call, ctx) {
330
336
  ctx.toolTimeoutMs
331
337
  );
332
338
  if (outcome.status === "aborted") {
333
- ctx.emitter({ type: "tool-aborted", toolCallId: call.id });
339
+ ctx.emitter(outcome.cause ? { type: "tool-aborted", toolCallId: call.id, cause: outcome.cause } : { type: "tool-aborted", toolCallId: call.id });
334
340
  return "stop-run";
335
341
  } else if (outcome.status === "failed") {
336
342
  ctx.emitter({ type: "tool-failed", toolCallId: call.id, error: errorText(outcome.error) });
@@ -343,20 +349,22 @@ async function processCall(call, ctx) {
343
349
  return "continue";
344
350
  }
345
351
  async function invokeToolHandler(handler, call, signal, toolTimeoutMs) {
346
- if (signal.aborted) return { status: "aborted" };
352
+ if (signal.aborted) return { status: "aborted", cause: "stopped" };
347
353
  const controller = new AbortController();
348
354
  const onParentAbort = () => controller.abort();
349
355
  signal.addEventListener("abort", onParentAbort, { once: true });
350
356
  let onParentAbortRace;
351
357
  const parentAborted = new Promise((resolve) => {
352
- onParentAbortRace = () => resolve({ status: "aborted" });
358
+ onParentAbortRace = () => resolve({ status: "aborted", cause: "stopped" });
353
359
  signal.addEventListener("abort", onParentAbortRace, { once: true });
354
360
  });
355
361
  let timeout;
362
+ let expired = false;
356
363
  const timedOut = new Promise((resolve) => {
357
364
  timeout = setTimeout(() => {
365
+ expired = true;
358
366
  controller.abort();
359
- resolve({ status: "aborted" });
367
+ resolve({ status: "aborted", cause: "timeout" });
360
368
  }, toolTimeoutMs);
361
369
  });
362
370
  try {
@@ -367,7 +375,11 @@ async function invokeToolHandler(handler, call, signal, toolTimeoutMs) {
367
375
  ]);
368
376
  return result;
369
377
  } catch (err) {
370
- if (err?.name === "AbortError") return { status: "aborted" };
378
+ if (err?.name === "AbortError") {
379
+ if (signal.aborted) return { status: "aborted", cause: "stopped" };
380
+ if (expired) return { status: "aborted", cause: "timeout" };
381
+ return { status: "aborted" };
382
+ }
371
383
  return { status: "failed", error: err };
372
384
  } finally {
373
385
  clearTimeout(timeout);
@@ -381,6 +393,7 @@ function errorText(error) {
381
393
  }
382
394
  export {
383
395
  runStreamAgent,
396
+ toolRefusal,
384
397
  unansweredTail
385
398
  };
386
399
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/agent/stream-run.ts"],"sourcesContent":["/**\n * stream-run.ts — framework-free structured-stream agent loop.\n *\n * The agent loop — extracted from core's inline `_streamLoop`, which it has since\n * replaced (audit 2026-08-28, D1): a `while(tool_use)` loop\n * that consumes a structured `AsyncIterable<StreamChatEvent>` from a transport,\n * runs approved tools, feeds their results back into the history, and re-calls\n * the transport until the model stops asking for tools. It performs **no DOM\n * work** — it emits {@link StreamRunEvent}s (in the inline loop's exact order) that\n * an adapter in `@aparte/core` turns into `targetElement.*` calls.\n *\n * Formerly the parity target of core's inline loop; now the one loop, its call\n * sequences pinned by core's `stream-parity` snapshots.\n * Scope: text · thinking · tool_use (+ HITL) · done · error · synthetic toolChoice\n * bypass. Tagged blocks in the text (`<artifact>`, `<cite>`…) are adapter-side: core's\n * parser reads the grammars registered on the config. The built-in `create_artifact`\n * left with the artifact (D7) — it is a registered tool now. The raw / XML artifact\n * modes and the multi-phase pipeline were removed (audit 2026-08-28, D2) — orchestration is the\n * product's, and a mode nothing in this repository emitted was a contract\n * maintained for nobody.\n */\n\nimport type {\n StreamRunEmitter,\n StreamChatEvent,\n StreamChatRequest,\n StreamAgentMessage,\n StreamToolCall,\n StreamToolHandler,\n StreamToolConfig,\n StreamApprovalResolver,\n StreamUsage,\n StreamDeferral,\n StreamDeferredCall,\n StreamDeferredReason,\n} from './stream-events.js';\n\n/** Default per-tool-call handler timeout — mirrors `TOOL_HANDLER_TIMEOUT_MS`. */\nconst DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1000;\n/** Default global turn cap — mirrors `AparteClientOptions.maxTurns ?? 10`. */\nconst DEFAULT_MAX_TURNS = 10;\n\nexport interface StreamRunOptions {\n /** Id of the assistant message being streamed (opaque; carried in events). */\n messageId: string;\n /** Turn-1 request; the loop clones its `messages` and enriches them per turn. */\n baseRequest: StreamChatRequest;\n /**\n * Calls the transport with the (possibly enriched) request. Returns the\n * structured stream, or a plain string for a non-streaming provider. Mirrors\n * `getTransport().chat(provider, request, auth, ctx)` with provider/auth/ctx\n * closed over by the adapter.\n */\n transportCall: (request: StreamChatRequest) => Promise<AsyncIterable<StreamChatEvent> | string>;\n /** Resolves a tool's handler by name (mirrors `aparteGlobalConfig.getToolHandler`). */\n toolLookup: (name: string) => StreamToolHandler | undefined;\n /** Resolves a tool's loop config by name (maxTurns / needsApproval). */\n toolConfigLookup?: (name: string) => StreamToolConfig | undefined;\n /** HITL approval resolver for `needsApproval` tools (default: never called). */\n approvalResolver?: StreamApprovalResolver;\n /** Synchronous, ordered event sink consumed by the adapter. */\n emitter: StreamRunEmitter;\n /** Single abort signal composing `_isAborted` + the stream controller. */\n signal: AbortSignal;\n /** Global turn cap. @default 10 */\n maxTurns?: number;\n /** Per-tool-call handler timeout in ms. @default 300000 */\n toolTimeoutMs?: number;\n /**\n * Called for every turn the loop appends to the history — the assistant message\n * carrying the turn's calls, each `tool` message answering one (resolved or\n * rejected), and a pipeline phase's reply — in order, and always before the\n * transport call that would carry it. Never called for the messages you passed\n * in `baseRequest`.\n *\n * For hosts that own their own transcript. The loop re-sends its `messages`\n * array every turn, which suits stateless message APIs but not a **prefix\n * cache** (llama.cpp slots, vLLM), where turn N+1 must EXTEND turn N byte for\n * byte. Such a host builds its own request in {@link transportCall} — ignoring\n * `request.messages` — and mirrors these notifications into its append-only\n * log, instead of reimplementing the loop's tool bookkeeping. Synchronous and\n * ordered, like {@link emitter}.\n *\n * One rule about the envelope: a turn's assistant message is reported ONCE, when\n * its first call completes, and the calls that complete later in the same turn are\n * already in that same object's `toolCalls` — the array is shared by reference, not\n * copied. Hold the reference, not a snapshot, and every result you receive after it\n * is declared by it.\n *\n * Two of its fields are finalised after you are notified, then: `toolCalls` gains\n * the turn's later calls, and `content` is re-read at the turn's end (a provider\n * that emits `[tool, text]` streams the rest of the sentence after the envelope is\n * opened — see `syncEnvelopeText`). A host that must write BYTES at receipt — an\n * append-only log, which cannot hold a reference — should therefore treat the\n * envelope as provisional and write it only once the turn's last `tool` message\n * has arrived, or re-read the object it holds before serialising.\n *\n * `content` is the field that moved here, and the failure is louder than it was:\n * a snapshot no longer loses a trailing clause, it loses the whole sentence the\n * assistant said before it called anything.\n */\n onHistoryAppend?: (message: StreamAgentMessage) => void;\n /**\n * Generates the ids the loop mints itself — today only the synthetic call of a\n * forced `toolChoice` (`prefix` is `'synthetic-tool'`). The default is a\n * deterministic per-run counter; core's client injects a crypto-based one. Tool\n * ids otherwise flow from the stream.\n */\n idGen?: (prefix: string) => string;\n /**\n * Defer instead of block. With this set, a call the gate sends to a person is not\n * awaited on `approvalResolver`, and an `external` tool is not treated as missing:\n * each takes its answer from `outcomes`, and without one the call is DEFERRED —\n * declared on the turn's envelope with no `tool` answer. The rest of the model's\n * reply is still read to its end (its later calls pending too, nothing run after the\n * pause point), and the run ends on `run-suspended` instead of `run-done`.\n *\n * A run whose `baseRequest.messages` ends on an assistant envelope with unanswered\n * calls RESUMES: those calls go through the same `processCall` as a streamed one,\n * in order, before the first transport call. A call the gate denies is never\n * deferred — it is refused, through `approvalResolver`, as without this option.\n */\n deferral?: StreamDeferral;\n /**\n * The caller's provider id. Opaque to the loop, like `messageId`: a runner that\n * forwards the run elsewhere (a server) needs it.\n */\n providerId?: string;\n}\n\n/** Warn once per event type for something this version does not map, then skip it. */\nconst warnedUnknownEvents = new Set<string>();\nfunction warnUnknownStreamEvent(event: unknown): void {\n const type = String((event as { type?: unknown })?.type ?? 'undefined');\n if (warnedUnknownEvents.has(type)) return;\n warnedUnknownEvents.add(type);\n console.warn(\n `[runStreamAgent] Ignoring unrecognised stream event \"${type}\". The reply is`\n + ' unaffected; this usually means the provider or its SDK emits a part this'\n + ' version of aparté does not map yet.',\n );\n}\n\n/**\n * Run the structured-stream agent loop. Resolves the last turn's usage (the\n * `done{usage}` last-write-wins, mirroring `_streamLoop`'s return), or\n * `undefined`. Throws on a stream `error` event or a non-abort tool failure —\n * the caller (adapter) routes that to its lifecycle-error handler, exactly as\n * `_handleSend`/`_handleRetry`/`_handleEdit` catch `_streamLoop`.\n *\n * This block has to stay ADJACENT to the function. It used to sit above\n * `warnUnknownStreamEvent`, six lines and a docblock away from what it describes, and a\n * docblock separated from its declaration is attached to nothing: TypeDoc took the\n * helper's comment as the neighbouring one and `@aparte/engine`'s reference page carried\n * the package's headline export with no description at all — the very export its own\n * frontmatter names. Same trap the custom-elements analyser has with a class docblock\n * pushed above an import.\n */\nexport async function runStreamAgent(opts: StreamRunOptions): Promise<StreamUsage | undefined> {\n const {\n transportCall,\n toolLookup,\n toolConfigLookup,\n approvalResolver,\n emitter,\n signal,\n maxTurns = DEFAULT_MAX_TURNS,\n toolTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS,\n deferral,\n } = opts;\n\n // Reassignable: the synthetic-toolChoice bypass strips `toolChoice`/`tools`\n // from it after the forced turn-1 handler runs (mirrors `_streamLoop`'s\n // `baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined }`).\n let baseRequest = opts.baseRequest;\n\n let idSeq = 0;\n const idGen = opts.idGen ?? ((prefix: string) => `${prefix}-${idSeq++}`);\n\n // Mutable history the loop enriches with the assistant and `tool` turns of a tool round-trip. Every\n // enrichment goes through `append`, so a caller owning its own transcript sees\n // the same turns in the same order (see `onHistoryAppend`).\n const messages: StreamAgentMessage[] = [...baseRequest.messages];\n const append = (message: StreamAgentMessage): void => {\n messages.push(message);\n opts.onHistoryAppend?.(message);\n };\n\n let continueLoop = true;\n let turns = 0;\n let lastUsage: StreamUsage | undefined;\n\n // Leading write before turn 1: mark the message streaming.\n emitter({ type: 'run-start' });\n\n // ── Resume: answer what a previous run left pending, before asking the model ──\n // The envelope is already in `messages` (it came with the request), so a resumed\n // call is declared by it: nothing to declare again, and no `tool-start` either —\n // its row exists, from the run that paused.\n const tail = deferral ? unansweredTail(messages) : [];\n if (tail.length > 0) {\n const stillPending: StreamDeferredCall[] = [];\n const ctx: CallContext = { turns: 1, maxTurns, toolLookup, toolConfigLookup, approvalResolver, emitter, signal, toolTimeoutMs, declareCall: () => {}, append, deferral };\n for (const [i, call] of tail.entries()) {\n if (stillPending.length > 0) {\n stillPending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: queuedReason(call, toolConfigLookup) });\n continue;\n }\n let verdict: CallVerdict;\n try {\n verdict = await processCall(call, ctx);\n } catch (error) {\n // A crash ends the run like a `stop-run` below, and the calls after it\n // have rows too: each is told, then the run fails on the crash.\n for (const rest of tail.slice(i + 1)) emitter({ type: 'tool-aborted', toolCallId: rest.id });\n throw error;\n }\n if (typeof verdict === 'object') {\n stillPending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: verdict.defer });\n continue;\n }\n // Unlike a streamed turn, a refusal does not skip the calls after it: each of\n // them is declared by the envelope, and a call left without a `tool` answer\n // makes the provider reject the next request. Each has its own outcome.\n if (verdict === 'stop-run') {\n // Unlike a streamed turn too, the calls after it have rows already, from\n // the run that paused: each is told it will not run, not left waiting.\n for (const rest of tail.slice(i + 1)) emitter({ type: 'tool-aborted', toolCallId: rest.id });\n continueLoop = false;\n break;\n }\n }\n if (stillPending.length > 0 && !signal.aborted) {\n emitter({ type: 'run-suspended', pending: stillPending });\n return undefined;\n }\n }\n\n // ── outer (turn) loop — one iteration = one transport call ────────────────\n while (continueLoop) {\n // Abort at the top of a turn: no stream open yet, so no cancel needed. The\n // `run-aborted` itself is emitted once, at the loop's exit (see below).\n if (signal.aborted) break;\n\n turns++;\n if (turns > maxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'global', limit: maxTurns });\n break;\n }\n\n // ── Synthetic toolChoice bypass (mirrors _streamLoop) ──────\n // toolChoice = { name, input } (orchestrator-forced): skip the LLM for\n // turn 1, run the handler directly, inject its result as a `tool` message,\n // then strip toolChoice/tools and fall through to the transport call in\n // the SAME turn so the model answers with the tool result already in\n // history. (The adapter's tool-start handler injects renderer CSS here\n // too — a benign, idempotent gain over _streamLoop's synthetic path,\n // which skipped it; reconciled in the parity test.)\n const toolChoice = baseRequest['toolChoice'];\n if (\n turns === 1 &&\n toolChoice &&\n typeof toolChoice === 'object' &&\n !Array.isArray(toolChoice) &&\n (toolChoice as { input?: unknown }).input !== undefined\n ) {\n // Narrowed to the declared shape rather than `input: unknown`: now that\n // `StreamChatRequest.toolChoice` mirrors core's type, the cast can be exact.\n const tc = toolChoice as { name: string; input: Record<string, unknown> };\n const syntheticId = idGen('synthetic-tool');\n emitter({ type: 'tool-start', toolCallId: syntheticId, name: tc.name, input: tc.input });\n\n const handler = toolLookup(tc.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n\n const outcome = await invokeToolHandler(handler, { id: syntheticId, name: tc.name, input: tc.input }, signal, toolTimeoutMs);\n if (outcome.status === 'aborted') {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n if (outcome.status === 'failed') {\n emitter({ type: 'tool-failed', toolCallId: syntheticId, error: errorText(outcome.error) });\n throw outcome.error;\n }\n emitter({ type: 'tool-resolved', toolCallId: syntheticId, result: outcome.content, structuredResult: outcome.structuredContent });\n append({ role: 'assistant', content: '', toolCalls: [{ id: syntheticId, name: tc.name, input: tc.input }] });\n append({ role: 'tool', content: outcome.content, toolCallId: syntheticId, toolName: tc.name });\n baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined };\n }\n\n // A stop that lands before the first event surfaces here as a REJECTION,\n // not as an `error` event — the fetch is aborted while still in flight, so\n // it never reaches the stream the loop reads. Letting it propagate made a\n // deliberate stop indistinguishable from a transport failure for anyone\n // driving this loop directly. (Core's `_streamTurn` has the mirror of this\n // guard; the browser suite is what found the path, after the two\n // event-level guards were already closed.)\n const request: StreamChatRequest = { ...baseRequest, messages };\n /*\n * Forcing a tool is a TURN-1 instruction, and only the loop can lift it: the\n * request is rebuilt from `baseRequest` every turn, so a `toolChoice` object\n * left there was re-sent after the tool had already answered — the model was\n * made to call it again, every turn, until `maxTurns`, and the run ended on\n * `turn-limit-exceeded` instead of a reply. Every object shape, not only the\n * `{ name, input }` the synthetic bypass above strips for itself: `{ name }`\n * alone is what a consumer sets, and it never enters that branch.\n */\n const forced = baseRequest['toolChoice'];\n if (forced && typeof forced === 'object') baseRequest = { ...baseRequest, toolChoice: 'auto' };\n let response: Awaited<ReturnType<typeof transportCall>>;\n try {\n response = await transportCall(request);\n } catch (err: unknown) {\n if (signal.aborted || (err as { name?: string } | undefined)?.name === 'AbortError') {\n // `break`, not `return`: the post-loop `run-done` below is documented\n // as running on EVERY exit path, and five of the six abort paths fall\n // through to it. Returning here skipped it, so a consumer wiring\n // `runStreamAgent` + `createStreamAdapter` directly never got the\n // `completed` status and the bubble stayed flagged streaming — which\n // is verbatim the defect the client-side guard was added to fix.\n // (`run-aborted` is emitted once, at the loop's exit.)\n continueLoop = false;\n break;\n }\n throw err;\n }\n\n // Non-streaming provider: the string IS the full assistant message. The\n // adapter writes it and completes (no done{usage}); spike scenarios\n // always stream, so we finish the run here.\n //\n // `text-flush` is not optional here. It is the adapter's ONLY caller of\n // `parser.finalize()`, and the parser deliberately withholds an ambiguous\n // tail — a lone backtick, a triple backtick, a `<`, or the safe window\n // inside an unterminated fence. Breaking out before the flush dropped all\n // of it, so a non-streaming reply ending on any of those characters lost\n // them and one consisting only of them rendered nothing at all. Core's\n // inline loop does `parse()` AND `finalize()` on its whole-response\n // branch; this is the twin.\n if (typeof response === 'string') {\n emitter({ type: 'text-delta', delta: response });\n emitter({ type: 'text-flush' });\n break;\n }\n\n // New turn: the adapter resets its per-turn parser / thinking / streaming\n // state here (mirrors `_streamLoop` creating a fresh AparteStreamParser +\n // sets each outer iteration). No DOM effect of its own.\n emitter({ type: 'turn-start' });\n\n // Per-turn streaming state.\n let precedingText = '';\n const toolCallsThisTurn: StreamToolCall[] = [];\n // \"Did this turn ask for tools\" — a different question from \"which calls are\n // declared\" (`toolCallsThisTurn`, which now holds only committed ones).\n let sawToolUse = false;\n /**\n * The turn's ONE assistant envelope, held by reference. Created — and\n * appended, so the host is notified once — the first time a call needs it;\n * every later call of the turn is already in the SAME `toolCalls` array, so a\n * `tool` message appended afterwards is always declared by it.\n *\n * This replaces an id scan over `messages` that guessed whether the envelope\n * was already there. The `create_artifact` fast path pushed a fresh envelope\n * of its own; the scan then found the artifact's id in it, concluded \"already\n * pushed\", and the next tool's result went out with no call declaring it —\n * the P0 of the 2026-08-28 audit, a history an Anthropic-shaped API rejects\n * outright. A reference cannot be guessed wrong.\n */\n let envelope: StreamAgentMessage | null = null;\n /*\n * A call is DECLARED in the envelope only once it is committed to producing\n * a `tool` message — at the three sites that append one right after. It used\n * to be pushed the moment the `tool_use` event was read, into the same array\n * the envelope holds by reference, so a call halted before its result (no\n * handler, turn limit, missing resolver, an abort during the wait) still\n * appeared in a history the host was told to hold by reference: an\n * assistant message declaring a call that never gets a result.\n */\n const declareCall = (call: StreamToolCall): void => {\n toolCallsThisTurn.push(call);\n if (envelope) return;\n envelope = { role: 'assistant', content: precedingText.trim(), toolCalls: toolCallsThisTurn };\n append(envelope);\n };\n /*\n * The envelope's text is a SNAPSHOT taken when the first call is declared, and\n * a turn can go on streaming afterwards — a provider that emits `[tool, text]`\n * in that order (`@aparte/provider-scenario` does; openai-compat flushes its\n * calls at the end of the stream and so cannot) had the sentence shown to the\n * user and dropped from the history, where nothing else carries it. Re-read at\n * the turn's end, through the closure: the envelope is held by reference for\n * exactly this reason, the way `toolCalls` is.\n */\n const syncEnvelopeText = (): void => {\n if (envelope) envelope.content = precedingText.trim();\n };\n /*\n * A call id must be unique WITHIN the turn: the transcript keys a row on it\n * (`tool-${id}`, and `updateSegment` takes the first match) and the history\n * files a `tool` message under it. A vendor that omits `id` used to give every\n * call of a turn the same one, so the second call's result was written onto the\n * first call's row and the pair could not be matched at all on the next turn.\n * A repeat is renamed rather than refused — the model asked for both.\n */\n const idsThisTurn = new Set<string>();\n const uniqueCallId = (raw: string): string => {\n let id = raw;\n for (let n = 2; idsThisTurn.has(id); n++) id = `${raw}#${n}`;\n idsThisTurn.add(id);\n return id;\n };\n\n // ── inner (SSE) loop — manual iteration so we can abort around each read.\n // Checked on BOTH sides of the read, and the second check is the one that\n // matters: the loop spends nearly all of its time parked on `next()`, so an\n // abort arriving while parked — the user pressing Stop while watching text\n // stream — is invisible to a check that only runs before the await. It also\n // covers both shapes a provider takes on abort: an `error` event or a quiet\n // close. Without it the `error` branch below throws and the caller reports a\n // deliberate stop as a failure. Mirrors `_streamLoop`'s `bailOnAbort`.\n // The calls this turn leaves for someone else to answer (see `deferral`).\n const pending: StreamDeferredCall[] = [];\n // Without the resume's answers: they were given for the calls a previous run left\n // pending, never for a new call that carries the same id — and a vendor that sends\n // no ids has them minted per stream, so the next turn reuses them.\n const callContext: CallContext = {\n turns, maxTurns, toolLookup, toolConfigLookup, approvalResolver, emitter, signal, toolTimeoutMs, declareCall, append,\n deferral: deferral && { ...deferral, outcomes: undefined },\n };\n\n const iterator = response[Symbol.asyncIterator]();\n const bailOnAbort = async (): Promise<boolean> => {\n if (!signal.aborted) return false;\n // Guarded like the `finally` below: a transport whose iterator throws on\n // the way out turned a deliberate Stop into a thrown run error, and core\n // paints an error card over the reply the user had just stopped. `try`,\n // not `.catch()`: a hand-written iterator throws BEFORE there is a promise\n // to reject, and one that returns a plain object has no `.catch` at all.\n try { await iterator.return?.(undefined); } catch { /* best effort */ }\n continueLoop = false;\n return true;\n };\n try {\n while (true) {\n if (await bailOnAbort()) break;\n\n const step = await iterator.next();\n if (await bailOnAbort()) break;\n if (step.done) break;\n const event = step.value;\n\n if (event.type === 'thinking') {\n emitter({ type: 'thinking-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'text') {\n precedingText += event.delta;\n emitter({ type: 'text-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'done') {\n if (event.usage) lastUsage = event.usage;\n continue;\n }\n\n if (event.type === 'error') {\n // Mirror `_streamLoop`: throw; the caller's catch handles it.\n throw new Error(event.message);\n }\n\n // A DISCRIMINANT GUARD, not a comment.\n //\n // This used to be the bare comment `// event.type === 'tool_use'`, so\n // anything the chain above did not recognise fell in here and was\n // treated as a tool call: a `tool-start` / `tool-aborted` pair with an\n // undefined id and name, and — worse — the rest of the stream was\n // never processed, so the reply was truncated too.\n //\n // Core's inline loop had the opposite failure on the same input (it\n // threw and replaced the reply with an error bubble). Both now ignore\n // the event and carry on, which is what forward compatibility with a\n // provider SDK actually requires.\n if (event.type !== 'tool_use') {\n warnUnknownStreamEvent(event);\n continue;\n }\n sawToolUse = true;\n // From here the CALL's id, not the event's: see `uniqueCallId`. The\n // row, the handler, the approval and the history slot must all name\n // the same call, so the rename happens once, before the first emit.\n const callId = uniqueCallId(event.id);\n\n // No built-in tool of any name: `create_artifact` used to be dispatched\n // here, before the tool path, with no handler, no approval and its own\n // result. A tool is a tool — the app registers it (`@aparte/plugin-artifacts`\n // does), and it goes through tool-start, the gate, the handler and the\n // envelope like every other.\n emitter({ type: 'tool-start', toolCallId: callId, name: event.name, input: event.input });\n\n const call: StreamToolCall = { id: callId, name: event.name, input: event.input };\n if (pending.length > 0) {\n // Past the pause point nothing runs. The call is declared, and says what\n // it waits for, so the client answers every call of the reply in ONE\n // round trip rather than one per call.\n declareCall(call);\n const reason = queuedReason(call, toolConfigLookup);\n if (reason === 'approval') {\n emitter({ type: 'tool-awaiting-approval', toolCallId: call.id, name: call.name, input: call.input });\n }\n pending.push({ toolCallId: call.id, name: call.name, input: call.input, reason });\n continue;\n }\n const verdict = await processCall(call, callContext);\n if (typeof verdict === 'object') {\n declareCall(call);\n pending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: verdict.defer });\n continue;\n }\n if (verdict === 'stop-run') { continueLoop = false; break; }\n // A refusal ends this turn's calls but not the run: see `processCall`.\n if (verdict === 'stop-turn') break;\n }\n\n // Turn boundary: finalize the parser (flush residual text). Mirrors\n // `_streamLoop`'s `textParser.finalize()` call — runs on normal\n // end AND abort-break, but NOT after a thrown `error` (which escapes\n // this try before reaching here, exactly like `_streamLoop`).\n emitter({ type: 'text-flush' });\n // Everything this turn streamed belongs to the turn, including what came\n // after the call that opened the envelope.\n syncEnvelopeText();\n\n } finally {\n // Mirror `reader.releaseLock()` in the finally: settle the iterator,\n // however it fails on the way out (see `bailOnAbort`).\n try { await iterator.return?.(undefined); } catch { /* best effort */ }\n }\n\n // Calls left pending: the run ends here, without `run-done` — the turn goes on\n // through the run that resumes them. (A Stop takes the abort exit instead.)\n if (pending.length > 0 && !signal.aborted) {\n emitter({ type: 'run-suspended', pending, usage: lastUsage });\n return lastUsage;\n }\n\n // No tool calls this turn → the final answer.\n if (!sawToolUse) continueLoop = false;\n }\n\n // Post-loop finalization runs on every exit path (normal / abort / maxTurns).\n //\n // `run-aborted` is decided HERE, once, from the signal — not at each site that\n // notices the abort. It used to be emitted at three of the six abort exits\n // (top of turn, transport rejection, mid-stream bail), and the three tool-level\n // ones — a Stop during a handler, during an approval wait, or with no resolver —\n // set `continueLoop = false` and fell through to `run-done` alone: no terminal\n // lifecycle event reached the host, so the typing indicator and the streaming\n // id were never cleared and the turn was never persisted. A tool TIMEOUT aborts\n // only the child controller, so `signal.aborted` stays false and that turn still\n // ends as done, unchanged.\n if (signal.aborted) emitter({ type: 'run-aborted' });\n emitter({ type: 'run-done', usage: lastUsage });\n return lastUsage;\n}\n\n/**\n * Run a tool handler with a per-call timeout, linked to the run's abort signal.\n * Resolves `{ status: 'resolved', content }` on success, or `{ status: 'aborted' }`\n * if the handler aborts (timeout OR parent abort); any other error is re-thrown so\n * the caller routes it to lifecycle-error, exactly as `_streamLoop` does. Shared\n * by the generic tool path and the synthetic-toolChoice bypass (mirrors the\n * AbortController + `TOOL_HANDLER_TIMEOUT_MS` dance both use in `_streamLoop`).\n */\n/**\n * The calls a history leaves unanswered: those of its LAST assistant envelope that no\n * following `tool` message answers, in declaration order. A resumed run answers them\n * first; a server checks them against the ticket it signed when the run paused.\n */\nexport function unansweredTail(messages: readonly StreamAgentMessage[]): StreamToolCall[] {\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i]!;\n if (m.role === 'assistant') {\n if (!m.toolCalls?.length) return [];\n const answered = new Set(messages.slice(i + 1).filter((x) => x.role === 'tool').map((x) => x.toolCallId));\n return m.toolCalls.filter((c) => !answered.has(c.id));\n }\n if (m.role !== 'tool') return [];\n }\n return [];\n}\n\n/** Why a call after the pause point waits: what the client must answer, or nothing. */\nfunction queuedReason(call: StreamToolCall, lookup: StreamRunOptions['toolConfigLookup']): StreamDeferredReason {\n const cfg = lookup?.(call.name);\n if (cfg?.external) return 'external';\n const gated = typeof cfg?.needsApproval === 'function'\n ? cfg.needsApproval({ id: call.id, name: call.name, input: call.input as Record<string, unknown> })\n : cfg?.needsApproval;\n return gated && gated !== 'deny' ? 'approval' : 'queued';\n}\n\n/** What the caller does after a call: next event, end this turn's stream, end the run. */\ntype CallVerdict = 'continue' | 'stop-turn' | 'stop-run' | { defer: 'approval' | 'external' };\n\ninterface CallContext {\n turns: number;\n maxTurns: number;\n toolLookup: StreamRunOptions['toolLookup'];\n toolConfigLookup: StreamRunOptions['toolConfigLookup'];\n approvalResolver: StreamRunOptions['approvalResolver'];\n emitter: StreamRunEmitter;\n signal: AbortSignal;\n toolTimeoutMs: number;\n /** Declares the call on the turn's envelope. */\n declareCall: (call: StreamToolCall) => void;\n append: (message: StreamAgentMessage) => void;\n deferral: StreamDeferral | undefined;\n}\n\n/**\n * A refused call: the row says so and the model reads why — the person's own words when\n * they gave them, else the fixed sentence, byte-identical to core's. Answers `'stop-turn'`:\n * the turn's other calls must not run, but the loop takes another turn so the model reads it.\n */\nfunction refuse(\n ctx: CallContext,\n call: StreamToolCall,\n decision: { reason?: string; instruction?: string },\n): 'stop-turn' {\n // Truthiness, like `instruction` beside it: an empty `reason` is no reason, not an\n // empty `tool` message.\n const rejection = decision.reason?.trim()\n || (decision.instruction\n ? `The user rejected this tool call and said: ${decision.instruction}`\n : 'Tool execution was rejected by the user.');\n ctx.emitter({ type: 'tool-rejected', toolCallId: call.id, reason: rejection });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: rejection, toolCallId: call.id, toolName: call.name });\n return 'stop-turn';\n}\n\n/**\n * One call, from its gate to its `tool` message: the ONLY place the loop decides what a\n * call does. The stream reader calls it for each `tool_use` as it arrives; one function,\n * so no second path can drift from it the way core's two loops did before D1.\n */\nasync function processCall(call: StreamToolCall, ctx: CallContext): Promise<CallVerdict> {\n const { signal } = ctx;\n const input = call.input as Record<string, unknown>;\n const cfg = ctx.toolConfigLookup?.(call.name);\n\n // The answer a resumed run was handed for this call, if any.\n const answer = ctx.deferral?.outcomes?.[call.id];\n // A refusal it carries settles the call, whatever the call is: the person's decision on a\n // gated one, and on any other the page refusing an earlier call of the same pause, which\n // ends the calls after it there as in a run in the page. Refusing runs nothing, so a\n // missing handler or a spent turn limit has nothing to stop — checked before them, the\n // refusal still reaches the model, and the model still takes its turn.\n if (answer?.kind === 'decision' && !answer.approved) return refuse(ctx, call, answer);\n\n // Per-tool maxTurns — the same arithmetic as the global cap (`>`), so one\n // number means one thing on both knobs. It was `>=`: `maxTurns: 1` on a\n // tool made it un-callable on the very first turn, and `maxTurns: N`\n // allowed N-1 calls against the global option's N.\n const effectiveMaxTurns = cfg?.maxTurns ?? ctx.maxTurns;\n if (ctx.turns > effectiveMaxTurns) {\n ctx.emitter({ type: 'turn-limit-exceeded', scope: 'tool', limit: effectiveMaxTurns, toolCallId: call.id });\n return 'stop-run';\n }\n\n // Known to the model, run elsewhere: its result comes back in the answer, and\n // until it does the call waits. Before the handler lookup, because there is no\n // handler here by definition — without `deferral` it is aborted like a missing one.\n // The page gates a browser tool as it would in its own loop (its `needsApproval`,\n // its policy), so an approval is no result yet, and a refusal was answered above.\n if (cfg?.external) {\n if (!ctx.deferral) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n }\n if (!answer || answer.kind === 'decision') return { defer: 'external' };\n if (answer.kind === 'failed') {\n // Like a crashing handler: the row hears it, then the run ends on it.\n ctx.emitter({ type: 'tool-failed', toolCallId: call.id, error: answer.error });\n throw new Error(answer.error);\n }\n ctx.emitter({ type: 'tool-resolved', toolCallId: call.id, result: answer.content, structuredResult: answer.structuredContent });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: answer.content, toolCallId: call.id, toolName: call.name });\n return 'continue';\n }\n\n const handler = ctx.toolLookup(call.name);\n if (!handler) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n }\n\n let effectiveInput = call.input;\n\n // ── HITL approval gate ────────────────────────────────────────\n const gated = typeof cfg?.needsApproval === 'function'\n ? cfg.needsApproval({ id: call.id, name: call.name, input })\n : cfg?.needsApproval;\n if (gated) {\n // A decision a resumed run was handed answers the gate: nobody is asked again.\n // Never a `'deny'`: that one is decided here, and an answer that came back with\n // the resume — the page's, for a call queued behind a pause — cannot overrule it.\n const provided = answer?.kind === 'decision' && gated !== 'deny' ? answer : undefined;\n // Announced only when someone is actually being asked: a `'deny'` is a\n // decision already made, and announcing a pause for it painted\n // `awaiting-approval` on the row and raised `aparte-tool-approval-request`\n // for a call nobody was ever going to be asked about.\n if (gated !== 'deny' && !provided) {\n ctx.emitter({ type: 'tool-awaiting-approval', toolCallId: call.id, name: call.name, input: call.input });\n }\n // Nobody here can be asked (the run is not in the page): the call waits for a\n // decision a later run will carry. A `'deny'` is never deferred — it is\n // already decided, and refused below as without `deferral`.\n if (!provided && ctx.deferral && gated !== 'deny') return { defer: 'approval' };\n /*\n * Nothing can ask, so nothing may answer.\n *\n * The default was `async () => ({ approved: false })`, which sent the\n * loop into the refusal branch and appended \"rejected by the user\" —\n * a host that forgot to wire a resolver had not refused anything, and\n * the model was told a person had. A misconfiguration is an aborted\n * call, not a decision.\n */\n if (!provided && !ctx.approvalResolver) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n }\n /*\n * A stop is not a refusal either. Core's built-in channel resolves\n * `{ approved: false }` on abort, indistinguishable by value from an\n * explicit Reject — so the signal is what tells them apart. No\n * `tool` message: an aborted call has nothing true to tell the model.\n *\n * Checked on three sides of the wait, because the stop can land on\n * any of them: while the gate was being shown (a host that reacts to\n * `awaiting-approval` synchronously — the panel's own Stop button),\n * inside the resolver (a channel that rejects with an AbortError\n * rather than resolving), or after it resolved. The first and second\n * used to fall through to the run-level abort, which never marked the\n * call: the segment stayed `awaiting-approval` for good.\n */\n let decision: Awaited<ReturnType<StreamApprovalResolver>> | undefined = provided;\n if (!decision && !signal.aborted && ctx.approvalResolver) {\n try {\n decision = await ctx.approvalResolver(\n { id: call.id, name: call.name, input },\n signal,\n );\n } catch (err) {\n if (!signal.aborted && (err as { name?: string } | undefined)?.name !== 'AbortError') throw err;\n }\n }\n if (signal.aborted || !decision) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n }\n\n if (!decision.approved) {\n /*\n * The user's own words when they gave them, and the fixed sentence\n * otherwise — byte-identical to core's twin, because this is the\n * only thing the model gets to read about the refusal.\n *\n * This branch used to hardcode the generic sentence. The resolver\n * type declared `instruction` and the whole loop never referenced\n * it, so on the engine path — the recommended one — a user who\n * refused and typed \"use the staging bucket instead\" had those\n * words dropped before the model saw them. Handing the model a\n * turn after a refusal exists SO THAT it reads the refusal; here\n * there was nothing to read.\n *\n * The parity suite could not see it: its resolver returned\n * `{ approved }` and never an instruction, so both loops agreed on\n * a case neither exercised. It supplies one now.\n */\n refuse(ctx, call, decision);\n /*\n * `'stop-turn'`, not `'stop-run'`, and the asymmetry is the point.\n * The remaining tool calls of this turn must not run — the\n * model asked for several and refusing one cannot license the\n * others — but the loop takes another turn, so the sentence just\n * appended actually reaches the model. It never did: the turn\n * ended here, and telling the assistant what you wanted instead\n * meant retyping it as a new message it read out of order.\n *\n * Core's twin returns `'respond'` for exactly this, and the parity\n * suite asserts the two agree.\n */\n return 'stop-turn';\n }\n if (decision.payload && typeof decision.payload === 'object' && !Array.isArray(decision.payload)) {\n effectiveInput = { ...input, ...(decision.payload as Record<string, unknown>) };\n }\n ctx.emitter({ type: 'tool-approved', toolCallId: call.id });\n }\n\n // ── handler invocation with per-call timeout ──────────────────\n const outcome = await invokeToolHandler(\n handler,\n { id: call.id, name: call.name, input: effectiveInput },\n signal,\n ctx.toolTimeoutMs,\n );\n if (outcome.status === 'aborted') {\n // Same rule as the rejection / turn-limit / missing-handler\n // exits above, which this branch was missing: a stopped turn\n // must not go on running the tool calls that follow it.\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n } else if (outcome.status === 'failed') {\n // A crash is the handler's, and the run ends on it as before —\n // but the row hears about it first, instead of spinning for good.\n ctx.emitter({ type: 'tool-failed', toolCallId: call.id, error: errorText(outcome.error) });\n throw outcome.error;\n } else {\n ctx.emitter({ type: 'tool-resolved', toolCallId: call.id, result: outcome.content, structuredResult: outcome.structuredContent });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: outcome.content, toolCallId: call.id, toolName: call.name });\n }\n return 'continue';\n}\n\nasync function invokeToolHandler(\n handler: StreamToolHandler,\n call: StreamToolCall,\n signal: AbortSignal,\n toolTimeoutMs: number,\n): Promise<\n | { status: 'resolved'; content: string; structuredContent?: unknown }\n | { status: 'aborted' }\n /** The handler threw something that is not an abort: a crash, reported by the caller then re-thrown. */\n | { status: 'failed'; error: unknown }\n> {\n // If the run was already aborted before we got here, don't invoke the\n // handler at all: a past 'abort' event will never re-fire on the listener\n // below, so the handler would otherwise run to completion despite cancel.\n if (signal.aborted) return { status: 'aborted' };\n\n const controller = new AbortController();\n const onParentAbort = () => controller.abort();\n signal.addEventListener('abort', onParentAbort, { once: true });\n\n /*\n * The parent abort is RACED for the same reason the timeout is.\n *\n * `onParentAbort` above only forwards the Stop to the child controller, and a\n * deaf handler ignores that exactly as it ignores the timeout's. So a Stop\n * pressed during a tool call unwound nothing: the loop stayed parked on the\n * handler until `toolTimeoutMs` expired — five minutes by default — with the\n * typing indicator still up and the stop button already pressed. The signal is\n * still fired first, so a handler that honours it gets to reject on its own\n * terms; this racer is what makes the Stop true when it does not.\n */\n let onParentAbortRace: (() => void) | undefined;\n const parentAborted = new Promise<{ status: 'aborted' }>((resolve) => {\n onParentAbortRace = () => resolve({ status: 'aborted' });\n signal.addEventListener('abort', onParentAbortRace, { once: true });\n });\n\n /*\n * RACED, not just signalled.\n *\n * Aborting the controller is a request the handler is free to ignore, and the\n * default shape of a consumer tool ignores it —\n * `async () => ({ content: await fetch(...).then(r => r.text()) })` never reads\n * its signal. So the timeout fired, nothing rejected, and the loop waited\n * forever on an option whose JSDoc promises a timeout.\n *\n * The signal still fires first, because a handler that DOES honour it should\n * get the chance to clean up and reject on its own terms; the race is what\n * makes the promise true when it does not.\n */\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<{ status: 'aborted' }>((resolve) => {\n timeout = setTimeout(() => {\n controller.abort();\n resolve({ status: 'aborted' });\n }, toolTimeoutMs);\n });\n\n try {\n const result = await Promise.race([\n handler(call, controller.signal).then((r) => ({ status: 'resolved' as const, content: r.content, structuredContent: r.structuredContent })),\n timedOut,\n parentAborted,\n ]);\n return result;\n } catch (err: unknown) {\n if ((err as { name?: string })?.name === 'AbortError') return { status: 'aborted' };\n // Not thrown from here: the call was announced with `tool-start`, and the\n // loop's invariant is that an announced call gets exactly one terminal\n // event. Throwing from inside skipped it — the row spun \"Running\" for good\n // while the message showed an error card. The caller reports, then throws.\n return { status: 'failed', error: err };\n } finally {\n clearTimeout(timeout);\n signal.removeEventListener('abort', onParentAbort);\n if (onParentAbortRace) signal.removeEventListener('abort', onParentAbortRace);\n }\n}\n\n/** The one-line text of a handler's crash, for the `tool-failed` event and the row. */\nfunction errorText(error: unknown): string {\n if (error instanceof Error) return error.message || error.name;\n return typeof error === 'string' ? error : String(error);\n}\n"],"names":[],"mappings":"AAsCA,MAAM,0BAA0B,IAAI,KAAK;AAEzC,MAAM,oBAAoB;AA2F1B,MAAM,0CAA0B,IAAA;AAChC,SAAS,uBAAuB,OAAsB;AAClD,QAAM,OAAO,OAAQ,OAA8B,QAAQ,WAAW;AACtE,MAAI,oBAAoB,IAAI,IAAI,EAAG;AACnC,sBAAoB,IAAI,IAAI;AAC5B,UAAQ;AAAA,IACJ,wDAAwD,IAAI;AAAA,EAAA;AAIpE;AAiBA,eAAsB,eAAe,MAA0D;AAC3F,QAAM;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB;AAAA,EAAA,IACA;AAKJ,MAAI,cAAc,KAAK;AAEvB,MAAI,QAAQ;AACZ,QAAM,QAAQ,KAAK,UAAU,CAAC,WAAmB,GAAG,MAAM,IAAI,OAAO;AAKrE,QAAM,WAAiC,CAAC,GAAG,YAAY,QAAQ;AAC/D,QAAM,SAAS,CAAC,YAAsC;AAClD,aAAS,KAAK,OAAO;AACrB,SAAK,kBAAkB,OAAO;AAAA,EAClC;AAEA,MAAI,eAAe;AACnB,MAAI,QAAQ;AACZ,MAAI;AAGJ,UAAQ,EAAE,MAAM,aAAa;AAM7B,QAAM,OAAO,WAAW,eAAe,QAAQ,IAAI,CAAA;AACnD,MAAI,KAAK,SAAS,GAAG;AACjB,UAAM,eAAqC,CAAA;AAC3C,UAAM,MAAmB,EAAE,OAAO,GAAG,UAAU,YAAY,kBAAkB,kBAAkB,SAAS,QAAQ,eAAe,aAAa,MAAM;AAAA,IAAC,GAAG,QAAQ,SAAA;AAC9J,eAAW,CAAC,GAAG,IAAI,KAAK,KAAK,WAAW;AACpC,UAAI,aAAa,SAAS,GAAG;AACzB,qBAAa,KAAK,EAAE,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,QAAQ,aAAa,MAAM,gBAAgB,GAAG;AAC3H;AAAA,MACJ;AACA,UAAI;AACJ,UAAI;AACA,kBAAU,MAAM,YAAY,MAAM,GAAG;AAAA,MACzC,SAAS,OAAO;AAGZ,mBAAW,QAAQ,KAAK,MAAM,IAAI,CAAC,EAAG,SAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AAC3F,cAAM;AAAA,MACV;AACA,UAAI,OAAO,YAAY,UAAU;AAC7B,qBAAa,KAAK,EAAE,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,OAAO;AACpG;AAAA,MACJ;AAIA,UAAI,YAAY,YAAY;AAGxB,mBAAW,QAAQ,KAAK,MAAM,IAAI,CAAC,EAAG,SAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AAC3F,uBAAe;AACf;AAAA,MACJ;AAAA,IACJ;AACA,QAAI,aAAa,SAAS,KAAK,CAAC,OAAO,SAAS;AAC5C,cAAQ,EAAE,MAAM,iBAAiB,SAAS,cAAc;AACxD,aAAO;AAAA,IACX;AAAA,EACJ;AAGA,SAAO,cAAc;AAGjB,QAAI,OAAO,QAAS;AAEpB;AACA,QAAI,QAAQ,UAAU;AAClB,cAAQ,EAAE,MAAM,uBAAuB,OAAO,UAAU,OAAO,UAAU;AACzE;AAAA,IACJ;AAUA,UAAM,aAAa,YAAY,YAAY;AAC3C,QACI,UAAU,KACV,cACA,OAAO,eAAe,YACtB,CAAC,MAAM,QAAQ,UAAU,KACxB,WAAmC,UAAU,QAChD;AAGE,YAAM,KAAK;AACX,YAAM,cAAc,MAAM,gBAAgB;AAC1C,cAAQ,EAAE,MAAM,cAAc,YAAY,aAAa,MAAM,GAAG,MAAM,OAAO,GAAG,MAAA,CAAO;AAEvF,YAAM,UAAU,WAAW,GAAG,IAAI;AAClC,UAAI,CAAC,SAAS;AACV,gBAAQ,EAAE,MAAM,gBAAgB,YAAY,aAAa;AACzD,uBAAe;AACf;AAAA,MACJ;AAEA,YAAM,UAAU,MAAM,kBAAkB,SAAS,EAAE,IAAI,aAAa,MAAM,GAAG,MAAM,OAAO,GAAG,MAAA,GAAS,QAAQ,aAAa;AAC3H,UAAI,QAAQ,WAAW,WAAW;AAC9B,gBAAQ,EAAE,MAAM,gBAAgB,YAAY,aAAa;AACzD,uBAAe;AACf;AAAA,MACJ;AACA,UAAI,QAAQ,WAAW,UAAU;AAC7B,gBAAQ,EAAE,MAAM,eAAe,YAAY,aAAa,OAAO,UAAU,QAAQ,KAAK,GAAG;AACzF,cAAM,QAAQ;AAAA,MAClB;AACA,cAAQ,EAAE,MAAM,iBAAiB,YAAY,aAAa,QAAQ,QAAQ,SAAS,kBAAkB,QAAQ,kBAAA,CAAmB;AAChI,aAAO,EAAE,MAAM,aAAa,SAAS,IAAI,WAAW,CAAC,EAAE,IAAI,aAAa,MAAM,GAAG,MAAM,OAAO,GAAG,MAAA,CAAO,GAAG;AAC3G,aAAO,EAAE,MAAM,QAAQ,SAAS,QAAQ,SAAS,YAAY,aAAa,UAAU,GAAG,KAAA,CAAM;AAC7F,oBAAc,EAAE,GAAG,aAAa,YAAY,QAAQ,OAAO,OAAA;AAAA,IAC/D;AASA,UAAM,UAA6B,EAAE,GAAG,aAAa,SAAA;AAUrD,UAAM,SAAS,YAAY,YAAY;AACvC,QAAI,UAAU,OAAO,WAAW,wBAAwB,EAAE,GAAG,aAAa,YAAY,OAAA;AACtF,QAAI;AACJ,QAAI;AACA,iBAAW,MAAM,cAAc,OAAO;AAAA,IAC1C,SAAS,KAAc;AACnB,UAAI,OAAO,WAAY,KAAuC,SAAS,cAAc;AAQjF,uBAAe;AACf;AAAA,MACJ;AACA,YAAM;AAAA,IACV;AAcA,QAAI,OAAO,aAAa,UAAU;AAC9B,cAAQ,EAAE,MAAM,cAAc,OAAO,UAAU;AAC/C,cAAQ,EAAE,MAAM,cAAc;AAC9B;AAAA,IACJ;AAKA,YAAQ,EAAE,MAAM,cAAc;AAG9B,QAAI,gBAAgB;AACpB,UAAM,oBAAsC,CAAA;AAG5C,QAAI,aAAa;AAcjB,QAAI,WAAsC;AAU1C,UAAM,cAAc,CAAC,SAA+B;AAChD,wBAAkB,KAAK,IAAI;AAC3B,UAAI,SAAU;AACd,iBAAW,EAAE,MAAM,aAAa,SAAS,cAAc,KAAA,GAAQ,WAAW,kBAAA;AAC1E,aAAO,QAAQ;AAAA,IACnB;AAUA,UAAM,mBAAmB,MAAY;AACjC,UAAI,SAAU,UAAS,UAAU,cAAc,KAAA;AAAA,IACnD;AASA,UAAM,kCAAkB,IAAA;AACxB,UAAM,eAAe,CAAC,QAAwB;AAC1C,UAAI,KAAK;AACT,eAAS,IAAI,GAAG,YAAY,IAAI,EAAE,GAAG,IAAK,MAAK,GAAG,GAAG,IAAI,CAAC;AAC1D,kBAAY,IAAI,EAAE;AAClB,aAAO;AAAA,IACX;AAWA,UAAM,UAAgC,CAAA;AAItC,UAAM,cAA2B;AAAA,MAC7B;AAAA,MAAO;AAAA,MAAU;AAAA,MAAY;AAAA,MAAkB;AAAA,MAAkB;AAAA,MAAS;AAAA,MAAQ;AAAA,MAAe;AAAA,MAAa;AAAA,MAC9G,UAAU,YAAY,EAAE,GAAG,UAAU,UAAU,OAAA;AAAA,IAAU;AAG7D,UAAM,WAAW,SAAS,OAAO,aAAa,EAAA;AAC9C,UAAM,cAAc,YAA8B;AAC9C,UAAI,CAAC,OAAO,QAAS,QAAO;AAM5B,UAAI;AAAE,cAAM,SAAS,SAAS,MAAS;AAAA,MAAG,QAAQ;AAAA,MAAoB;AACtE,qBAAe;AACf,aAAO;AAAA,IACX;AACA,QAAI;AACA,aAAO,MAAM;AACT,YAAI,MAAM,cAAe;AAEzB,cAAM,OAAO,MAAM,SAAS,KAAA;AAC5B,YAAI,MAAM,cAAe;AACzB,YAAI,KAAK,KAAM;AACf,cAAM,QAAQ,KAAK;AAEnB,YAAI,MAAM,SAAS,YAAY;AAC3B,kBAAQ,EAAE,MAAM,kBAAkB,OAAO,MAAM,OAAO;AACtD;AAAA,QACJ;AAEA,YAAI,MAAM,SAAS,QAAQ;AACvB,2BAAiB,MAAM;AACvB,kBAAQ,EAAE,MAAM,cAAc,OAAO,MAAM,OAAO;AAClD;AAAA,QACJ;AAEA,YAAI,MAAM,SAAS,QAAQ;AACvB,cAAI,MAAM,MAAO,aAAY,MAAM;AACnC;AAAA,QACJ;AAEA,YAAI,MAAM,SAAS,SAAS;AAExB,gBAAM,IAAI,MAAM,MAAM,OAAO;AAAA,QACjC;AAcA,YAAI,MAAM,SAAS,YAAY;AAC3B,iCAAuB,KAAK;AAC5B;AAAA,QACJ;AACA,qBAAa;AAIb,cAAM,SAAS,aAAa,MAAM,EAAE;AAOpC,gBAAQ,EAAE,MAAM,cAAc,YAAY,QAAQ,MAAM,MAAM,MAAM,OAAO,MAAM,MAAA,CAAO;AAExF,cAAM,OAAuB,EAAE,IAAI,QAAQ,MAAM,MAAM,MAAM,OAAO,MAAM,MAAA;AAC1E,YAAI,QAAQ,SAAS,GAAG;AAIpB,sBAAY,IAAI;AAChB,gBAAM,SAAS,aAAa,MAAM,gBAAgB;AAClD,cAAI,WAAW,YAAY;AACvB,oBAAQ,EAAE,MAAM,0BAA0B,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,MAAA,CAAO;AAAA,UACvG;AACA,kBAAQ,KAAK,EAAE,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,OAAA,CAAQ;AAChF;AAAA,QACJ;AACA,cAAM,UAAU,MAAM,YAAY,MAAM,WAAW;AACnD,YAAI,OAAO,YAAY,UAAU;AAC7B,sBAAY,IAAI;AAChB,kBAAQ,KAAK,EAAE,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO,QAAQ,QAAQ,OAAO;AAC/F;AAAA,QACJ;AACA,YAAI,YAAY,YAAY;AAAE,yBAAe;AAAO;AAAA,QAAO;AAE3D,YAAI,YAAY,YAAa;AAAA,MACjC;AAMA,cAAQ,EAAE,MAAM,cAAc;AAG9B,uBAAA;AAAA,IAEJ,UAAA;AAGI,UAAI;AAAE,cAAM,SAAS,SAAS,MAAS;AAAA,MAAG,QAAQ;AAAA,MAAoB;AAAA,IAC1E;AAIA,QAAI,QAAQ,SAAS,KAAK,CAAC,OAAO,SAAS;AACvC,cAAQ,EAAE,MAAM,iBAAiB,SAAS,OAAO,WAAW;AAC5D,aAAO;AAAA,IACX;AAGA,QAAI,CAAC,WAAY,gBAAe;AAAA,EACpC;AAaA,MAAI,OAAO,QAAS,SAAQ,EAAE,MAAM,eAAe;AACnD,UAAQ,EAAE,MAAM,YAAY,OAAO,WAAW;AAC9C,SAAO;AACX;AAeO,SAAS,eAAe,UAA2D;AACtF,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC3C,UAAM,IAAI,SAAS,CAAC;AACpB,QAAI,EAAE,SAAS,aAAa;AACxB,UAAI,CAAC,EAAE,WAAW,eAAe,CAAA;AACjC,YAAM,WAAW,IAAI,IAAI,SAAS,MAAM,IAAI,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC;AACxG,aAAO,EAAE,UAAU,OAAO,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;AAAA,IACxD;AACA,QAAI,EAAE,SAAS,OAAQ,QAAO,CAAA;AAAA,EAClC;AACA,SAAO,CAAA;AACX;AAGA,SAAS,aAAa,MAAsB,QAAoE;AAC5G,QAAM,MAAM,SAAS,KAAK,IAAI;AAC9B,MAAI,KAAK,SAAU,QAAO;AAC1B,QAAM,QAAQ,OAAO,KAAK,kBAAkB,aACtC,IAAI,cAAc,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,MAAA,CAAkC,IAChG,KAAK;AACX,SAAO,SAAS,UAAU,SAAS,aAAa;AACpD;AAyBA,SAAS,OACL,KACA,MACA,UACW;AAGX,QAAM,YAAY,SAAS,QAAQ,KAAA,MAC3B,SAAS,cACP,8CAA8C,SAAS,WAAW,KAClE;AACV,MAAI,QAAQ,EAAE,MAAM,iBAAiB,YAAY,KAAK,IAAI,QAAQ,WAAW;AAC7E,MAAI,YAAY,IAAI;AACpB,MAAI,OAAO,EAAE,MAAM,QAAQ,SAAS,WAAW,YAAY,KAAK,IAAI,UAAU,KAAK,KAAA,CAAM;AACzF,SAAO;AACX;AAOA,eAAe,YAAY,MAAsB,KAAwC;AACrF,QAAM,EAAE,WAAW;AACnB,QAAM,QAAQ,KAAK;AACnB,QAAM,MAAM,IAAI,mBAAmB,KAAK,IAAI;AAG5C,QAAM,SAAS,IAAI,UAAU,WAAW,KAAK,EAAE;AAM/C,MAAI,QAAQ,SAAS,cAAc,CAAC,OAAO,SAAU,QAAO,OAAO,KAAK,MAAM,MAAM;AAMpF,QAAM,oBAAoB,KAAK,YAAY,IAAI;AAC/C,MAAI,IAAI,QAAQ,mBAAmB;AAC/B,QAAI,QAAQ,EAAE,MAAM,uBAAuB,OAAO,QAAQ,OAAO,mBAAmB,YAAY,KAAK,GAAA,CAAI;AACzG,WAAO;AAAA,EACX;AAOA,MAAI,KAAK,UAAU;AACf,QAAI,CAAC,IAAI,UAAU;AACf,UAAI,QAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AACzD,aAAO;AAAA,IACX;AACA,QAAI,CAAC,UAAU,OAAO,SAAS,WAAY,QAAO,EAAE,OAAO,WAAA;AAC3D,QAAI,OAAO,SAAS,UAAU;AAE1B,UAAI,QAAQ,EAAE,MAAM,eAAe,YAAY,KAAK,IAAI,OAAO,OAAO,MAAA,CAAO;AAC7E,YAAM,IAAI,MAAM,OAAO,KAAK;AAAA,IAChC;AACA,QAAI,QAAQ,EAAE,MAAM,iBAAiB,YAAY,KAAK,IAAI,QAAQ,OAAO,SAAS,kBAAkB,OAAO,mBAAmB;AAC9H,QAAI,YAAY,IAAI;AACpB,QAAI,OAAO,EAAE,MAAM,QAAQ,SAAS,OAAO,SAAS,YAAY,KAAK,IAAI,UAAU,KAAK,MAAM;AAC9F,WAAO;AAAA,EACX;AAEA,QAAM,UAAU,IAAI,WAAW,KAAK,IAAI;AACxC,MAAI,CAAC,SAAS;AACV,QAAI,QAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AACzD,WAAO;AAAA,EACX;AAEA,MAAI,iBAAiB,KAAK;AAG1B,QAAM,QAAQ,OAAO,KAAK,kBAAkB,aACtC,IAAI,cAAc,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,MAAM,MAAA,CAAO,IACzD,KAAK;AACX,MAAI,OAAO;AAIP,UAAM,WAAW,QAAQ,SAAS,cAAc,UAAU,SAAS,SAAS;AAK5E,QAAI,UAAU,UAAU,CAAC,UAAU;AAC/B,UAAI,QAAQ,EAAE,MAAM,0BAA0B,YAAY,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,KAAK,OAAO;AAAA,IAC3G;AAIA,QAAI,CAAC,YAAY,IAAI,YAAY,UAAU,OAAQ,QAAO,EAAE,OAAO,WAAA;AAUnE,QAAI,CAAC,YAAY,CAAC,IAAI,kBAAkB;AACpC,UAAI,QAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AACzD,aAAO;AAAA,IACX;AAeA,QAAI,WAAoE;AACxE,QAAI,CAAC,YAAY,CAAC,OAAO,WAAW,IAAI,kBAAkB;AACtD,UAAI;AACA,mBAAW,MAAM,IAAI;AAAA,UACjB,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,MAAM,MAAA;AAAA,UAChC;AAAA,QAAA;AAAA,MAER,SAAS,KAAK;AACV,YAAI,CAAC,OAAO,WAAY,KAAuC,SAAS,aAAc,OAAM;AAAA,MAChG;AAAA,IACJ;AACA,QAAI,OAAO,WAAW,CAAC,UAAU;AAC7B,UAAI,QAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AACzD,aAAO;AAAA,IACX;AAEA,QAAI,CAAC,SAAS,UAAU;AAkBpB,aAAO,KAAK,MAAM,QAAQ;AAa1B,aAAO;AAAA,IACX;AACA,QAAI,SAAS,WAAW,OAAO,SAAS,YAAY,YAAY,CAAC,MAAM,QAAQ,SAAS,OAAO,GAAG;AAC9F,uBAAiB,EAAE,GAAG,OAAO,GAAI,SAAS,QAAA;AAAA,IAC9C;AACA,QAAI,QAAQ,EAAE,MAAM,iBAAiB,YAAY,KAAK,IAAI;AAAA,EAC9D;AAGA,QAAM,UAAU,MAAM;AAAA,IAClB;AAAA,IACA,EAAE,IAAI,KAAK,IAAI,MAAM,KAAK,MAAM,OAAO,eAAA;AAAA,IACvC;AAAA,IACA,IAAI;AAAA,EAAA;AAER,MAAI,QAAQ,WAAW,WAAW;AAI9B,QAAI,QAAQ,EAAE,MAAM,gBAAgB,YAAY,KAAK,IAAI;AACzD,WAAO;AAAA,EACX,WAAW,QAAQ,WAAW,UAAU;AAGpC,QAAI,QAAQ,EAAE,MAAM,eAAe,YAAY,KAAK,IAAI,OAAO,UAAU,QAAQ,KAAK,EAAA,CAAG;AACzF,UAAM,QAAQ;AAAA,EAClB,OAAO;AACH,QAAI,QAAQ,EAAE,MAAM,iBAAiB,YAAY,KAAK,IAAI,QAAQ,QAAQ,SAAS,kBAAkB,QAAQ,mBAAmB;AAChI,QAAI,YAAY,IAAI;AACpB,QAAI,OAAO,EAAE,MAAM,QAAQ,SAAS,QAAQ,SAAS,YAAY,KAAK,IAAI,UAAU,KAAK,MAAM;AAAA,EACnG;AACA,SAAO;AACX;AAEA,eAAe,kBACX,SACA,MACA,QACA,eAMF;AAIE,MAAI,OAAO,QAAS,QAAO,EAAE,QAAQ,UAAA;AAErC,QAAM,aAAa,IAAI,gBAAA;AACvB,QAAM,gBAAgB,MAAM,WAAW,MAAA;AACvC,SAAO,iBAAiB,SAAS,eAAe,EAAE,MAAM,MAAM;AAa9D,MAAI;AACJ,QAAM,gBAAgB,IAAI,QAA+B,CAAC,YAAY;AAClE,wBAAoB,MAAM,QAAQ,EAAE,QAAQ,WAAW;AACvD,WAAO,iBAAiB,SAAS,mBAAmB,EAAE,MAAM,MAAM;AAAA,EACtE,CAAC;AAeD,MAAI;AACJ,QAAM,WAAW,IAAI,QAA+B,CAAC,YAAY;AAC7D,cAAU,WAAW,MAAM;AACvB,iBAAW,MAAA;AACX,cAAQ,EAAE,QAAQ,WAAW;AAAA,IACjC,GAAG,aAAa;AAAA,EACpB,CAAC;AAED,MAAI;AACA,UAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,MAC9B,QAAQ,MAAM,WAAW,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,QAAQ,YAAqB,SAAS,EAAE,SAAS,mBAAmB,EAAE,oBAAoB;AAAA,MAC1I;AAAA,MACA;AAAA,IAAA,CACH;AACD,WAAO;AAAA,EACX,SAAS,KAAc;AACnB,QAAK,KAA2B,SAAS,aAAc,QAAO,EAAE,QAAQ,UAAA;AAKxE,WAAO,EAAE,QAAQ,UAAU,OAAO,IAAA;AAAA,EACtC,UAAA;AACI,iBAAa,OAAO;AACpB,WAAO,oBAAoB,SAAS,aAAa;AACjD,QAAI,kBAAmB,QAAO,oBAAoB,SAAS,iBAAiB;AAAA,EAChF;AACJ;AAGA,SAAS,UAAU,OAAwB;AACvC,MAAI,iBAAiB,MAAO,QAAO,MAAM,WAAW,MAAM;AAC1D,SAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAC3D;"}
1
+ {"version":3,"file":"index.js","sources":["../src/agent/stream-run.ts"],"sourcesContent":["/**\n * stream-run.ts — framework-free structured-stream agent loop.\n *\n * The agent loop — extracted from core's inline `_streamLoop`, which it has since\n * replaced (audit 2026-08-28, D1): a `while(tool_use)` loop\n * that consumes a structured `AsyncIterable<StreamChatEvent>` from a transport,\n * runs approved tools, feeds their results back into the history, and re-calls\n * the transport until the model stops asking for tools. It performs **no DOM\n * work** — it emits {@link StreamRunEvent}s (in the inline loop's exact order) that\n * an adapter in `@aparte/core` turns into `targetElement.*` calls.\n *\n * Formerly the parity target of core's inline loop; now the one loop, its call\n * sequences pinned by core's `stream-parity` snapshots.\n * Scope: text · thinking · tool_use (+ HITL) · done · error · synthetic toolChoice\n * bypass. Tagged blocks in the text (`<artifact>`, `<cite>`…) are adapter-side: core's\n * parser reads the grammars registered on the config. The built-in `create_artifact`\n * left with the artifact (D7) — it is a registered tool now. The raw / XML artifact\n * modes and the multi-phase pipeline were removed (audit 2026-08-28, D2) — orchestration is the\n * product's, and a mode nothing in this repository emitted was a contract\n * maintained for nobody.\n */\n\nimport type {\n StreamRunEmitter,\n StreamChatEvent,\n StreamChatRequest,\n StreamAgentMessage,\n StreamToolCall,\n StreamToolHandler,\n StreamToolConfig,\n StreamApprovalResolver,\n StreamUsage,\n StreamDeferral,\n StreamDeferredCall,\n StreamDeferredReason,\n StreamRefusalOrigin,\n StreamAbortCause,\n} from './stream-events.js';\n\n/** Default per-tool-call handler timeout — mirrors `TOOL_HANDLER_TIMEOUT_MS`. */\nconst DEFAULT_TOOL_TIMEOUT_MS = 5 * 60 * 1000;\n/** Default global turn cap — mirrors `AparteClientOptions.maxTurns ?? 10`. */\nconst DEFAULT_MAX_TURNS = 10;\n\nexport interface StreamRunOptions {\n /** Id of the assistant message being streamed (opaque; carried in events). */\n messageId: string;\n /** Turn-1 request; the loop clones its `messages` and enriches them per turn. */\n baseRequest: StreamChatRequest;\n /**\n * Calls the transport with the (possibly enriched) request. Returns the\n * structured stream, or a plain string for a non-streaming provider. Mirrors\n * `getTransport().chat(provider, request, auth, ctx)` with provider/auth/ctx\n * closed over by the adapter.\n */\n transportCall: (request: StreamChatRequest) => Promise<AsyncIterable<StreamChatEvent> | string>;\n /** Resolves a tool's handler by name (mirrors `aparteGlobalConfig.getToolHandler`). */\n toolLookup: (name: string) => StreamToolHandler | undefined;\n /** Resolves a tool's loop config by name (maxTurns / needsApproval). */\n toolConfigLookup?: (name: string) => StreamToolConfig | undefined;\n /** HITL approval resolver for `needsApproval` tools (default: never called). */\n approvalResolver?: StreamApprovalResolver;\n /** Synchronous, ordered event sink consumed by the adapter. */\n emitter: StreamRunEmitter;\n /** Single abort signal composing `_isAborted` + the stream controller. */\n signal: AbortSignal;\n /** Global turn cap. @default 10 */\n maxTurns?: number;\n /** Per-tool-call handler timeout in ms. @default 300000 */\n toolTimeoutMs?: number;\n /**\n * Called for every turn the loop appends to the history — the assistant message\n * carrying the turn's calls, each `tool` message answering one (resolved or\n * rejected), and a pipeline phase's reply — in order, and always before the\n * transport call that would carry it. Never called for the messages you passed\n * in `baseRequest`.\n *\n * For hosts that own their own transcript. The loop re-sends its `messages`\n * array every turn, which suits stateless message APIs but not a **prefix\n * cache** (llama.cpp slots, vLLM), where turn N+1 must EXTEND turn N byte for\n * byte. Such a host builds its own request in {@link transportCall} — ignoring\n * `request.messages` — and mirrors these notifications into its append-only\n * log, instead of reimplementing the loop's tool bookkeeping. Synchronous and\n * ordered, like {@link emitter}.\n *\n * One rule about the envelope: a turn's assistant message is reported ONCE, when\n * its first call completes, and the calls that complete later in the same turn are\n * already in that same object's `toolCalls` — the array is shared by reference, not\n * copied. Hold the reference, not a snapshot, and every result you receive after it\n * is declared by it.\n *\n * Two of its fields are finalised after you are notified, then: `toolCalls` gains\n * the turn's later calls, and `content` is re-read at the turn's end (a provider\n * that emits `[tool, text]` streams the rest of the sentence after the envelope is\n * opened — see `syncEnvelopeText`). A host that must write BYTES at receipt — an\n * append-only log, which cannot hold a reference — should therefore treat the\n * envelope as provisional and write it only once the turn's last `tool` message\n * has arrived, or re-read the object it holds before serialising.\n *\n * `content` is the field that moved here, and the failure is louder than it was:\n * a snapshot no longer loses a trailing clause, it loses the whole sentence the\n * assistant said before it called anything.\n */\n onHistoryAppend?: (message: StreamAgentMessage) => void;\n /**\n * Generates the ids the loop mints itself — today only the synthetic call of a\n * forced `toolChoice` (`prefix` is `'synthetic-tool'`). The default is a\n * deterministic per-run counter; core's client injects a crypto-based one. Tool\n * ids otherwise flow from the stream.\n */\n idGen?: (prefix: string) => string;\n /**\n * Defer instead of block. With this set, a call the gate sends to a person is not\n * awaited on `approvalResolver`, and an `external` tool is not treated as missing:\n * each takes its answer from `outcomes`, and without one the call is DEFERRED —\n * declared on the turn's envelope with no `tool` answer. The rest of the model's\n * reply is still read to its end (its later calls pending too, nothing run after the\n * pause point), and the run ends on `run-suspended` instead of `run-done`.\n *\n * A run whose `baseRequest.messages` ends on an assistant envelope with unanswered\n * calls RESUMES: those calls go through the same `processCall` as a streamed one,\n * in order, before the first transport call. A call the gate denies is never\n * deferred — it is refused, through `approvalResolver`, as without this option.\n */\n deferral?: StreamDeferral;\n /**\n * The caller's provider id. Opaque to the loop, like `messageId`: a runner that\n * forwards the run elsewhere (a server) needs it.\n */\n providerId?: string;\n}\n\n/** Warn once per event type for something this version does not map, then skip it. */\nconst warnedUnknownEvents = new Set<string>();\nfunction warnUnknownStreamEvent(event: unknown): void {\n const type = String((event as { type?: unknown })?.type ?? 'undefined');\n if (warnedUnknownEvents.has(type)) return;\n warnedUnknownEvents.add(type);\n console.warn(\n `[runStreamAgent] Ignoring unrecognised stream event \"${type}\". The reply is`\n + ' unaffected; this usually means the provider or its SDK emits a part this'\n + ' version of aparté does not map yet.',\n );\n}\n\n/**\n * Run the structured-stream agent loop. Resolves the last turn's usage (the\n * `done{usage}` last-write-wins, mirroring `_streamLoop`'s return), or\n * `undefined`. Throws on a stream `error` event or a non-abort tool failure —\n * the caller (adapter) routes that to its lifecycle-error handler, exactly as\n * `_handleSend`/`_handleRetry`/`_handleEdit` catch `_streamLoop`.\n *\n * This block has to stay ADJACENT to the function. It used to sit above\n * `warnUnknownStreamEvent`, six lines and a docblock away from what it describes, and a\n * docblock separated from its declaration is attached to nothing: TypeDoc took the\n * helper's comment as the neighbouring one and `@aparte/engine`'s reference page carried\n * the package's headline export with no description at all — the very export its own\n * frontmatter names. Same trap the custom-elements analyser has with a class docblock\n * pushed above an import.\n */\nexport async function runStreamAgent(opts: StreamRunOptions): Promise<StreamUsage | undefined> {\n const {\n transportCall,\n toolLookup,\n toolConfigLookup,\n approvalResolver,\n emitter,\n signal,\n maxTurns = DEFAULT_MAX_TURNS,\n toolTimeoutMs = DEFAULT_TOOL_TIMEOUT_MS,\n deferral,\n } = opts;\n\n // Reassignable: the synthetic-toolChoice bypass strips `toolChoice`/`tools`\n // from it after the forced turn-1 handler runs (mirrors `_streamLoop`'s\n // `baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined }`).\n let baseRequest = opts.baseRequest;\n\n let idSeq = 0;\n const idGen = opts.idGen ?? ((prefix: string) => `${prefix}-${idSeq++}`);\n\n // Mutable history the loop enriches with the assistant and `tool` turns of a tool round-trip. Every\n // enrichment goes through `append`, so a caller owning its own transcript sees\n // the same turns in the same order (see `onHistoryAppend`).\n const messages: StreamAgentMessage[] = [...baseRequest.messages];\n const append = (message: StreamAgentMessage): void => {\n messages.push(message);\n opts.onHistoryAppend?.(message);\n };\n\n let continueLoop = true;\n let turns = 0;\n let lastUsage: StreamUsage | undefined;\n\n // Leading write before turn 1: mark the message streaming.\n emitter({ type: 'run-start' });\n\n // ── Resume: answer what a previous run left pending, before asking the model ──\n // The envelope is already in `messages` (it came with the request), so a resumed\n // call is declared by it: nothing to declare again, and no `tool-start` either —\n // its row exists, from the run that paused.\n const tail = deferral ? unansweredTail(messages) : [];\n if (tail.length > 0) {\n const stillPending: StreamDeferredCall[] = [];\n const ctx: CallContext = { turns: 1, maxTurns, toolLookup, toolConfigLookup, approvalResolver, emitter, signal, toolTimeoutMs, declareCall: () => {}, append, deferral };\n for (const [i, call] of tail.entries()) {\n if (stillPending.length > 0) {\n stillPending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: queuedReason(call, toolConfigLookup) });\n continue;\n }\n let verdict: CallVerdict;\n try {\n verdict = await processCall(call, ctx);\n } catch (error) {\n // A crash ends the run like a `stop-run` below, and the calls after it\n // have rows too: each is told, then the run fails on the crash.\n for (const rest of tail.slice(i + 1)) emitter({ type: 'tool-aborted', toolCallId: rest.id });\n throw error;\n }\n if (typeof verdict === 'object') {\n stillPending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: verdict.defer });\n continue;\n }\n // Unlike a streamed turn, a refusal does not skip the calls after it: each of\n // them is declared by the envelope, and a call left without a `tool` answer\n // makes the provider reject the next request. Each has its own outcome.\n if (verdict === 'stop-run') {\n // Unlike a streamed turn too, the calls after it have rows already, from\n // the run that paused: each is told it will not run, not left waiting.\n for (const rest of tail.slice(i + 1)) emitter({ type: 'tool-aborted', toolCallId: rest.id });\n continueLoop = false;\n break;\n }\n }\n if (stillPending.length > 0 && !signal.aborted) {\n emitter({ type: 'run-suspended', pending: stillPending });\n return undefined;\n }\n }\n\n // ── outer (turn) loop — one iteration = one transport call ────────────────\n while (continueLoop) {\n // Abort at the top of a turn: no stream open yet, so no cancel needed. The\n // `run-aborted` itself is emitted once, at the loop's exit (see below).\n if (signal.aborted) break;\n\n turns++;\n if (turns > maxTurns) {\n emitter({ type: 'turn-limit-exceeded', scope: 'global', limit: maxTurns });\n break;\n }\n\n // ── Synthetic toolChoice bypass (mirrors _streamLoop) ──────\n // toolChoice = { name, input } (orchestrator-forced): skip the LLM for\n // turn 1, run the handler directly, inject its result as a `tool` message,\n // then strip toolChoice/tools and fall through to the transport call in\n // the SAME turn so the model answers with the tool result already in\n // history. (The adapter's tool-start handler injects renderer CSS here\n // too — a benign, idempotent gain over _streamLoop's synthetic path,\n // which skipped it; reconciled in the parity test.)\n const toolChoice = baseRequest['toolChoice'];\n if (\n turns === 1 &&\n toolChoice &&\n typeof toolChoice === 'object' &&\n !Array.isArray(toolChoice) &&\n (toolChoice as { input?: unknown }).input !== undefined\n ) {\n // Narrowed to the declared shape rather than `input: unknown`: now that\n // `StreamChatRequest.toolChoice` mirrors core's type, the cast can be exact.\n const tc = toolChoice as { name: string; input: Record<string, unknown> };\n const syntheticId = idGen('synthetic-tool');\n emitter({ type: 'tool-start', toolCallId: syntheticId, name: tc.name, input: tc.input });\n\n const handler = toolLookup(tc.name);\n if (!handler) {\n emitter({ type: 'tool-aborted', toolCallId: syntheticId, cause: 'no-handler' });\n continueLoop = false;\n continue;\n }\n\n const outcome = await invokeToolHandler(handler, { id: syntheticId, name: tc.name, input: tc.input }, signal, toolTimeoutMs);\n if (outcome.status === 'aborted') {\n emitter(outcome.cause ? { type: 'tool-aborted', toolCallId: syntheticId, cause: outcome.cause } : { type: 'tool-aborted', toolCallId: syntheticId });\n continueLoop = false;\n continue;\n }\n if (outcome.status === 'failed') {\n emitter({ type: 'tool-failed', toolCallId: syntheticId, error: errorText(outcome.error) });\n throw outcome.error;\n }\n emitter({ type: 'tool-resolved', toolCallId: syntheticId, result: outcome.content, structuredResult: outcome.structuredContent });\n append({ role: 'assistant', content: '', toolCalls: [{ id: syntheticId, name: tc.name, input: tc.input }] });\n append({ role: 'tool', content: outcome.content, toolCallId: syntheticId, toolName: tc.name });\n baseRequest = { ...baseRequest, toolChoice: 'none', tools: undefined };\n }\n\n // A stop that lands before the first event surfaces here as a REJECTION,\n // not as an `error` event — the fetch is aborted while still in flight, so\n // it never reaches the stream the loop reads. Letting it propagate made a\n // deliberate stop indistinguishable from a transport failure for anyone\n // driving this loop directly. (Core's `_streamTurn` has the mirror of this\n // guard; the browser suite is what found the path, after the two\n // event-level guards were already closed.)\n const request: StreamChatRequest = { ...baseRequest, messages };\n /*\n * Forcing a tool is a TURN-1 instruction, and only the loop can lift it: the\n * request is rebuilt from `baseRequest` every turn, so a `toolChoice` object\n * left there was re-sent after the tool had already answered — the model was\n * made to call it again, every turn, until `maxTurns`, and the run ended on\n * `turn-limit-exceeded` instead of a reply. Every object shape, not only the\n * `{ name, input }` the synthetic bypass above strips for itself: `{ name }`\n * alone is what a consumer sets, and it never enters that branch.\n */\n const forced = baseRequest['toolChoice'];\n if (forced && typeof forced === 'object') baseRequest = { ...baseRequest, toolChoice: 'auto' };\n let response: Awaited<ReturnType<typeof transportCall>>;\n try {\n response = await transportCall(request);\n } catch (err: unknown) {\n if (signal.aborted || (err as { name?: string } | undefined)?.name === 'AbortError') {\n // `break`, not `return`: the post-loop `run-done` below is documented\n // as running on EVERY exit path, and five of the six abort paths fall\n // through to it. Returning here skipped it, so a consumer wiring\n // `runStreamAgent` + `createStreamAdapter` directly never got the\n // `completed` status and the bubble stayed flagged streaming — which\n // is verbatim the defect the client-side guard was added to fix.\n // (`run-aborted` is emitted once, at the loop's exit.)\n continueLoop = false;\n break;\n }\n throw err;\n }\n\n // Non-streaming provider: the string IS the full assistant message. The\n // adapter writes it and completes (no done{usage}); spike scenarios\n // always stream, so we finish the run here.\n //\n // `text-flush` is not optional here. It is the adapter's ONLY caller of\n // `parser.finalize()`, and the parser deliberately withholds an ambiguous\n // tail — a lone backtick, a triple backtick, a `<`, or the safe window\n // inside an unterminated fence. Breaking out before the flush dropped all\n // of it, so a non-streaming reply ending on any of those characters lost\n // them and one consisting only of them rendered nothing at all. Core's\n // inline loop does `parse()` AND `finalize()` on its whole-response\n // branch; this is the twin.\n if (typeof response === 'string') {\n emitter({ type: 'text-delta', delta: response });\n emitter({ type: 'text-flush' });\n break;\n }\n\n // New turn: the adapter resets its per-turn parser / thinking / streaming\n // state here (mirrors `_streamLoop` creating a fresh AparteStreamParser +\n // sets each outer iteration). No DOM effect of its own.\n emitter({ type: 'turn-start' });\n\n // Per-turn streaming state.\n let precedingText = '';\n const toolCallsThisTurn: StreamToolCall[] = [];\n // \"Did this turn ask for tools\" — a different question from \"which calls are\n // declared\" (`toolCallsThisTurn`, which now holds only committed ones).\n let sawToolUse = false;\n /**\n * The turn's ONE assistant envelope, held by reference. Created — and\n * appended, so the host is notified once — the first time a call needs it;\n * every later call of the turn is already in the SAME `toolCalls` array, so a\n * `tool` message appended afterwards is always declared by it.\n *\n * This replaces an id scan over `messages` that guessed whether the envelope\n * was already there. The `create_artifact` fast path pushed a fresh envelope\n * of its own; the scan then found the artifact's id in it, concluded \"already\n * pushed\", and the next tool's result went out with no call declaring it —\n * the P0 of the 2026-08-28 audit, a history an Anthropic-shaped API rejects\n * outright. A reference cannot be guessed wrong.\n */\n let envelope: StreamAgentMessage | null = null;\n /*\n * A call is DECLARED in the envelope only once it is committed to producing\n * a `tool` message — at the three sites that append one right after. It used\n * to be pushed the moment the `tool_use` event was read, into the same array\n * the envelope holds by reference, so a call halted before its result (no\n * handler, turn limit, missing resolver, an abort during the wait) still\n * appeared in a history the host was told to hold by reference: an\n * assistant message declaring a call that never gets a result.\n */\n const declareCall = (call: StreamToolCall): void => {\n toolCallsThisTurn.push(call);\n if (envelope) return;\n envelope = { role: 'assistant', content: precedingText.trim(), toolCalls: toolCallsThisTurn };\n append(envelope);\n };\n /*\n * The envelope's text is a SNAPSHOT taken when the first call is declared, and\n * a turn can go on streaming afterwards — a provider that emits `[tool, text]`\n * in that order (`@aparte/provider-scenario` does; openai-compat flushes its\n * calls at the end of the stream and so cannot) had the sentence shown to the\n * user and dropped from the history, where nothing else carries it. Re-read at\n * the turn's end, through the closure: the envelope is held by reference for\n * exactly this reason, the way `toolCalls` is.\n */\n const syncEnvelopeText = (): void => {\n if (envelope) envelope.content = precedingText.trim();\n };\n /*\n * A call id must be unique WITHIN the turn: the transcript keys a row on it\n * (`tool-${id}`, and `updateSegment` takes the first match) and the history\n * files a `tool` message under it. A vendor that omits `id` used to give every\n * call of a turn the same one, so the second call's result was written onto the\n * first call's row and the pair could not be matched at all on the next turn.\n * A repeat is renamed rather than refused — the model asked for both.\n */\n const idsThisTurn = new Set<string>();\n const uniqueCallId = (raw: string): string => {\n let id = raw;\n for (let n = 2; idsThisTurn.has(id); n++) id = `${raw}#${n}`;\n idsThisTurn.add(id);\n return id;\n };\n\n // ── inner (SSE) loop — manual iteration so we can abort around each read.\n // Checked on BOTH sides of the read, and the second check is the one that\n // matters: the loop spends nearly all of its time parked on `next()`, so an\n // abort arriving while parked — the user pressing Stop while watching text\n // stream — is invisible to a check that only runs before the await. It also\n // covers both shapes a provider takes on abort: an `error` event or a quiet\n // close. Without it the `error` branch below throws and the caller reports a\n // deliberate stop as a failure. Mirrors `_streamLoop`'s `bailOnAbort`.\n // The calls this turn leaves for someone else to answer (see `deferral`).\n const pending: StreamDeferredCall[] = [];\n // Without the resume's answers: they were given for the calls a previous run left\n // pending, never for a new call that carries the same id — and a vendor that sends\n // no ids has them minted per stream, so the next turn reuses them.\n const callContext: CallContext = {\n turns, maxTurns, toolLookup, toolConfigLookup, approvalResolver, emitter, signal, toolTimeoutMs, declareCall, append,\n deferral: deferral && { ...deferral, outcomes: undefined },\n };\n\n const iterator = response[Symbol.asyncIterator]();\n const bailOnAbort = async (): Promise<boolean> => {\n if (!signal.aborted) return false;\n // Guarded like the `finally` below: a transport whose iterator throws on\n // the way out turned a deliberate Stop into a thrown run error, and core\n // paints an error card over the reply the user had just stopped. `try`,\n // not `.catch()`: a hand-written iterator throws BEFORE there is a promise\n // to reject, and one that returns a plain object has no `.catch` at all.\n try { await iterator.return?.(undefined); } catch { /* best effort */ }\n continueLoop = false;\n return true;\n };\n try {\n while (true) {\n if (await bailOnAbort()) break;\n\n const step = await iterator.next();\n if (await bailOnAbort()) break;\n if (step.done) break;\n const event = step.value;\n\n if (event.type === 'thinking') {\n emitter({ type: 'thinking-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'text') {\n precedingText += event.delta;\n emitter({ type: 'text-delta', delta: event.delta });\n continue;\n }\n\n if (event.type === 'done') {\n if (event.usage) lastUsage = event.usage;\n continue;\n }\n\n if (event.type === 'error') {\n // Mirror `_streamLoop`: throw; the caller's catch handles it.\n throw new Error(event.message);\n }\n\n // A DISCRIMINANT GUARD, not a comment.\n //\n // This used to be the bare comment `// event.type === 'tool_use'`, so\n // anything the chain above did not recognise fell in here and was\n // treated as a tool call: a `tool-start` / `tool-aborted` pair with an\n // undefined id and name, and — worse — the rest of the stream was\n // never processed, so the reply was truncated too.\n //\n // Core's inline loop had the opposite failure on the same input (it\n // threw and replaced the reply with an error bubble). Both now ignore\n // the event and carry on, which is what forward compatibility with a\n // provider SDK actually requires.\n if (event.type !== 'tool_use') {\n warnUnknownStreamEvent(event);\n continue;\n }\n sawToolUse = true;\n // From here the CALL's id, not the event's: see `uniqueCallId`. The\n // row, the handler, the approval and the history slot must all name\n // the same call, so the rename happens once, before the first emit.\n const callId = uniqueCallId(event.id);\n\n // No built-in tool of any name: `create_artifact` used to be dispatched\n // here, before the tool path, with no handler, no approval and its own\n // result. A tool is a tool — the app registers it (`@aparte/plugin-artifacts`\n // does), and it goes through tool-start, the gate, the handler and the\n // envelope like every other.\n emitter({ type: 'tool-start', toolCallId: callId, name: event.name, input: event.input });\n\n const call: StreamToolCall = { id: callId, name: event.name, input: event.input };\n if (pending.length > 0) {\n // Past the pause point nothing runs. The call is declared, and says what\n // it waits for, so the client answers every call of the reply in ONE\n // round trip rather than one per call.\n declareCall(call);\n const reason = queuedReason(call, toolConfigLookup);\n if (reason === 'approval') {\n emitter({ type: 'tool-awaiting-approval', toolCallId: call.id, name: call.name, input: call.input });\n }\n pending.push({ toolCallId: call.id, name: call.name, input: call.input, reason });\n continue;\n }\n const verdict = await processCall(call, callContext);\n if (typeof verdict === 'object') {\n declareCall(call);\n pending.push({ toolCallId: call.id, name: call.name, input: call.input, reason: verdict.defer });\n continue;\n }\n if (verdict === 'stop-run') { continueLoop = false; break; }\n // A refusal ends this turn's calls but not the run: see `processCall`.\n if (verdict === 'stop-turn') break;\n }\n\n // Turn boundary: finalize the parser (flush residual text). Mirrors\n // `_streamLoop`'s `textParser.finalize()` call — runs on normal\n // end AND abort-break, but NOT after a thrown `error` (which escapes\n // this try before reaching here, exactly like `_streamLoop`).\n emitter({ type: 'text-flush' });\n // Everything this turn streamed belongs to the turn, including what came\n // after the call that opened the envelope.\n syncEnvelopeText();\n\n } finally {\n // Mirror `reader.releaseLock()` in the finally: settle the iterator,\n // however it fails on the way out (see `bailOnAbort`).\n try { await iterator.return?.(undefined); } catch { /* best effort */ }\n }\n\n // Calls left pending: the run ends here, without `run-done` — the turn goes on\n // through the run that resumes them. (A Stop takes the abort exit instead.)\n if (pending.length > 0 && !signal.aborted) {\n emitter({ type: 'run-suspended', pending, usage: lastUsage });\n return lastUsage;\n }\n\n // No tool calls this turn → the final answer.\n if (!sawToolUse) continueLoop = false;\n }\n\n // Post-loop finalization runs on every exit path (normal / abort / maxTurns).\n //\n // `run-aborted` is decided HERE, once, from the signal — not at each site that\n // notices the abort. It used to be emitted at three of the six abort exits\n // (top of turn, transport rejection, mid-stream bail), and the three tool-level\n // ones — a Stop during a handler, during an approval wait, or with no resolver —\n // set `continueLoop = false` and fell through to `run-done` alone: no terminal\n // lifecycle event reached the host, so the typing indicator and the streaming\n // id were never cleared and the turn was never persisted. A tool TIMEOUT aborts\n // only the child controller, so `signal.aborted` stays false and that turn still\n // ends as done, unchanged.\n if (signal.aborted) emitter({ type: 'run-aborted' });\n emitter({ type: 'run-done', usage: lastUsage });\n return lastUsage;\n}\n\n/**\n * Run a tool handler with a per-call timeout, linked to the run's abort signal.\n * Resolves `{ status: 'resolved', content }` on success, or `{ status: 'aborted' }`\n * if the handler aborts (timeout OR parent abort); any other error is re-thrown so\n * the caller routes it to lifecycle-error, exactly as `_streamLoop` does. Shared\n * by the generic tool path and the synthetic-toolChoice bypass (mirrors the\n * AbortController + `TOOL_HANDLER_TIMEOUT_MS` dance both use in `_streamLoop`).\n */\n/**\n * The calls a history leaves unanswered: those of its LAST assistant envelope that no\n * following `tool` message answers, in declaration order. A resumed run answers them\n * first; a server checks them against the ticket it signed when the run paused.\n */\nexport function unansweredTail(messages: readonly StreamAgentMessage[]): StreamToolCall[] {\n for (let i = messages.length - 1; i >= 0; i--) {\n const m = messages[i]!;\n if (m.role === 'assistant') {\n if (!m.toolCalls?.length) return [];\n const answered = new Set(messages.slice(i + 1).filter((x) => x.role === 'tool').map((x) => x.toolCallId));\n return m.toolCalls.filter((c) => !answered.has(c.id));\n }\n if (m.role !== 'tool') return [];\n }\n return [];\n}\n\n/** Why a call after the pause point waits: what the client must answer, or nothing. */\nfunction queuedReason(call: StreamToolCall, lookup: StreamRunOptions['toolConfigLookup']): StreamDeferredReason {\n const cfg = lookup?.(call.name);\n if (cfg?.external) return 'external';\n const gated = typeof cfg?.needsApproval === 'function'\n ? cfg.needsApproval({ id: call.id, name: call.name, input: call.input as Record<string, unknown> })\n : cfg?.needsApproval;\n return gated && gated !== 'deny' ? 'approval' : 'queued';\n}\n\n/** What the caller does after a call: next event, end this turn's stream, end the run. */\ntype CallVerdict = 'continue' | 'stop-turn' | 'stop-run' | { defer: 'approval' | 'external' };\n\ninterface CallContext {\n turns: number;\n maxTurns: number;\n toolLookup: StreamRunOptions['toolLookup'];\n toolConfigLookup: StreamRunOptions['toolConfigLookup'];\n approvalResolver: StreamRunOptions['approvalResolver'];\n emitter: StreamRunEmitter;\n signal: AbortSignal;\n toolTimeoutMs: number;\n /** Declares the call on the turn's envelope. */\n declareCall: (call: StreamToolCall) => void;\n append: (message: StreamAgentMessage) => void;\n deferral: StreamDeferral | undefined;\n}\n\n/**\n * What a refusal says, and to whom. `reason` is the sentence the model reads: the refuser's\n * own when it gave one, else the person's words quoted, else the fixed sentence. `by` and\n * `instruction` are for the row, which tells a person who refused in their own language —\n * a reason with no origin is a policy's, as {@link StreamApprovalResolver} defines it.\n *\n * Exported so a runner that answers an agent elsewhere writes the same sentence.\n */\nexport function toolRefusal(decision: { reason?: string; instruction?: string; by?: StreamRefusalOrigin }): {\n reason: string;\n by: StreamRefusalOrigin;\n instruction?: string;\n} {\n // Truthiness, like `instruction` beside it: an empty `reason` is no reason, not an\n // empty `tool` message.\n const reason = decision.reason?.trim();\n const by = decision.by ?? (reason ? 'policy' : 'person');\n // Without a reason of its own, the sentence follows the origin: a policy's refusal\n // never tells the model \"the user rejected this\".\n const sentence = reason\n || (by === 'policy' ? 'Tool execution was refused by the approval policy.'\n : by === 'earlier-refusal' ? 'Not run: an earlier call of the same step was refused.'\n : decision.instruction\n ? `The user rejected this tool call and said: ${decision.instruction}`\n : 'Tool execution was rejected by the user.');\n return decision.instruction ? { reason: sentence, by, instruction: decision.instruction } : { reason: sentence, by };\n}\n\n/**\n * A refused call: the row says so and the model reads why (see {@link toolRefusal}).\n * Answers `'stop-turn'`: the turn's other calls must not run, but the loop takes another\n * turn so the model reads it.\n */\nfunction refuse(\n ctx: CallContext,\n call: StreamToolCall,\n decision: { reason?: string; instruction?: string; by?: StreamRefusalOrigin },\n): 'stop-turn' {\n const refusal = toolRefusal(decision);\n ctx.emitter({ type: 'tool-rejected', toolCallId: call.id, ...refusal });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: refusal.reason, toolCallId: call.id, toolName: call.name });\n return 'stop-turn';\n}\n\n/**\n * One call, from its gate to its `tool` message: the ONLY place the loop decides what a\n * call does. The stream reader calls it for each `tool_use` as it arrives; one function,\n * so no second path can drift from it the way core's two loops did before D1.\n */\nasync function processCall(call: StreamToolCall, ctx: CallContext): Promise<CallVerdict> {\n const { signal } = ctx;\n const input = call.input as Record<string, unknown>;\n const cfg = ctx.toolConfigLookup?.(call.name);\n\n // The answer a resumed run was handed for this call, if any.\n const answer = ctx.deferral?.outcomes?.[call.id];\n // A refusal it carries settles the call, whatever the call is: the person's decision on a\n // gated one, and on any other the page refusing an earlier call of the same pause, which\n // ends the calls after it there as in a run in the page. Refusing runs nothing, so a\n // missing handler or a spent turn limit has nothing to stop — checked before them, the\n // refusal still reaches the model, and the model still takes its turn.\n if (answer?.kind === 'decision' && !answer.approved) return refuse(ctx, call, answer);\n\n // Per-tool maxTurns — the same arithmetic as the global cap (`>`), so one\n // number means one thing on both knobs. It was `>=`: `maxTurns: 1` on a\n // tool made it un-callable on the very first turn, and `maxTurns: N`\n // allowed N-1 calls against the global option's N.\n const effectiveMaxTurns = cfg?.maxTurns ?? ctx.maxTurns;\n if (ctx.turns > effectiveMaxTurns) {\n ctx.emitter({ type: 'turn-limit-exceeded', scope: 'tool', limit: effectiveMaxTurns, toolCallId: call.id });\n return 'stop-run';\n }\n\n // Known to the model, run elsewhere: its result comes back in the answer, and\n // until it does the call waits. Before the handler lookup, because there is no\n // handler here by definition — without `deferral` it is aborted like a missing one.\n // The page gates a browser tool as it would in its own loop (its `needsApproval`,\n // its policy), so an approval is no result yet, and a refusal was answered above.\n if (cfg?.external) {\n if (!ctx.deferral) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id, cause: 'no-handler' });\n return 'stop-run';\n }\n if (!answer || answer.kind === 'decision') return { defer: 'external' };\n if (answer.kind === 'failed') {\n // Like a crashing handler: the row hears it, then the run ends on it.\n ctx.emitter({ type: 'tool-failed', toolCallId: call.id, error: answer.error });\n throw new Error(answer.error);\n }\n ctx.emitter({ type: 'tool-resolved', toolCallId: call.id, result: answer.content, structuredResult: answer.structuredContent });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: answer.content, toolCallId: call.id, toolName: call.name });\n return 'continue';\n }\n\n const handler = ctx.toolLookup(call.name);\n if (!handler) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id, cause: 'no-handler' });\n return 'stop-run';\n }\n\n let effectiveInput = call.input;\n\n // ── HITL approval gate ────────────────────────────────────────\n const gated = typeof cfg?.needsApproval === 'function'\n ? cfg.needsApproval({ id: call.id, name: call.name, input })\n : cfg?.needsApproval;\n if (gated) {\n // A decision a resumed run was handed answers the gate: nobody is asked again.\n // Never a `'deny'`: that one is decided here, and an answer that came back with\n // the resume — the page's, for a call queued behind a pause — cannot overrule it.\n const provided = answer?.kind === 'decision' && gated !== 'deny' ? answer : undefined;\n // Announced only when someone is actually being asked: a `'deny'` is a\n // decision already made, and announcing a pause for it painted\n // `awaiting-approval` on the row and raised `aparte-tool-approval-request`\n // for a call nobody was ever going to be asked about.\n if (gated !== 'deny' && !provided) {\n ctx.emitter({ type: 'tool-awaiting-approval', toolCallId: call.id, name: call.name, input: call.input });\n }\n // Nobody here can be asked (the run is not in the page): the call waits for a\n // decision a later run will carry. A `'deny'` is never deferred — it is\n // already decided, and refused below as without `deferral`.\n if (!provided && ctx.deferral && gated !== 'deny') return { defer: 'approval' };\n /*\n * Nothing can ask, so nothing may answer.\n *\n * The default was `async () => ({ approved: false })`, which sent the\n * loop into the refusal branch and appended \"rejected by the user\" —\n * a host that forgot to wire a resolver had not refused anything, and\n * the model was told a person had. A misconfiguration is an aborted\n * call, not a decision.\n */\n if (!provided && !ctx.approvalResolver) {\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id, cause: 'no-resolver' });\n return 'stop-run';\n }\n /*\n * A stop is not a refusal either. Core's built-in channel resolves\n * `{ approved: false }` on abort, indistinguishable by value from an\n * explicit Reject — so the signal is what tells them apart. No\n * `tool` message: an aborted call has nothing true to tell the model.\n *\n * Checked on three sides of the wait, because the stop can land on\n * any of them: while the gate was being shown (a host that reacts to\n * `awaiting-approval` synchronously — the panel's own Stop button),\n * inside the resolver (a channel that rejects with an AbortError\n * rather than resolving), or after it resolved. The first and second\n * used to fall through to the run-level abort, which never marked the\n * call: the segment stayed `awaiting-approval` for good.\n */\n let decision: Awaited<ReturnType<StreamApprovalResolver>> | undefined = provided;\n if (!decision && !signal.aborted && ctx.approvalResolver) {\n try {\n decision = await ctx.approvalResolver(\n { id: call.id, name: call.name, input },\n signal,\n );\n } catch (err) {\n if (!signal.aborted && (err as { name?: string } | undefined)?.name !== 'AbortError') throw err;\n }\n }\n if (signal.aborted || !decision) {\n // No decision without an abort is a resolver that threw an AbortError: a stop too.\n ctx.emitter({ type: 'tool-aborted', toolCallId: call.id, cause: 'stopped' });\n return 'stop-run';\n }\n\n if (!decision.approved) {\n /*\n * The user's own words when they gave them, and the fixed sentence\n * otherwise — byte-identical to core's twin, because this is the\n * only thing the model gets to read about the refusal.\n *\n * This branch used to hardcode the generic sentence. The resolver\n * type declared `instruction` and the whole loop never referenced\n * it, so on the engine path — the recommended one — a user who\n * refused and typed \"use the staging bucket instead\" had those\n * words dropped before the model saw them. Handing the model a\n * turn after a refusal exists SO THAT it reads the refusal; here\n * there was nothing to read.\n *\n * The parity suite could not see it: its resolver returned\n * `{ approved }` and never an instruction, so both loops agreed on\n * a case neither exercised. It supplies one now.\n */\n refuse(ctx, call, decision);\n /*\n * `'stop-turn'`, not `'stop-run'`, and the asymmetry is the point.\n * The remaining tool calls of this turn must not run — the\n * model asked for several and refusing one cannot license the\n * others — but the loop takes another turn, so the sentence just\n * appended actually reaches the model. It never did: the turn\n * ended here, and telling the assistant what you wanted instead\n * meant retyping it as a new message it read out of order.\n *\n * Core's twin returns `'respond'` for exactly this, and the parity\n * suite asserts the two agree.\n */\n return 'stop-turn';\n }\n if (decision.payload && typeof decision.payload === 'object' && !Array.isArray(decision.payload)) {\n effectiveInput = { ...input, ...(decision.payload as Record<string, unknown>) };\n }\n ctx.emitter({ type: 'tool-approved', toolCallId: call.id });\n }\n\n // ── handler invocation with per-call timeout ──────────────────\n const outcome = await invokeToolHandler(\n handler,\n { id: call.id, name: call.name, input: effectiveInput },\n signal,\n ctx.toolTimeoutMs,\n );\n if (outcome.status === 'aborted') {\n // Same rule as the rejection / turn-limit / missing-handler\n // exits above, which this branch was missing: a stopped turn\n // must not go on running the tool calls that follow it.\n ctx.emitter(outcome.cause ? { type: 'tool-aborted', toolCallId: call.id, cause: outcome.cause } : { type: 'tool-aborted', toolCallId: call.id });\n return 'stop-run';\n } else if (outcome.status === 'failed') {\n // A crash is the handler's, and the run ends on it as before —\n // but the row hears about it first, instead of spinning for good.\n ctx.emitter({ type: 'tool-failed', toolCallId: call.id, error: errorText(outcome.error) });\n throw outcome.error;\n } else {\n ctx.emitter({ type: 'tool-resolved', toolCallId: call.id, result: outcome.content, structuredResult: outcome.structuredContent });\n ctx.declareCall(call);\n ctx.append({ role: 'tool', content: outcome.content, toolCallId: call.id, toolName: call.name });\n }\n return 'continue';\n}\n\nasync function invokeToolHandler(\n handler: StreamToolHandler,\n call: StreamToolCall,\n signal: AbortSignal,\n toolTimeoutMs: number,\n): Promise<\n | { status: 'resolved'; content: string; structuredContent?: unknown }\n /** `cause` is absent for a handler that aborted itself, with no Stop and no timeout. */\n | { status: 'aborted'; cause?: Extract<StreamAbortCause, 'stopped' | 'timeout'> }\n /** The handler threw something that is not an abort: a crash, reported by the caller then re-thrown. */\n | { status: 'failed'; error: unknown }\n> {\n // If the run was already aborted before we got here, don't invoke the\n // handler at all: a past 'abort' event will never re-fire on the listener\n // below, so the handler would otherwise run to completion despite cancel.\n if (signal.aborted) return { status: 'aborted', cause: 'stopped' };\n\n const controller = new AbortController();\n const onParentAbort = () => controller.abort();\n signal.addEventListener('abort', onParentAbort, { once: true });\n\n /*\n * The parent abort is RACED for the same reason the timeout is.\n *\n * `onParentAbort` above only forwards the Stop to the child controller, and a\n * deaf handler ignores that exactly as it ignores the timeout's. So a Stop\n * pressed during a tool call unwound nothing: the loop stayed parked on the\n * handler until `toolTimeoutMs` expired — five minutes by default — with the\n * typing indicator still up and the stop button already pressed. The signal is\n * still fired first, so a handler that honours it gets to reject on its own\n * terms; this racer is what makes the Stop true when it does not.\n */\n let onParentAbortRace: (() => void) | undefined;\n const parentAborted = new Promise<{ status: 'aborted'; cause: 'stopped' }>((resolve) => {\n onParentAbortRace = () => resolve({ status: 'aborted', cause: 'stopped' });\n signal.addEventListener('abort', onParentAbortRace, { once: true });\n });\n\n /*\n * RACED, not just signalled.\n *\n * Aborting the controller is a request the handler is free to ignore, and the\n * default shape of a consumer tool ignores it —\n * `async () => ({ content: await fetch(...).then(r => r.text()) })` never reads\n * its signal. So the timeout fired, nothing rejected, and the loop waited\n * forever on an option whose JSDoc promises a timeout.\n *\n * The signal still fires first, because a handler that DOES honour it should\n * get the chance to clean up and reject on its own terms; the race is what\n * makes the promise true when it does not.\n */\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let expired = false;\n const timedOut = new Promise<{ status: 'aborted'; cause: 'timeout' }>((resolve) => {\n timeout = setTimeout(() => {\n expired = true;\n controller.abort();\n resolve({ status: 'aborted', cause: 'timeout' });\n }, toolTimeoutMs);\n });\n\n try {\n const result = await Promise.race([\n handler(call, controller.signal).then((r) => ({ status: 'resolved' as const, content: r.content, structuredContent: r.structuredContent })),\n timedOut,\n parentAborted,\n ]);\n return result;\n } catch (err: unknown) {\n // A handler that honoured the signal rejects with its own AbortError: whichever\n // fired it is the cause.\n if ((err as { name?: string })?.name === 'AbortError') {\n if (signal.aborted) return { status: 'aborted', cause: 'stopped' };\n if (expired) return { status: 'aborted', cause: 'timeout' };\n // Its own abort — nobody pressed Stop and it did not run out of time.\n return { status: 'aborted' };\n }\n // Not thrown from here: the call was announced with `tool-start`, and the\n // loop's invariant is that an announced call gets exactly one terminal\n // event. Throwing from inside skipped it — the row spun \"Running\" for good\n // while the message showed an error card. The caller reports, then throws.\n return { status: 'failed', error: err };\n } finally {\n clearTimeout(timeout);\n signal.removeEventListener('abort', onParentAbort);\n if (onParentAbortRace) signal.removeEventListener('abort', onParentAbortRace);\n }\n}\n\n/** The one-line text of a handler's crash, for the `tool-failed` event and the row. */\nfunction errorText(error: unknown): string {\n if (error instanceof Error) return error.message || error.name;\n return typeof error === 'string' ? error : String(error);\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aparte/engine",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "homepage": "https://apartejs.dev/reference/engine/",
5
5
  "description": "The agent loop on its own: runStreamAgent streams a model's turns and runs its tools, in the browser or in Node — zero runtime dependencies.",
6
6
  "type": "module",