llmz 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/DOCS.md CHANGED
@@ -1582,30 +1582,94 @@ await execute({
1582
1582
 
1583
1583
  ---
1584
1584
 
1585
- ### Buffered Delivery (midStreamFallback)
1585
+ ### Mid-Stream Model Fallback (midStreamFallback)
1586
1586
 
1587
- By default, LLMz streams responses: send blocks are dispatched as soon as they are parsed, and the ■run block starts executing while the model is still generating the rest of the response. This gives the lowest time-to-first-token but runs code before the response is complete.
1587
+ By default, LLMz streams responses: message bodies (`■send` blocks) and completed `Chat.handler` sends are dispatched as soon as they are produced, and the ■run block starts executing while the model is still generating the rest of the response. This gives the lowest time-to-first-token but runs code before the response is complete.
1588
1588
 
1589
- Set `options.midStreamFallback: true` to allow Cognitive to restart a failed stream on another model safely. LLMz buffers delivery and discards all content from abandoned attempts:
1589
+ Set `options.midStreamFallback: true` to allow Cognitive to restart a failed stream on another model. There is **no buffered delivery and no final delivery queue**: with fallback enabled, delta text and completed `Chat.handler` sends both stream immediately during generation. Only **TOOL/CODE execution** waits for a stream to complete successfully. When Cognitive abandons an attempt, a reset-only delta (`restart: true`) is delivered through the existing `Chat.onMessageDelta`; it invalidates **all** messages of the current iteration — **including messages already committed through `handler`** — so consumers must retract and replace them:
1590
1590
 
1591
1591
  ```typescript
1592
1592
  await execute({
1593
1593
  // ...
1594
+ chat: new MyChat({
1595
+ onMessageDelta: (delta) => {
1596
+ if (delta.restart) {
1597
+ // invalidates ALL current-iteration messages, including completed handler
1598
+ // sends: retract/replace them before the replacement streams
1599
+ return retractIteration(delta.iterationId)
1600
+ } else {
1601
+ // stream this message's text as it arrives (same iterationId as below)
1602
+ updateMessage(delta)
1603
+ }
1604
+ },
1605
+ }),
1594
1606
  options: { midStreamFallback: true },
1595
1607
  })
1596
1608
  ```
1597
1609
 
1598
- When enabled, the runtime buffers the full generated response and only dispatches it (sends and code execution) once the stream has finished. Trade-offs:
1610
+ `retractIteration`/`updateMessage` are placeholder names for your consumer's message store. **This option is not safe to enable blindly**: a reset invalidates messages that were already delivered through `handler`. If your transport cannot retract or replace an already-sent message — an external irreversible send such as SMS, email, or a third-party webhook — those sends cannot be undone, so keep `midStreamFallback` disabled unless your consumer can retract and replace invalidated messages.
1611
+
1612
+ #### MessageDelta contract
1613
+
1614
+ `Chat.onMessageDelta` receives a single typed `MessageDelta` discriminated union:
1615
+
1616
+ - **Ordinary text chunk** (`restart: false`) — the next piece of one message's body; all fields are required:
1617
+ ```typescript
1618
+ {
1619
+ restart: false,
1620
+ iterationId: string, // stable across ALL attempts of this LLM generation iteration
1621
+ id: string, // message id — DIFFERENT per attempt (unique when fallback is on)
1622
+ component: string,
1623
+ props: Record<string, unknown>, // final by the time the body starts streaming
1624
+ delta: string, // new chunk of body text
1625
+ content: string, // full body text accumulated so far, including this chunk
1626
+ }
1627
+ ```
1628
+ - **Reset-only delta** (`restart: true`) — carries no text; signals the current generation attempt was abandoned and a replacement is being attempted:
1629
+ ```typescript
1630
+ {
1631
+ restart: true,
1632
+ iterationId: string, // the iteration whose messages (incl. handler sends) must be retracted/replaced
1633
+ attempt: number,
1634
+ fromModel: string,
1635
+ toModel: string,
1636
+ reason: string,
1637
+ }
1638
+ ```
1639
+
1640
+ IDs: `iterationId` is **stable across attempts** — every restart of the same iteration keeps the same `iterationId` — while `id` is **different per attempt**. After a reset, replacement chunks arrive under new `id`s within the same `iterationId`.
1641
+
1642
+ Consumer rules:
1643
+
1644
+ - On `restart: true`, retract/replace **all** current-iteration messages bearing that `iterationId` — streamed text **and** messages already delivered through `handler`.
1645
+ - On an ordinary chunk, update the message for `delta.id` (create it if new, otherwise append).
1646
+ - A reset is emitted and awaited **before** replacement output begins, **even if the replacement yields no message at all**. Return/await the retraction promise in your handler. If it throws or rejects, generation fails without delivering replacement output or executing code; ordinary text-preview errors remain best-effort.
1647
+ - A reset only invalidates the messages of its own `iterationId`; it **never** invalidates messages from earlier, completed iterations.
1648
+ - Only TOOL/CODE execution is gated on a successful stream: generated code and tool calls never run from an abandoned attempt.
1649
+
1650
+ Outside of fallback (the default), `onMessageDelta` behavior is unchanged: ordinary chunks carry `iterationId`/`id`/`component`/`props`/`delta`/`content` with a single stable message id, and no restart/reset deltas are produced.
1651
+
1652
+ #### Completed-send correlation
1653
+
1654
+ `Chat.handler(component, metadata)` receives `{ iterationId: string, id: string }` for **every send**, including empty-body and non-text components, without requiring `onMessageDelta`. For streamed messages, `metadata.id` matches the text delta's `id`; it changes across attempts while `iterationId` stays stable. Existing one-argument handlers continue to work.
1655
+
1656
+ Record **all** persisted message IDs created while awaiting that handler under its metadata (one component may produce multiple persisted messages). A restart can then retract everything for the matching iteration, even after a message's stream tracking has completed. Non-streaming sends also receive metadata. Tool-yielded messages get unique per-yield IDs and the runtime iteration ID; standalone `Tool.execute` calls without an iteration use their `callId` as the scope.
1657
+
1658
+ #### Migrating consumers
1659
+
1660
+ - **No final delivery queue**: messages are not withheld until the stream ends. Deltas and completed `handler` sends arrive during generation; drop any "promote on success" design — treat every delivered message as provisional until the iteration completes without a reset.
1661
+ - **Retract, don't just clear previews**: on `restart: true`, retraction must cascade to every current-iteration message, including ones already committed through `handler`. If your UI or transport cannot do that, keep `midStreamFallback` disabled.
1662
+ - **Clear on errors and cancellation**: a failed or cancelled generation can leave current-iteration messages on screen, incomplete, or unresettable. Explicitly clear/roll back current-iteration state when execution errors or ends — there is no exactly-once guarantee, and caller retries can duplicate effects.
1663
+ - **Irreversible transports**: external sends that cannot be undone (SMS, email, webhooks) must not blindly enable fallback without a reset-capable gateway.
1664
+
1665
+ #### Caveats
1599
1666
 
1600
- - **Latency**: higher time-to-first-send/token, since nothing is delivered until the response completes.
1601
- - **No early code**: the VM is not run mid-stream; code executes only after the full response is parsed.
1602
- - **Cancellation**: cancellation still aborts the original generation request as usual.
1603
1667
  - **No extra retry loop**: Cognitive owns fallback, bounded by the model chain. LLMz does not retry abandoned attempts itself.
1604
- - **Not exactly-once**: abandoned attempts deliver nothing, but successful-attempt callbacks cannot be rolled back if delivery fails or cancellation arrives during delivery. Caller retries can duplicate effects.
1668
+ - **Cancellation**: cancellation still aborts the original generation request as usual; consumers must clear current-iteration state on abort (see above). Cancellation deadlines are not reset, and the existing stream inactivity guard remains active during handoffs.
1605
1669
 
1606
- This option defaults to false and applies only to streaming clients. Non-streaming Cognitive requests already fall back transparently. It can be combined with `maxTimeToFirstToken` and `transcriptionModel`; do not enable it globally underneath LLMz without also enabling LLMz's buffering option.
1670
+ This option defaults to false and applies only to streaming clients. Non-streaming Cognitive requests already fall back transparently. It can be combined with `maxTimeToFirstToken` and `transcriptionModel`; do not enable it globally underneath LLMz without also enabling LLMz's fallback option and a reset-capable consumer.
1607
1671
 
1608
- Restarts emit `llm_call_restarted` traces with the attempt, source/destination models, and reason. Token timings remain relative to the original request and describe the surviving attempt (including time spent on prior attempts). Usage and cost retain Cognitive's final reported metadata; LLMz does not infer or sum abandoned-attempt billing. Cancellation deadlines are not reset, and the existing stream inactivity guard remains active during handoffs.
1672
+ Restarts emit `llm_call_restarted` traces with the attempt, source/destination models, and reason. Token timings remain relative to the original request and describe the surviving attempt (including time spent on prior attempts). Usage and cost retain Cognitive's final reported metadata; LLMz does not infer or sum abandoned-attempt billing.
1609
1673
 
1610
1674
  ## API Reference
1611
1675
 
package/dist/chat.d.ts CHANGED
@@ -24,7 +24,14 @@ import { TranscriptArray, Transcript } from './transcript.js';
24
24
  * }
25
25
  * ```
26
26
  */
27
- export type MessageHandler = (input: RenderedComponent) => Promise<void> | void;
27
+ /** Correlates every generated send with its deltas and restart scope. */
28
+ export type MessageMetadata = {
29
+ /** Stable across generation attempts; standalone tool calls use their callId. */
30
+ iterationId: string;
31
+ /** Logical send ID; matches MessageDelta.id when text deltas exist. Unique across attempts. */
32
+ id: string;
33
+ };
34
+ export type MessageHandler = (input: RenderedComponent, metadata: MessageMetadata) => Promise<void> | void;
28
35
  /**
29
36
  * A chunk of a message body streamed live from the LLM, before the message is complete.
30
37
  *
@@ -32,10 +39,14 @@ export type MessageHandler = (input: RenderedComponent) => Promise<void> | void;
32
39
  * Each chunk is forwarded to {@link Chat} `onMessageDelta` as soon as it is parsed, so the
33
40
  * client can render the message progressively (e.g. typewriter effect in a chat UI).
34
41
  *
35
- * The complete message is always delivered to the regular `handler` afterwards — deltas
36
- * are a progressive preview, the `handler` call remains the authoritative delivery.
42
+ * In mid-stream fallback mode, both deltas and completed `handler` messages
43
+ * are live and retractable: restart deltas invalidate every message from that
44
+ * iteration. Consumers must retract them; only code execution waits for success.
37
45
  */
38
46
  export type MessageDelta = {
47
+ restart: false;
48
+ /** Generation whose provisional messages must be cleared on a restart. */
49
+ iterationId: string;
39
50
  /** Identifies the message being streamed. Unique within an `execute()` call. */
40
51
  id: string;
41
52
  /** Component name of the message being streamed (e.g. 'message', 'button'). */
@@ -46,10 +57,24 @@ export type MessageDelta = {
46
57
  delta: string;
47
58
  /** The full body text accumulated so far, including this chunk. */
48
59
  content: string;
60
+ } | {
61
+ /**
62
+ * Reset-only delta, emitted before replacement text (even if none follows).
63
+ * Clear ALL messages for iterationId, including completed `handler` sends.
64
+ * Other iterations are unaffected. Irreversible sends cannot be undone.
65
+ */
66
+ restart: true;
67
+ iterationId: string;
68
+ /** 1-based index of the new attempt. */
69
+ attempt: number;
70
+ fromModel: string;
71
+ toModel: string;
72
+ reason: string;
49
73
  };
50
74
  /**
51
75
  * Function type for handling message chunks as they are streamed from the LLM.
52
- * Errors thrown by this handler are ignored — streaming previews are best-effort.
76
+ * Text-preview errors are ignored. Restart-handler errors stop generation:
77
+ * replacement output must not be delivered if retraction fails.
53
78
  */
54
79
  export type MessageDeltaHandler = (delta: MessageDelta) => Promise<void> | void;
55
80
  /**
package/dist/context.d.ts CHANGED
@@ -565,8 +565,14 @@ export declare class Context implements Serializable<Context.JSON> {
565
565
  */
566
566
  maxTimeToFirstToken?: number;
567
567
  /**
568
- * Allow Cognitive mid-stream fallback, buffering delivery until generation
569
- * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
568
+ * Allow Cognitive to restart a failed stream on another model. With fallback
569
+ * enabled, there is no buffered delivery and no final delivery queue: delta
570
+ * text and completed Chat.handler sends stream immediately during generation.
571
+ * A reset-only delta (restart: true) on Chat.onMessageDelta invalidates ALL
572
+ * current-iteration messages, including completed sends — consumers must
573
+ * retract/replace them, which is unsafe for irreversible external transports.
574
+ * Only TOOL/CODE execution waits for a successful stream. Streaming-only;
575
+ * defaults to false, which leaves progressive sending unchanged.
570
576
  */
571
577
  midStreamFallback?: boolean;
572
578
  /**
@@ -4,7 +4,7 @@ import { f as flatRest, l as extractType, m as baseCreate, p as copyArray, t as
4
4
  import { _ as getSymbolsIn, b as keysIn, g as getAllKeysIn, o as getTokenizer, x as assignValue, y as baseSlice } from "./utils-UBeAFeEI.js";
5
5
  import { t as getPrototype } from "./_getPrototype-CG7Dxrrz.js";
6
6
  import { t as isPlainObject } from "./isPlainObject-D39AzPO2.js";
7
- import { t as Tool } from "./tool-CAo-BQHs.js";
7
+ import { t as Tool } from "./tool-Bv8N4IPj.js";
8
8
  import { o as LoopExceededError } from "./errors-Cg1G0Awu.js";
9
9
  import { t as Exit } from "./exit-CDdxDBRT.js";
10
10
  import { n as assertValidComponent } from "./component-DGG7d_Mz.js";
@@ -2254,8 +2254,14 @@ var Context = class {
2254
2254
  */
2255
2255
  maxTimeToFirstToken;
2256
2256
  /**
2257
- * Allow Cognitive mid-stream fallback, buffering delivery until generation
2258
- * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
2257
+ * Allow Cognitive to restart a failed stream on another model. With fallback
2258
+ * enabled, there is no buffered delivery and no final delivery queue: delta
2259
+ * text and completed Chat.handler sends stream immediately during generation.
2260
+ * A reset-only delta (restart: true) on Chat.onMessageDelta invalidates ALL
2261
+ * current-iteration messages, including completed sends — consumers must
2262
+ * retract/replace them, which is unsafe for irreversible external transports.
2263
+ * Only TOOL/CODE execution waits for a successful stream. Streaming-only;
2264
+ * defaults to false, which leaves progressive sending unchanged.
2259
2265
  */
2260
2266
  midStreamFallback;
2261
2267
  /**
@@ -4,7 +4,7 @@ const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
4
4
  const require_utils = require("./utils-ZpJm00ed.cjs");
5
5
  const require__getPrototype = require("./_getPrototype-DQDlNVVM.cjs");
6
6
  const require_isPlainObject = require("./isPlainObject-CSxXVKCk.cjs");
7
- const require_tool = require("./tool-DdPZb0JW.cjs");
7
+ const require_tool = require("./tool-ReA9gxMp.cjs");
8
8
  const require_errors = require("./errors-C962uJdS.cjs");
9
9
  const require_exit = require("./exit-BGi0Kk0y.cjs");
10
10
  const require_component = require("./component-BwixNVH8.cjs");
@@ -2254,8 +2254,14 @@ var Context = class {
2254
2254
  */
2255
2255
  maxTimeToFirstToken;
2256
2256
  /**
2257
- * Allow Cognitive mid-stream fallback, buffering delivery until generation
2258
- * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
2257
+ * Allow Cognitive to restart a failed stream on another model. With fallback
2258
+ * enabled, there is no buffered delivery and no final delivery queue: delta
2259
+ * text and completed Chat.handler sends stream immediately during generation.
2260
+ * A reset-only delta (restart: true) on Chat.onMessageDelta invalidates ALL
2261
+ * current-iteration messages, including completed sends — consumers must
2262
+ * retract/replace them, which is unsafe for irreversible external transports.
2263
+ * Only TOOL/CODE execution waits for a successful stream. Streaming-only;
2264
+ * defaults to false, which leaves progressive sending unchanged.
2259
2265
  */
2260
2266
  midStreamFallback;
2261
2267
  /**
@@ -1,6 +1,6 @@
1
1
  const require__baseForOwn = require("./_baseForOwn-buOZDFbK.cjs");
2
2
  const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
3
- const require_custom_client = require("./custom-client-BX8gx0tI.cjs");
3
+ const require_custom_client = require("./custom-client-Cr33K60k.cjs");
4
4
  const require_utils = require("./utils-ZpJm00ed.cjs");
5
5
  const require_jsx = require("./jsx-BWvT6ukg.cjs");
6
6
  const require_stack_traces = require("./stack-traces-ntw_qhoy.cjs");
@@ -197,11 +197,18 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
197
197
  /** Milliseconds between the stream request and the first/last streamed tokens. */
198
198
  let timeToFirstToken;
199
199
  let timeToLastToken;
200
- const bufferedDelivery = ctx.midStreamFallback === true;
201
- let pendingDeliveries = [];
202
- const deliver = async (callback) => {
203
- if (bufferedDelivery) pendingDeliveries.push(callback);
204
- else await callback();
200
+ const midStreamFallback = ctx.midStreamFallback === true;
201
+ let attempt = 1;
202
+ const messageMetadata = (itemId) => ({
203
+ iterationId: iteration.id,
204
+ id: midStreamFallback ? `${iteration.id}:${attempt}:${itemId}` : `${iteration.id}:${itemId}`
205
+ });
206
+ const preview = async (delta) => {
207
+ try {
208
+ await (onSendDelta === null || onSendDelta === void 0 ? void 0 : onSendDelta(delta));
209
+ } catch (err) {
210
+ if (delta.restart) throw new require_errors.CognitiveError(`LLM stream restart handler failed: ${require_utils.getErrorMessage(err)}`);
211
+ }
205
212
  };
206
213
  const liveItems = /* @__PURE__ */ new Map();
207
214
  const liveContent = /* @__PURE__ */ new Map();
@@ -212,14 +219,11 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
212
219
  liveItems.set(event.item.id, event.item);
213
220
  if (event.item.kind === "run" && !codeGenerationTraced) {
214
221
  codeGenerationTraced = true;
215
- const started_at = Date.now();
216
- await deliver(() => {
217
- traces.push({
218
- type: "code_generation_started",
219
- started_at
220
- });
221
- onRunStart === null || onRunStart === void 0 || onRunStart();
222
+ traces.push({
223
+ type: "code_generation_started",
224
+ started_at: Date.now()
222
225
  });
226
+ onRunStart === null || onRunStart === void 0 || onRunStart();
223
227
  }
224
228
  } else if (event.type === "body-delta" && onSendDelta) {
225
229
  const item = liveItems.get(event.itemId);
@@ -227,29 +231,24 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
227
231
  const content = (liveContent.get(item.id) ?? "") + event.delta;
228
232
  liveContent.set(item.id, content);
229
233
  const delta = {
230
- id: `${iteration.id}:${item.id}`,
234
+ restart: false,
235
+ ...messageMetadata(item.id),
231
236
  component: item.name,
232
- props: bufferedDelivery ? structuredClone(item.props) : item.props,
237
+ props: item.props,
233
238
  delta: event.delta,
234
239
  content
235
240
  };
236
- await deliver(async () => {
237
- try {
238
- await onSendDelta(delta);
239
- } catch (err) {}
240
- });
241
+ await preview(delta);
241
242
  } else if (event.type === "item-complete") {
242
- if (event.item.kind === "send" && onSend) {
243
- const send = {
244
- name: event.item.name,
245
- props: bufferedDelivery ? structuredClone(event.item.props) : event.item.props,
246
- body: event.item.body
247
- };
248
- await deliver(() => onSend(send));
249
- } else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
243
+ if (event.item.kind === "send" && onSend) await onSend({
244
+ name: event.item.name,
245
+ props: event.item.props,
246
+ body: event.item.body
247
+ }, messageMetadata(event.item.id));
248
+ else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
250
249
  runCompleted = true;
251
250
  const code = (event.item.body ?? "").trim();
252
- await deliver(() => onRunComplete === null || onRunComplete === void 0 ? void 0 : onRunComplete(code));
251
+ onRunComplete === null || onRunComplete === void 0 || onRunComplete(code);
253
252
  }
254
253
  }
255
254
  };
@@ -260,10 +259,10 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
260
259
  const requestedAt = Date.now();
261
260
  const stream = cognitive.generateTextStream({
262
261
  ...input,
263
- ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
262
+ ...ctx.maxTimeToFirstToken || midStreamFallback ? { options: {
264
263
  ...input.options,
265
264
  ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
266
- ...bufferedDelivery ? { midStreamFallback: true } : {}
265
+ ...midStreamFallback ? { midStreamFallback: true } : {}
267
266
  } } : {}
268
267
  }, { signal: streamController.signal });
269
268
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -296,7 +295,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
296
295
  streamCompleted = true;
297
296
  break;
298
297
  }
299
- if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !bufferedDelivery) {
298
+ if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !midStreamFallback) {
300
299
  streamController.abort("Unexpected LLM stream restart");
301
300
  throw new require_errors.CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
302
301
  }
@@ -311,10 +310,15 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
311
310
  fence = new LeadingFenceFilter();
312
311
  liveItems.clear();
313
312
  liveContent.clear();
314
- pendingDeliveries = [];
315
313
  codeGenerationTraced = false;
316
314
  runCompleted = false;
317
315
  responseMetadata = void 0;
316
+ attempt = chunk.value.restart.attempt;
317
+ await preview({
318
+ ...chunk.value.restart,
319
+ restart: true,
320
+ iterationId: iteration.id
321
+ });
318
322
  timeToFirstToken = void 0;
319
323
  timeToLastToken = void 0;
320
324
  continue;
@@ -334,16 +338,9 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
334
338
  assistantResponse = require_dual_modes.toParsedAssistantResponse(parser.items, raw);
335
339
  } finally {
336
340
  streamController.abort("LLM stream closed");
337
- if (!streamCompleted) stream.return(void 0).catch(() => {});
338
- }
339
- if (bufferedDelivery) {
340
- controller.signal.throwIfAborted();
341
- for (const callback of pendingDeliveries) {
342
- controller.signal.throwIfAborted();
343
- await callback();
344
- }
345
- pendingDeliveries = [];
341
+ if (!streamCompleted) stream.return(void 0).catch((err) => {});
346
342
  }
343
+ if (midStreamFallback) controller.signal.throwIfAborted();
347
344
  } else {
348
345
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
349
346
  throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
@@ -352,7 +349,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
352
349
  responseMetadata = response.metadata;
353
350
  raw = response.output;
354
351
  assistantResponse = ctx.version.parseAssistantResponse(raw);
355
- for (const send of assistantResponse.sends) await (onSend === null || onSend === void 0 ? void 0 : onSend(send));
352
+ for (const [index, send] of assistantResponse.sends.entries()) await (onSend === null || onSend === void 0 ? void 0 : onSend(send, messageMetadata(`send-${index}`)));
356
353
  }
357
354
  iteration.code = assistantResponse.code;
358
355
  iteration.sends = assistantResponse.sends;
@@ -628,7 +625,10 @@ function wrapTool({ chat, tool, traces, object, iteration, beforeHook, afterHook
628
625
  toolCallId
629
626
  }));
630
627
  if (typeof (beforeRes === null || beforeRes === void 0 ? void 0 : beforeRes.input) !== "undefined") effectiveInput = beforeRes.input;
631
- output = await tool.execute(effectiveInput, { callId: toolCallId }, chat);
628
+ output = await tool.execute(effectiveInput, {
629
+ callId: toolCallId,
630
+ iterationId: iteration.id
631
+ }, chat);
632
632
  const afterRes = await (afterHook === null || afterHook === void 0 ? void 0 : afterHook({
633
633
  iteration,
634
634
  tool,
@@ -959,7 +959,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
959
959
  promise: runCode(code)
960
960
  };
961
961
  } : void 0,
962
- onSend: async (send) => {
962
+ onSend: async (send, messageMetadata) => {
963
963
  if (!ctx.chat) return;
964
964
  const sendStartedAt = Date.now();
965
965
  const component = require_jsx.createJsxComponent({
@@ -968,7 +968,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
968
968
  children: send.body ? [send.body] : []
969
969
  });
970
970
  try {
971
- await ctx.chat.handler(component);
971
+ await ctx.chat.handler(component, messageMetadata);
972
972
  } catch (err) {
973
973
  throw new Error(`Error while sending message (■send=${send.name}): ${require_utils.getErrorMessage(err)}`);
974
974
  }
@@ -1,5 +1,5 @@
1
1
  import { a as StreamingMessageParser, d as isEqual, h as toNumber, i as toParsedAssistantResponse } from "./dual-modes-D_p-jedw.js";
2
- import { d as Snapshot, f as parseExit, l as PartialExecutionResult, n as Context, s as ErrorExecutionResult, t as _CustomModelClient, u as SuccessExecutionResult } from "./custom-client-BnlDpjdI.js";
2
+ import { d as Snapshot, f as parseExit, l as PartialExecutionResult, n as Context, s as ErrorExecutionResult, t as _CustomModelClient, u as SuccessExecutionResult } from "./custom-client-CJIThG2H.js";
3
3
  import { d as stripInvalidIdentifiers, i as getErrorMessage, s as init } from "./utils-UBeAFeEI.js";
4
4
  import { t as createJsxComponent } from "./jsx-CIgdGGVs.js";
5
5
  import { t as cleanStackTrace } from "./stack-traces-BIE_5Xwf.js";
@@ -195,11 +195,18 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
195
195
  /** Milliseconds between the stream request and the first/last streamed tokens. */
196
196
  let timeToFirstToken;
197
197
  let timeToLastToken;
198
- const bufferedDelivery = ctx.midStreamFallback === true;
199
- let pendingDeliveries = [];
200
- const deliver = async (callback) => {
201
- if (bufferedDelivery) pendingDeliveries.push(callback);
202
- else await callback();
198
+ const midStreamFallback = ctx.midStreamFallback === true;
199
+ let attempt = 1;
200
+ const messageMetadata = (itemId) => ({
201
+ iterationId: iteration.id,
202
+ id: midStreamFallback ? `${iteration.id}:${attempt}:${itemId}` : `${iteration.id}:${itemId}`
203
+ });
204
+ const preview = async (delta) => {
205
+ try {
206
+ await (onSendDelta === null || onSendDelta === void 0 ? void 0 : onSendDelta(delta));
207
+ } catch (err) {
208
+ if (delta.restart) throw new CognitiveError(`LLM stream restart handler failed: ${getErrorMessage(err)}`);
209
+ }
203
210
  };
204
211
  const liveItems = /* @__PURE__ */ new Map();
205
212
  const liveContent = /* @__PURE__ */ new Map();
@@ -210,14 +217,11 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
210
217
  liveItems.set(event.item.id, event.item);
211
218
  if (event.item.kind === "run" && !codeGenerationTraced) {
212
219
  codeGenerationTraced = true;
213
- const started_at = Date.now();
214
- await deliver(() => {
215
- traces.push({
216
- type: "code_generation_started",
217
- started_at
218
- });
219
- onRunStart === null || onRunStart === void 0 || onRunStart();
220
+ traces.push({
221
+ type: "code_generation_started",
222
+ started_at: Date.now()
220
223
  });
224
+ onRunStart === null || onRunStart === void 0 || onRunStart();
221
225
  }
222
226
  } else if (event.type === "body-delta" && onSendDelta) {
223
227
  const item = liveItems.get(event.itemId);
@@ -225,29 +229,24 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
225
229
  const content = (liveContent.get(item.id) ?? "") + event.delta;
226
230
  liveContent.set(item.id, content);
227
231
  const delta = {
228
- id: `${iteration.id}:${item.id}`,
232
+ restart: false,
233
+ ...messageMetadata(item.id),
229
234
  component: item.name,
230
- props: bufferedDelivery ? structuredClone(item.props) : item.props,
235
+ props: item.props,
231
236
  delta: event.delta,
232
237
  content
233
238
  };
234
- await deliver(async () => {
235
- try {
236
- await onSendDelta(delta);
237
- } catch (err) {}
238
- });
239
+ await preview(delta);
239
240
  } else if (event.type === "item-complete") {
240
- if (event.item.kind === "send" && onSend) {
241
- const send = {
242
- name: event.item.name,
243
- props: bufferedDelivery ? structuredClone(event.item.props) : event.item.props,
244
- body: event.item.body
245
- };
246
- await deliver(() => onSend(send));
247
- } else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
241
+ if (event.item.kind === "send" && onSend) await onSend({
242
+ name: event.item.name,
243
+ props: event.item.props,
244
+ body: event.item.body
245
+ }, messageMetadata(event.item.id));
246
+ else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
248
247
  runCompleted = true;
249
248
  const code = (event.item.body ?? "").trim();
250
- await deliver(() => onRunComplete === null || onRunComplete === void 0 ? void 0 : onRunComplete(code));
249
+ onRunComplete === null || onRunComplete === void 0 || onRunComplete(code);
251
250
  }
252
251
  }
253
252
  };
@@ -258,10 +257,10 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
258
257
  const requestedAt = Date.now();
259
258
  const stream = cognitive.generateTextStream({
260
259
  ...input,
261
- ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
260
+ ...ctx.maxTimeToFirstToken || midStreamFallback ? { options: {
262
261
  ...input.options,
263
262
  ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
264
- ...bufferedDelivery ? { midStreamFallback: true } : {}
263
+ ...midStreamFallback ? { midStreamFallback: true } : {}
265
264
  } } : {}
266
265
  }, { signal: streamController.signal });
267
266
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -294,7 +293,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
294
293
  streamCompleted = true;
295
294
  break;
296
295
  }
297
- if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !bufferedDelivery) {
296
+ if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !midStreamFallback) {
298
297
  streamController.abort("Unexpected LLM stream restart");
299
298
  throw new CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
300
299
  }
@@ -309,10 +308,15 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
309
308
  fence = new LeadingFenceFilter();
310
309
  liveItems.clear();
311
310
  liveContent.clear();
312
- pendingDeliveries = [];
313
311
  codeGenerationTraced = false;
314
312
  runCompleted = false;
315
313
  responseMetadata = void 0;
314
+ attempt = chunk.value.restart.attempt;
315
+ await preview({
316
+ ...chunk.value.restart,
317
+ restart: true,
318
+ iterationId: iteration.id
319
+ });
316
320
  timeToFirstToken = void 0;
317
321
  timeToLastToken = void 0;
318
322
  continue;
@@ -332,16 +336,9 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
332
336
  assistantResponse = toParsedAssistantResponse(parser.items, raw);
333
337
  } finally {
334
338
  streamController.abort("LLM stream closed");
335
- if (!streamCompleted) stream.return(void 0).catch(() => {});
336
- }
337
- if (bufferedDelivery) {
338
- controller.signal.throwIfAborted();
339
- for (const callback of pendingDeliveries) {
340
- controller.signal.throwIfAborted();
341
- await callback();
342
- }
343
- pendingDeliveries = [];
339
+ if (!streamCompleted) stream.return(void 0).catch((err) => {});
344
340
  }
341
+ if (midStreamFallback) controller.signal.throwIfAborted();
345
342
  } else {
346
343
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
347
344
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
@@ -350,7 +347,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
350
347
  responseMetadata = response.metadata;
351
348
  raw = response.output;
352
349
  assistantResponse = ctx.version.parseAssistantResponse(raw);
353
- for (const send of assistantResponse.sends) await (onSend === null || onSend === void 0 ? void 0 : onSend(send));
350
+ for (const [index, send] of assistantResponse.sends.entries()) await (onSend === null || onSend === void 0 ? void 0 : onSend(send, messageMetadata(`send-${index}`)));
354
351
  }
355
352
  iteration.code = assistantResponse.code;
356
353
  iteration.sends = assistantResponse.sends;
@@ -626,7 +623,10 @@ function wrapTool({ chat, tool, traces, object, iteration, beforeHook, afterHook
626
623
  toolCallId
627
624
  }));
628
625
  if (typeof (beforeRes === null || beforeRes === void 0 ? void 0 : beforeRes.input) !== "undefined") effectiveInput = beforeRes.input;
629
- output = await tool.execute(effectiveInput, { callId: toolCallId }, chat);
626
+ output = await tool.execute(effectiveInput, {
627
+ callId: toolCallId,
628
+ iterationId: iteration.id
629
+ }, chat);
630
630
  const afterRes = await (afterHook === null || afterHook === void 0 ? void 0 : afterHook({
631
631
  iteration,
632
632
  tool,
@@ -957,7 +957,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
957
957
  promise: runCode(code)
958
958
  };
959
959
  } : void 0,
960
- onSend: async (send) => {
960
+ onSend: async (send, messageMetadata) => {
961
961
  if (!ctx.chat) return;
962
962
  const sendStartedAt = Date.now();
963
963
  const component = createJsxComponent({
@@ -966,7 +966,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
966
966
  children: send.body ? [send.body] : []
967
967
  });
968
968
  try {
969
- await ctx.chat.handler(component);
969
+ await ctx.chat.handler(component, messageMetadata);
970
970
  } catch (err) {
971
971
  throw new Error(`Error while sending message (■send=${send.name}): ${getErrorMessage(err)}`);
972
972
  }
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
3
- const require_custom_client = require("./custom-client-BX8gx0tI.cjs");
3
+ const require_custom_client = require("./custom-client-Cr33K60k.cjs");
4
4
  const require_utils = require("./utils-ZpJm00ed.cjs");
5
- const require_tool = require("./tool-DdPZb0JW.cjs");
5
+ const require_tool = require("./tool-ReA9gxMp.cjs");
6
6
  const require_errors = require("./errors-C962uJdS.cjs");
7
7
  const require_typings = require("./typings-BwAz_TM9.cjs");
8
8
  const require_exit = require("./exit-BGi0Kk0y.cjs");
@@ -11,7 +11,7 @@ const require_truncator = require("./truncator-B6aaJaN1.cjs");
11
11
  const require_quickjs_variant = require("./quickjs-variant-CsIwTVFC.cjs");
12
12
  let _bpinternal_zui = require("@bpinternal/zui");
13
13
  //#region package.json
14
- var version = "0.6.5";
14
+ var version = "0.6.6";
15
15
  //#endregion
16
16
  //#region src/hoist.ts
17
17
  /**
@@ -1774,7 +1774,7 @@ const utils = {
1774
1774
  * })
1775
1775
  */
1776
1776
  const execute = async (props) => {
1777
- const { executeContext } = await Promise.resolve().then(() => require("./execute-BJF4Txh2.cjs"));
1777
+ const { executeContext } = await Promise.resolve().then(() => require("./execute-BmEjJjka.cjs"));
1778
1778
  return executeContext(props);
1779
1779
  };
1780
1780
  /**
@@ -1783,9 +1783,9 @@ const execute = async (props) => {
1783
1783
  * It's recommended to call this function at the beginning of your application without awaiting it (void init())
1784
1784
  */
1785
1785
  const init = async () => {
1786
- await Promise.resolve().then(() => require("./execute-BJF4Txh2.cjs"));
1786
+ await Promise.resolve().then(() => require("./execute-BmEjJjka.cjs"));
1787
1787
  await Promise.resolve().then(() => require("./component-BwixNVH8.cjs")).then((n) => n.component_exports);
1788
- await Promise.resolve().then(() => require("./tool-DdPZb0JW.cjs")).then((n) => n.tool_exports);
1788
+ await Promise.resolve().then(() => require("./tool-ReA9gxMp.cjs")).then((n) => n.tool_exports);
1789
1789
  await Promise.resolve().then(() => require("./exit-BGi0Kk0y.cjs")).then((n) => n.exit_exports);
1790
1790
  await Promise.resolve().then(() => require("./jsx-BWvT6ukg.cjs")).then((n) => n.jsx_exports);
1791
1791
  await Promise.resolve().then(() => require("./vm-BjkJqSQH.cjs"));
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@ export { Component, type RenderedComponent, type LeafComponentDefinition, type C
8
8
  export { type Citation, CitationsManager } from './citations.js';
9
9
  export { DefaultComponents } from './component.default.js';
10
10
  export { Snapshot } from './snapshots.js';
11
- export { Chat, type MessageHandler, type MessageDelta, type MessageDeltaHandler } from './chat.js';
11
+ export { Chat, type MessageHandler, type MessageMetadata, type MessageDelta, type MessageDeltaHandler } from './chat.js';
12
12
  import { ExecutionResult } from './result.js';
13
13
  import { type ExecutionProps } from './runtime/types.js';
14
14
  import { stripTruncationTags, truncateWrappedContent, wrapContent } from './truncator.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { a as StreamingMessageParser, c as NAME_REGEX, o as tryParseJson, r as generateInstructions, s as MARKER } from "./dual-modes-D_p-jedw.js";
2
- import { a as ThinkExit, c as ExecutionResult, d as Snapshot, f as parseExit, i as ListenExit, l as PartialExecutionResult, o as getValue, r as DefaultExit, s as ErrorExecutionResult, t as _CustomModelClient, u as SuccessExecutionResult } from "./custom-client-BnlDpjdI.js";
2
+ import { a as ThinkExit, c as ExecutionResult, d as Snapshot, f as parseExit, i as ListenExit, l as PartialExecutionResult, o as getValue, r as DefaultExit, s as ErrorExecutionResult, t as _CustomModelClient, u as SuccessExecutionResult } from "./custom-client-CJIThG2H.js";
3
3
  import { a as getMultilineComment, c as isJsonSchema, l as isValidIdentifier, m as toValidObjectName, n as escapeString, p as toValidFunctionName, t as configureTokenizer, u as isZuiSchema, v as upperFirst } from "./utils-UBeAFeEI.js";
4
- import { t as Tool } from "./tool-CAo-BQHs.js";
4
+ import { t as Tool } from "./tool-Bv8N4IPj.js";
5
5
  import { c as SnapshotSignal, l as ThinkSignal, o as LoopExceededError, r as CodeFormattingError } from "./errors-Cg1G0Awu.js";
6
6
  import { a as isPunct, i as isGroup, o as parse, r as formatTypings, s as tokenize, t as getTypings } from "./typings-CrBa7Tyy.js";
7
7
  import { t as Exit } from "./exit-CDdxDBRT.js";
@@ -10,7 +10,7 @@ import { i as wrapContent, n as truncateWrappedContent, t as stripTruncationTags
10
10
  import { t as configureQuickJS } from "./quickjs-variant-BYhMBpwU.js";
11
11
  import { transforms, z } from "@bpinternal/zui";
12
12
  //#region package.json
13
- var version = "0.6.5";
13
+ var version = "0.6.6";
14
14
  //#endregion
15
15
  //#region src/hoist.ts
16
16
  /**
@@ -1773,7 +1773,7 @@ const utils = {
1773
1773
  * })
1774
1774
  */
1775
1775
  const execute = async (props) => {
1776
- const { executeContext } = await import("./execute-BjBBTshA.js");
1776
+ const { executeContext } = await import("./execute-sn9RT_IN.js");
1777
1777
  return executeContext(props);
1778
1778
  };
1779
1779
  /**
@@ -1782,9 +1782,9 @@ const execute = async (props) => {
1782
1782
  * It's recommended to call this function at the beginning of your application without awaiting it (void init())
1783
1783
  */
1784
1784
  const init = async () => {
1785
- await import("./execute-BjBBTshA.js");
1785
+ await import("./execute-sn9RT_IN.js");
1786
1786
  await import("./component-DGG7d_Mz.js").then((n) => n.r);
1787
- await import("./tool-CAo-BQHs.js").then((n) => n.n);
1787
+ await import("./tool-Bv8N4IPj.js").then((n) => n.n);
1788
1788
  await import("./exit-CDdxDBRT.js").then((n) => n.n);
1789
1789
  await import("./jsx-CIgdGGVs.js").then((n) => n.i);
1790
1790
  await import("./vm-C5vf1LH3.js");
@@ -52,7 +52,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
52
52
  }) : target, mod));
53
53
  //#endregion
54
54
  //#region package.json
55
- var version = "0.6.5";
55
+ var version = "0.6.6";
56
56
  //#endregion
57
57
  //#region ../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/_freeGlobal.js
58
58
  var freeGlobal;
@@ -7682,7 +7682,10 @@ var init_tool = __esmMin((() => {
7682
7682
  if (done) return value;
7683
7683
  if (yieldIndex >= yieldedCount) {
7684
7684
  setYieldedCount(yieldIndex + 1);
7685
- await chat?.handler?.(value);
7685
+ await chat?.handler?.(value, {
7686
+ iterationId: ctx.iterationId ?? ctx.callId,
7687
+ id: `${ctx.callId}:yield-${yieldIndex}`
7688
+ });
7686
7689
  }
7687
7690
  yieldIndex++;
7688
7691
  }
@@ -12215,8 +12218,14 @@ var init_context = __esmMin((() => {
12215
12218
  */
12216
12219
  maxTimeToFirstToken;
12217
12220
  /**
12218
- * Allow Cognitive mid-stream fallback, buffering delivery until generation
12219
- * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
12221
+ * Allow Cognitive to restart a failed stream on another model. With fallback
12222
+ * enabled, there is no buffered delivery and no final delivery queue: delta
12223
+ * text and completed Chat.handler sends stream immediately during generation.
12224
+ * A reset-only delta (restart: true) on Chat.onMessageDelta invalidates ALL
12225
+ * current-iteration messages, including completed sends — consumers must
12226
+ * retract/replace them, which is unsafe for irreversible external transports.
12227
+ * Only TOOL/CODE execution waits for a successful stream. Streaming-only;
12228
+ * defaults to false, which leaves progressive sending unchanged.
12220
12229
  */
12221
12230
  midStreamFallback;
12222
12231
  /**
@@ -17100,11 +17109,18 @@ var init_generate = __esmMin((() => {
17100
17109
  /** Milliseconds between the stream request and the first/last streamed tokens. */
17101
17110
  let timeToFirstToken;
17102
17111
  let timeToLastToken;
17103
- const bufferedDelivery = ctx.midStreamFallback === true;
17104
- let pendingDeliveries = [];
17105
- const deliver = async (callback) => {
17106
- if (bufferedDelivery) pendingDeliveries.push(callback);
17107
- else await callback();
17112
+ const midStreamFallback = ctx.midStreamFallback === true;
17113
+ let attempt = 1;
17114
+ const messageMetadata = (itemId) => ({
17115
+ iterationId: iteration.id,
17116
+ id: midStreamFallback ? `${iteration.id}:${attempt}:${itemId}` : `${iteration.id}:${itemId}`
17117
+ });
17118
+ const preview = async (delta) => {
17119
+ try {
17120
+ await onSendDelta?.(delta);
17121
+ } catch (err) {
17122
+ if (delta.restart) throw new CognitiveError(`LLM stream restart handler failed: ${getErrorMessage(err)}`);
17123
+ }
17108
17124
  };
17109
17125
  const liveItems = /* @__PURE__ */ new Map();
17110
17126
  const liveContent = /* @__PURE__ */ new Map();
@@ -17115,14 +17131,11 @@ var init_generate = __esmMin((() => {
17115
17131
  liveItems.set(event.item.id, event.item);
17116
17132
  if (event.item.kind === "run" && !codeGenerationTraced) {
17117
17133
  codeGenerationTraced = true;
17118
- const started_at = Date.now();
17119
- await deliver(() => {
17120
- traces.push({
17121
- type: "code_generation_started",
17122
- started_at
17123
- });
17124
- onRunStart?.();
17134
+ traces.push({
17135
+ type: "code_generation_started",
17136
+ started_at: Date.now()
17125
17137
  });
17138
+ onRunStart?.();
17126
17139
  }
17127
17140
  } else if (event.type === "body-delta" && onSendDelta) {
17128
17141
  const item = liveItems.get(event.itemId);
@@ -17130,29 +17143,24 @@ var init_generate = __esmMin((() => {
17130
17143
  const content = (liveContent.get(item.id) ?? "") + event.delta;
17131
17144
  liveContent.set(item.id, content);
17132
17145
  const delta = {
17133
- id: `${iteration.id}:${item.id}`,
17146
+ restart: false,
17147
+ ...messageMetadata(item.id),
17134
17148
  component: item.name,
17135
- props: bufferedDelivery ? structuredClone(item.props) : item.props,
17149
+ props: item.props,
17136
17150
  delta: event.delta,
17137
17151
  content
17138
17152
  };
17139
- await deliver(async () => {
17140
- try {
17141
- await onSendDelta(delta);
17142
- } catch (err) {}
17143
- });
17153
+ await preview(delta);
17144
17154
  } else if (event.type === "item-complete") {
17145
- if (event.item.kind === "send" && onSend) {
17146
- const send = {
17147
- name: event.item.name,
17148
- props: bufferedDelivery ? structuredClone(event.item.props) : event.item.props,
17149
- body: event.item.body
17150
- };
17151
- await deliver(() => onSend(send));
17152
- } else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
17155
+ if (event.item.kind === "send" && onSend) await onSend({
17156
+ name: event.item.name,
17157
+ props: event.item.props,
17158
+ body: event.item.body
17159
+ }, messageMetadata(event.item.id));
17160
+ else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
17153
17161
  runCompleted = true;
17154
17162
  const code = (event.item.body ?? "").trim();
17155
- await deliver(() => onRunComplete?.(code));
17163
+ onRunComplete?.(code);
17156
17164
  }
17157
17165
  }
17158
17166
  };
@@ -17163,10 +17171,10 @@ var init_generate = __esmMin((() => {
17163
17171
  const requestedAt = Date.now();
17164
17172
  const stream = cognitive.generateTextStream({
17165
17173
  ...input,
17166
- ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
17174
+ ...ctx.maxTimeToFirstToken || midStreamFallback ? { options: {
17167
17175
  ...input.options,
17168
17176
  ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
17169
- ...bufferedDelivery ? { midStreamFallback: true } : {}
17177
+ ...midStreamFallback ? { midStreamFallback: true } : {}
17170
17178
  } } : {}
17171
17179
  }, { signal: streamController.signal });
17172
17180
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -17198,7 +17206,7 @@ var init_generate = __esmMin((() => {
17198
17206
  streamCompleted = true;
17199
17207
  break;
17200
17208
  }
17201
- if (chunk.value?.restart && !bufferedDelivery) {
17209
+ if (chunk.value?.restart && !midStreamFallback) {
17202
17210
  streamController.abort("Unexpected LLM stream restart");
17203
17211
  throw new CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
17204
17212
  }
@@ -17213,10 +17221,15 @@ var init_generate = __esmMin((() => {
17213
17221
  fence = new LeadingFenceFilter();
17214
17222
  liveItems.clear();
17215
17223
  liveContent.clear();
17216
- pendingDeliveries = [];
17217
17224
  codeGenerationTraced = false;
17218
17225
  runCompleted = false;
17219
17226
  responseMetadata = void 0;
17227
+ attempt = chunk.value.restart.attempt;
17228
+ await preview({
17229
+ ...chunk.value.restart,
17230
+ restart: true,
17231
+ iterationId: iteration.id
17232
+ });
17220
17233
  timeToFirstToken = void 0;
17221
17234
  timeToLastToken = void 0;
17222
17235
  continue;
@@ -17236,16 +17249,9 @@ var init_generate = __esmMin((() => {
17236
17249
  assistantResponse = toParsedAssistantResponse(parser.items, raw);
17237
17250
  } finally {
17238
17251
  streamController.abort("LLM stream closed");
17239
- if (!streamCompleted) stream.return(void 0).catch(() => {});
17240
- }
17241
- if (bufferedDelivery) {
17242
- controller.signal.throwIfAborted();
17243
- for (const callback of pendingDeliveries) {
17244
- controller.signal.throwIfAborted();
17245
- await callback();
17246
- }
17247
- pendingDeliveries = [];
17252
+ if (!streamCompleted) stream.return(void 0).catch((err) => {});
17248
17253
  }
17254
+ if (midStreamFallback) controller.signal.throwIfAborted();
17249
17255
  } else {
17250
17256
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
17251
17257
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
@@ -17254,7 +17260,7 @@ var init_generate = __esmMin((() => {
17254
17260
  responseMetadata = response.metadata;
17255
17261
  raw = response.output;
17256
17262
  assistantResponse = ctx.version.parseAssistantResponse(raw);
17257
- for (const send of assistantResponse.sends) await onSend?.(send);
17263
+ for (const [index, send] of assistantResponse.sends.entries()) await onSend?.(send, messageMetadata(`send-${index}`));
17258
17264
  }
17259
17265
  iteration.code = assistantResponse.code;
17260
17266
  iteration.sends = assistantResponse.sends;
@@ -17534,7 +17540,10 @@ function wrapTool({ chat, tool, traces, object, iteration, beforeHook, afterHook
17534
17540
  toolCallId
17535
17541
  });
17536
17542
  if (typeof beforeRes?.input !== "undefined") effectiveInput = beforeRes.input;
17537
- output = await tool.execute(effectiveInput, { callId: toolCallId }, chat);
17543
+ output = await tool.execute(effectiveInput, {
17544
+ callId: toolCallId,
17545
+ iterationId: iteration.id
17546
+ }, chat);
17538
17547
  const afterRes = await afterHook?.({
17539
17548
  iteration,
17540
17549
  tool,
@@ -17887,7 +17896,7 @@ var init_execute = __esmMin((() => {
17887
17896
  promise: runCode(code)
17888
17897
  };
17889
17898
  } : void 0,
17890
- onSend: async (send) => {
17899
+ onSend: async (send, messageMetadata) => {
17891
17900
  if (!ctx.chat) return;
17892
17901
  const sendStartedAt = Date.now();
17893
17902
  const component = createJsxComponent({
@@ -17896,7 +17905,7 @@ var init_execute = __esmMin((() => {
17896
17905
  children: send.body ? [send.body] : []
17897
17906
  });
17898
17907
  try {
17899
- await ctx.chat.handler(component);
17908
+ await ctx.chat.handler(component, messageMetadata);
17900
17909
  } catch (err) {
17901
17910
  throw new Error(`Error while sending message (■send=${send.name}): ${getErrorMessage(err)}`);
17902
17911
  }
@@ -1,4 +1,4 @@
1
- import type { MessageDelta } from '../chat.js';
1
+ import type { MessageDelta, MessageMetadata } from '../chat.js';
2
2
  import { Context, Iteration } from '../context.js';
3
3
  import type { ParsedSend } from '../prompts/prompt.js';
4
4
  import { RuntimeCognitive } from './types.js';
@@ -12,10 +12,11 @@ type GenerateCodeProps = {
12
12
  * Called for each completed `■send` block. On streaming clients this fires
13
13
  * while the model is still generating — messages are delivered progressively.
14
14
  */
15
- onSend?: (send: ParsedSend) => Promise<void>;
15
+ onSend?: (send: ParsedSend, metadata: MessageMetadata) => Promise<void>;
16
16
  /**
17
17
  * Called for each `■send` body chunk as it is parsed from the stream
18
- * (streaming clients only). Best-effort: errors are ignored.
18
+ * (streaming clients only), or with a restart delta before replacement output.
19
+ * Text errors are best-effort; restart errors terminate generation.
19
20
  */
20
21
  onSendDelta?: (delta: MessageDelta) => Promise<void> | void;
21
22
  /**
@@ -123,10 +123,13 @@ type Options = Partial<Pick<Context, 'loop' | 'timeout'>> & {
123
123
  */
124
124
  maxTimeToFirstToken?: number;
125
125
  /**
126
- * Allow Cognitive to restart failed streams on another model. Buffers message
127
- * previews, sends, and code execution until generation succeeds; abandoned
128
- * attempts deliver nothing. Streaming-only; defaults to false, preserving
129
- * progressive sends and early code execution.
126
+ * Allow Cognitive to restart failed streams on another model. No buffered
127
+ * delivery and no final delivery queue: delta text and completed Chat.handler
128
+ * sends stream immediately during generation. A reset-only delta (restart:
129
+ * true) on Chat.onMessageDelta invalidates ALL current-iteration messages,
130
+ * including completed sends — consumers must retract/replace them (not safe for
131
+ * irreversible transports). Only TOOL/CODE execution waits for a successful
132
+ * stream. Streaming-only; defaults to false, preserving progressive sending.
130
133
  */
131
134
  midStreamFallback?: boolean;
132
135
  /**
@@ -500,7 +500,10 @@ var Tool = class Tool {
500
500
  if (yieldIndex >= yieldedCount) {
501
501
  var _chat$handler;
502
502
  setYieldedCount(yieldIndex + 1);
503
- await (chat === null || chat === void 0 || (_chat$handler = chat.handler) === null || _chat$handler === void 0 ? void 0 : _chat$handler.call(chat, value));
503
+ await (chat === null || chat === void 0 || (_chat$handler = chat.handler) === null || _chat$handler === void 0 ? void 0 : _chat$handler.call(chat, value, {
504
+ iterationId: ctx.iterationId ?? ctx.callId,
505
+ id: `${ctx.callId}:yield-${yieldIndex}`
506
+ }));
504
507
  }
505
508
  yieldIndex++;
506
509
  }
@@ -500,7 +500,10 @@ var Tool = class Tool {
500
500
  if (yieldIndex >= yieldedCount) {
501
501
  var _chat$handler;
502
502
  setYieldedCount(yieldIndex + 1);
503
- await (chat === null || chat === void 0 || (_chat$handler = chat.handler) === null || _chat$handler === void 0 ? void 0 : _chat$handler.call(chat, value));
503
+ await (chat === null || chat === void 0 || (_chat$handler = chat.handler) === null || _chat$handler === void 0 ? void 0 : _chat$handler.call(chat, value, {
504
+ iterationId: ctx.iterationId ?? ctx.callId,
505
+ id: `${ctx.callId}:yield-${yieldIndex}`
506
+ }));
504
507
  }
505
508
  yieldIndex++;
506
509
  }
package/dist/tool.d.ts CHANGED
@@ -49,6 +49,8 @@ type SmartPartial<T> = IsObject<T> extends true ? Partial<T> : T;
49
49
  type ToolCallContext = {
50
50
  /** Unique identifier for this specific tool call */
51
51
  callId: string;
52
+ /** Set by the runtime; standalone calls use callId as their message scope. */
53
+ iterationId?: string;
52
54
  };
53
55
  export declare namespace Tool {
54
56
  type JSON = {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "llmz",
3
3
  "type": "module",
4
4
  "description": "LLMz - An LLM-native Typescript VM built on top of Zui",
5
- "version": "0.6.5",
5
+ "version": "0.6.6",
6
6
  "files": [
7
7
  "dist",
8
8
  "DOCS.md"