llmz 0.6.3 → 0.6.5

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,6 +1582,31 @@ await execute({
1582
1582
 
1583
1583
  ---
1584
1584
 
1585
+ ### Buffered Delivery (midStreamFallback)
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.
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:
1590
+
1591
+ ```typescript
1592
+ await execute({
1593
+ // ...
1594
+ options: { midStreamFallback: true },
1595
+ })
1596
+ ```
1597
+
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:
1599
+
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
+ - **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.
1605
+
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.
1607
+
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.
1609
+
1585
1610
  ## API Reference
1586
1611
 
1587
1612
  ### Core Functions
package/dist/context.d.ts CHANGED
@@ -564,6 +564,11 @@ export declare class Context implements Serializable<Context.JSON> {
564
564
  * before the cognitive service falls back to the next model/provider.
565
565
  */
566
566
  maxTimeToFirstToken?: number;
567
+ /**
568
+ * Allow Cognitive mid-stream fallback, buffering delivery until generation
569
+ * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
570
+ */
571
+ midStreamFallback?: boolean;
567
572
  /**
568
573
  * STT model used by the cognitive service to transcribe audio attachments
569
574
  * when the target LLM does not support audio natively. Defaults to 'fast'.
@@ -600,6 +605,7 @@ export declare class Context implements Serializable<Context.JSON> {
600
605
  timeout?: number;
601
606
  maxTokens?: number;
602
607
  maxTimeToFirstToken?: number;
608
+ midStreamFallback?: boolean;
603
609
  transcriptionModel?: SttModels;
604
610
  });
605
611
  toJSON(): {
@@ -2254,6 +2254,11 @@ 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.
2259
+ */
2260
+ midStreamFallback;
2261
+ /**
2257
2262
  * STT model used by the cognitive service to transcribe audio attachments
2258
2263
  * when the target LLM does not support audio natively. Defaults to 'fast'.
2259
2264
  */
@@ -2509,10 +2514,12 @@ var Context = class {
2509
2514
  this.snapshot = props.snapshot;
2510
2515
  this.maxTokens = props.maxTokens;
2511
2516
  this.maxTimeToFirstToken = props.maxTimeToFirstToken;
2517
+ this.midStreamFallback = props.midStreamFallback;
2512
2518
  this.transcriptionModel = props.transcriptionModel;
2513
2519
  if (this.loop < 1 || this.loop > 100) throw new Error("Invalid loop. Expected a number between 1 and 100.");
2514
2520
  if (this.maxTokens !== void 0 && (!Number.isFinite(this.maxTokens) || this.maxTokens < 1)) throw new Error("Invalid maxTokens. Expected a positive number.");
2515
2521
  if (this.maxTimeToFirstToken !== void 0 && (!Number.isFinite(this.maxTimeToFirstToken) || this.maxTimeToFirstToken < 1)) throw new Error("Invalid maxTimeToFirstToken. Expected a positive number of milliseconds.");
2522
+ if (this.midStreamFallback !== void 0 && typeof this.midStreamFallback !== "boolean") throw new Error("Invalid midStreamFallback. Expected a boolean.");
2516
2523
  }
2517
2524
  toJSON() {
2518
2525
  var _this$snapshot4;
@@ -2254,6 +2254,11 @@ 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.
2259
+ */
2260
+ midStreamFallback;
2261
+ /**
2257
2262
  * STT model used by the cognitive service to transcribe audio attachments
2258
2263
  * when the target LLM does not support audio natively. Defaults to 'fast'.
2259
2264
  */
@@ -2509,10 +2514,12 @@ var Context = class {
2509
2514
  this.snapshot = props.snapshot;
2510
2515
  this.maxTokens = props.maxTokens;
2511
2516
  this.maxTimeToFirstToken = props.maxTimeToFirstToken;
2517
+ this.midStreamFallback = props.midStreamFallback;
2512
2518
  this.transcriptionModel = props.transcriptionModel;
2513
2519
  if (this.loop < 1 || this.loop > 100) throw new Error("Invalid loop. Expected a number between 1 and 100.");
2514
2520
  if (this.maxTokens !== void 0 && (!Number.isFinite(this.maxTokens) || this.maxTokens < 1)) throw new Error("Invalid maxTokens. Expected a positive number.");
2515
2521
  if (this.maxTimeToFirstToken !== void 0 && (!Number.isFinite(this.maxTimeToFirstToken) || this.maxTimeToFirstToken < 1)) throw new Error("Invalid maxTimeToFirstToken. Expected a positive number of milliseconds.");
2522
+ if (this.midStreamFallback !== void 0 && typeof this.midStreamFallback !== "boolean") throw new Error("Invalid midStreamFallback. Expected a boolean.");
2516
2523
  }
2517
2524
  toJSON() {
2518
2525
  var _this$snapshot4;
@@ -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-BPmEoWKu.cjs");
3
+ const require_custom_client = require("./custom-client-BX8gx0tI.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");
@@ -121,7 +121,7 @@ const RESPONSE_LENGTH_BUFFER = {
121
121
  PERCENTAGE: .1
122
122
  };
123
123
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
124
- const STREAM_INACTIVITY_TIMEOUT = 6e4;
124
+ const STREAM_INACTIVITY_TIMEOUT = 18e4;
125
125
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
126
126
  /**
127
127
  * Models sometimes wrap their whole response in a code fence. The fence has to
@@ -197,6 +197,12 @@ 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();
205
+ };
200
206
  const liveItems = /* @__PURE__ */ new Map();
201
207
  const liveContent = /* @__PURE__ */ new Map();
202
208
  let codeGenerationTraced = false;
@@ -206,48 +212,58 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
206
212
  liveItems.set(event.item.id, event.item);
207
213
  if (event.item.kind === "run" && !codeGenerationTraced) {
208
214
  codeGenerationTraced = true;
209
- traces.push({
210
- type: "code_generation_started",
211
- started_at: Date.now()
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();
212
222
  });
213
- onRunStart === null || onRunStart === void 0 || onRunStart();
214
223
  }
215
224
  } else if (event.type === "body-delta" && onSendDelta) {
216
225
  const item = liveItems.get(event.itemId);
217
226
  if ((item === null || item === void 0 ? void 0 : item.kind) !== "send") continue;
218
227
  const content = (liveContent.get(item.id) ?? "") + event.delta;
219
228
  liveContent.set(item.id, content);
220
- try {
221
- await onSendDelta({
222
- id: `${iteration.id}:${item.id}`,
223
- component: item.name,
224
- props: item.props,
225
- delta: event.delta,
226
- content
227
- });
228
- } catch (err) {}
229
- } else if (event.type === "item-complete") {
230
- if (event.item.kind === "send" && onSend) await onSend({
231
- name: event.item.name,
232
- props: event.item.props,
233
- body: event.item.body
229
+ const delta = {
230
+ id: `${iteration.id}:${item.id}`,
231
+ component: item.name,
232
+ props: bufferedDelivery ? structuredClone(item.props) : item.props,
233
+ delta: event.delta,
234
+ content
235
+ };
236
+ await deliver(async () => {
237
+ try {
238
+ await onSendDelta(delta);
239
+ } catch (err) {}
234
240
  });
235
- else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
241
+ } 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) {
236
250
  runCompleted = true;
237
- onRunComplete === null || onRunComplete === void 0 || onRunComplete((event.item.body ?? "").trim());
251
+ const code = (event.item.body ?? "").trim();
252
+ await deliver(() => onRunComplete === null || onRunComplete === void 0 ? void 0 : onRunComplete(code));
238
253
  }
239
254
  }
240
255
  };
241
256
  if (typeof cognitive.generateTextStream === "function") {
242
- const parser = new require_dual_modes.StreamingMessageParser();
243
- const fence = new LeadingFenceFilter();
257
+ let parser = new require_dual_modes.StreamingMessageParser();
258
+ let fence = new LeadingFenceFilter();
244
259
  const streamController = createJoinedAbortController([controller.signal]);
245
260
  const requestedAt = Date.now();
246
261
  const stream = cognitive.generateTextStream({
247
262
  ...input,
248
- ...ctx.maxTimeToFirstToken ? { options: {
263
+ ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
249
264
  ...input.options,
250
- maxTimeToFirstToken: ctx.maxTimeToFirstToken
265
+ ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
266
+ ...bufferedDelivery ? { midStreamFallback: true } : {}
251
267
  } } : {}
252
268
  }, { signal: streamController.signal });
253
269
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -266,28 +282,68 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
266
282
  }
267
283
  };
268
284
  raw = "";
269
- while (true) {
270
- var _chunk$value, _chunk$value2;
271
- let chunk;
272
- try {
273
- chunk = await nextChunk();
274
- } catch (thrown) {
275
- throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
285
+ let streamCompleted = false;
286
+ try {
287
+ while (true) {
288
+ var _chunk$value, _chunk$value2, _chunk$value3, _chunk$value4;
289
+ let chunk;
290
+ try {
291
+ chunk = await nextChunk();
292
+ } catch (thrown) {
293
+ throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
294
+ }
295
+ if (chunk.done) {
296
+ streamCompleted = true;
297
+ break;
298
+ }
299
+ if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !bufferedDelivery) {
300
+ streamController.abort("Unexpected LLM stream restart");
301
+ throw new require_errors.CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
302
+ }
303
+ if ((_chunk$value2 = chunk.value) === null || _chunk$value2 === void 0 ? void 0 : _chunk$value2.restart) {
304
+ traces.push({
305
+ type: "llm_call_restarted",
306
+ started_at: Date.now(),
307
+ ...chunk.value.restart
308
+ });
309
+ raw = "";
310
+ parser = new require_dual_modes.StreamingMessageParser();
311
+ fence = new LeadingFenceFilter();
312
+ liveItems.clear();
313
+ liveContent.clear();
314
+ pendingDeliveries = [];
315
+ codeGenerationTraced = false;
316
+ runCompleted = false;
317
+ responseMetadata = void 0;
318
+ timeToFirstToken = void 0;
319
+ timeToLastToken = void 0;
320
+ continue;
321
+ }
322
+ if ((_chunk$value3 = chunk.value) === null || _chunk$value3 === void 0 ? void 0 : _chunk$value3.metadata) responseMetadata = chunk.value.metadata;
323
+ const delta = (_chunk$value4 = chunk.value) === null || _chunk$value4 === void 0 ? void 0 : _chunk$value4.output;
324
+ if (!delta) continue;
325
+ timeToLastToken = Date.now() - requestedAt;
326
+ timeToFirstToken ??= timeToLastToken;
327
+ raw += delta;
328
+ await dispatchSends(parser.push(fence.push(delta)));
329
+ }
330
+ if (!responseMetadata) throw new require_errors.CognitiveError("LLM streaming completed without metadata");
331
+ const remaining = fence.flush();
332
+ if (remaining) await dispatchSends(parser.push(remaining));
333
+ await dispatchSends(parser.finish());
334
+ assistantResponse = require_dual_modes.toParsedAssistantResponse(parser.items, raw);
335
+ } finally {
336
+ 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();
276
344
  }
277
- if (chunk.done) break;
278
- if ((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.metadata) responseMetadata = chunk.value.metadata;
279
- const delta = (_chunk$value2 = chunk.value) === null || _chunk$value2 === void 0 ? void 0 : _chunk$value2.output;
280
- if (!delta) continue;
281
- timeToLastToken = Date.now() - requestedAt;
282
- timeToFirstToken ??= timeToLastToken;
283
- raw += delta;
284
- await dispatchSends(parser.push(fence.push(delta)));
345
+ pendingDeliveries = [];
285
346
  }
286
- if (!responseMetadata) throw new require_errors.CognitiveError("LLM streaming completed without metadata");
287
- const remaining = fence.flush();
288
- if (remaining) await dispatchSends(parser.push(remaining));
289
- await dispatchSends(parser.finish());
290
- assistantResponse = require_dual_modes.toParsedAssistantResponse(parser.items, raw);
291
347
  } else {
292
348
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
293
349
  throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
@@ -329,7 +385,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
329
385
  type: "llm_call_success",
330
386
  started_at: startedAt,
331
387
  ended_at: iteration.llm.ended_at,
332
- model: model.id,
388
+ model: iteration.llm.model,
333
389
  code: iteration.code ?? ""
334
390
  });
335
391
  };
@@ -677,7 +733,7 @@ const executeContext = async (props) => {
677
733
  return result;
678
734
  };
679
735
  const executeContextInternal = async (props) => {
680
- var _props$options, _props$options2, _props$options3, _props$options4, _props$options5;
736
+ var _props$options, _props$options2, _props$options3, _props$options4, _props$options5, _props$options6;
681
737
  const controller = createJoinedAbortController([props.signal]);
682
738
  const { onIterationStart, onIterationEnd, onTrace, onExit, onBeforeExecution, onAfterTool, onBeforeTool } = props;
683
739
  const client = props.client ?? new _botpress_client.Client();
@@ -691,7 +747,8 @@ const executeContextInternal = async (props) => {
691
747
  timeout: (_props$options2 = props.options) === null || _props$options2 === void 0 ? void 0 : _props$options2.timeout,
692
748
  maxTokens: (_props$options3 = props.options) === null || _props$options3 === void 0 ? void 0 : _props$options3.maxTokens,
693
749
  maxTimeToFirstToken: (_props$options4 = props.options) === null || _props$options4 === void 0 ? void 0 : _props$options4.maxTimeToFirstToken,
694
- transcriptionModel: (_props$options5 = props.options) === null || _props$options5 === void 0 ? void 0 : _props$options5.transcriptionModel,
750
+ midStreamFallback: (_props$options5 = props.options) === null || _props$options5 === void 0 ? void 0 : _props$options5.midStreamFallback,
751
+ transcriptionModel: (_props$options6 = props.options) === null || _props$options6 === void 0 ? void 0 : _props$options6.transcriptionModel,
695
752
  exits: props.exits,
696
753
  snapshot: props.snapshot,
697
754
  model: props.model,
@@ -884,7 +941,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
884
941
  };
885
942
  });
886
943
  };
887
- const canExecuteEarly = typeof onBeforeExecution !== "function";
944
+ const canExecuteEarly = typeof onBeforeExecution !== "function" && !ctx.midStreamFallback;
888
945
  let earlyExecution;
889
946
  await generateCode({
890
947
  iteration,
@@ -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-NUoxrFQh.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";
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";
@@ -119,7 +119,7 @@ const RESPONSE_LENGTH_BUFFER = {
119
119
  PERCENTAGE: .1
120
120
  };
121
121
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
122
- const STREAM_INACTIVITY_TIMEOUT = 6e4;
122
+ const STREAM_INACTIVITY_TIMEOUT = 18e4;
123
123
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
124
124
  /**
125
125
  * Models sometimes wrap their whole response in a code fence. The fence has to
@@ -195,6 +195,12 @@ 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();
203
+ };
198
204
  const liveItems = /* @__PURE__ */ new Map();
199
205
  const liveContent = /* @__PURE__ */ new Map();
200
206
  let codeGenerationTraced = false;
@@ -204,48 +210,58 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
204
210
  liveItems.set(event.item.id, event.item);
205
211
  if (event.item.kind === "run" && !codeGenerationTraced) {
206
212
  codeGenerationTraced = true;
207
- traces.push({
208
- type: "code_generation_started",
209
- started_at: Date.now()
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();
210
220
  });
211
- onRunStart === null || onRunStart === void 0 || onRunStart();
212
221
  }
213
222
  } else if (event.type === "body-delta" && onSendDelta) {
214
223
  const item = liveItems.get(event.itemId);
215
224
  if ((item === null || item === void 0 ? void 0 : item.kind) !== "send") continue;
216
225
  const content = (liveContent.get(item.id) ?? "") + event.delta;
217
226
  liveContent.set(item.id, content);
218
- try {
219
- await onSendDelta({
220
- id: `${iteration.id}:${item.id}`,
221
- component: item.name,
222
- props: item.props,
223
- delta: event.delta,
224
- content
225
- });
226
- } catch (err) {}
227
- } else if (event.type === "item-complete") {
228
- if (event.item.kind === "send" && onSend) await onSend({
229
- name: event.item.name,
230
- props: event.item.props,
231
- body: event.item.body
227
+ const delta = {
228
+ id: `${iteration.id}:${item.id}`,
229
+ component: item.name,
230
+ props: bufferedDelivery ? structuredClone(item.props) : item.props,
231
+ delta: event.delta,
232
+ content
233
+ };
234
+ await deliver(async () => {
235
+ try {
236
+ await onSendDelta(delta);
237
+ } catch (err) {}
232
238
  });
233
- else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
239
+ } 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) {
234
248
  runCompleted = true;
235
- onRunComplete === null || onRunComplete === void 0 || onRunComplete((event.item.body ?? "").trim());
249
+ const code = (event.item.body ?? "").trim();
250
+ await deliver(() => onRunComplete === null || onRunComplete === void 0 ? void 0 : onRunComplete(code));
236
251
  }
237
252
  }
238
253
  };
239
254
  if (typeof cognitive.generateTextStream === "function") {
240
- const parser = new StreamingMessageParser();
241
- const fence = new LeadingFenceFilter();
255
+ let parser = new StreamingMessageParser();
256
+ let fence = new LeadingFenceFilter();
242
257
  const streamController = createJoinedAbortController([controller.signal]);
243
258
  const requestedAt = Date.now();
244
259
  const stream = cognitive.generateTextStream({
245
260
  ...input,
246
- ...ctx.maxTimeToFirstToken ? { options: {
261
+ ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
247
262
  ...input.options,
248
- maxTimeToFirstToken: ctx.maxTimeToFirstToken
263
+ ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
264
+ ...bufferedDelivery ? { midStreamFallback: true } : {}
249
265
  } } : {}
250
266
  }, { signal: streamController.signal });
251
267
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -264,28 +280,68 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
264
280
  }
265
281
  };
266
282
  raw = "";
267
- while (true) {
268
- var _chunk$value, _chunk$value2;
269
- let chunk;
270
- try {
271
- chunk = await nextChunk();
272
- } catch (thrown) {
273
- throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
283
+ let streamCompleted = false;
284
+ try {
285
+ while (true) {
286
+ var _chunk$value, _chunk$value2, _chunk$value3, _chunk$value4;
287
+ let chunk;
288
+ try {
289
+ chunk = await nextChunk();
290
+ } catch (thrown) {
291
+ throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
292
+ }
293
+ if (chunk.done) {
294
+ streamCompleted = true;
295
+ break;
296
+ }
297
+ if (((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.restart) && !bufferedDelivery) {
298
+ streamController.abort("Unexpected LLM stream restart");
299
+ throw new CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
300
+ }
301
+ if ((_chunk$value2 = chunk.value) === null || _chunk$value2 === void 0 ? void 0 : _chunk$value2.restart) {
302
+ traces.push({
303
+ type: "llm_call_restarted",
304
+ started_at: Date.now(),
305
+ ...chunk.value.restart
306
+ });
307
+ raw = "";
308
+ parser = new StreamingMessageParser();
309
+ fence = new LeadingFenceFilter();
310
+ liveItems.clear();
311
+ liveContent.clear();
312
+ pendingDeliveries = [];
313
+ codeGenerationTraced = false;
314
+ runCompleted = false;
315
+ responseMetadata = void 0;
316
+ timeToFirstToken = void 0;
317
+ timeToLastToken = void 0;
318
+ continue;
319
+ }
320
+ if ((_chunk$value3 = chunk.value) === null || _chunk$value3 === void 0 ? void 0 : _chunk$value3.metadata) responseMetadata = chunk.value.metadata;
321
+ const delta = (_chunk$value4 = chunk.value) === null || _chunk$value4 === void 0 ? void 0 : _chunk$value4.output;
322
+ if (!delta) continue;
323
+ timeToLastToken = Date.now() - requestedAt;
324
+ timeToFirstToken ??= timeToLastToken;
325
+ raw += delta;
326
+ await dispatchSends(parser.push(fence.push(delta)));
327
+ }
328
+ if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
329
+ const remaining = fence.flush();
330
+ if (remaining) await dispatchSends(parser.push(remaining));
331
+ await dispatchSends(parser.finish());
332
+ assistantResponse = toParsedAssistantResponse(parser.items, raw);
333
+ } finally {
334
+ 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();
274
342
  }
275
- if (chunk.done) break;
276
- if ((_chunk$value = chunk.value) === null || _chunk$value === void 0 ? void 0 : _chunk$value.metadata) responseMetadata = chunk.value.metadata;
277
- const delta = (_chunk$value2 = chunk.value) === null || _chunk$value2 === void 0 ? void 0 : _chunk$value2.output;
278
- if (!delta) continue;
279
- timeToLastToken = Date.now() - requestedAt;
280
- timeToFirstToken ??= timeToLastToken;
281
- raw += delta;
282
- await dispatchSends(parser.push(fence.push(delta)));
343
+ pendingDeliveries = [];
283
344
  }
284
- if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
285
- const remaining = fence.flush();
286
- if (remaining) await dispatchSends(parser.push(remaining));
287
- await dispatchSends(parser.finish());
288
- assistantResponse = toParsedAssistantResponse(parser.items, raw);
289
345
  } else {
290
346
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
291
347
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
@@ -327,7 +383,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
327
383
  type: "llm_call_success",
328
384
  started_at: startedAt,
329
385
  ended_at: iteration.llm.ended_at,
330
- model: model.id,
386
+ model: iteration.llm.model,
331
387
  code: iteration.code ?? ""
332
388
  });
333
389
  };
@@ -675,7 +731,7 @@ const executeContext = async (props) => {
675
731
  return result;
676
732
  };
677
733
  const executeContextInternal = async (props) => {
678
- var _props$options, _props$options2, _props$options3, _props$options4, _props$options5;
734
+ var _props$options, _props$options2, _props$options3, _props$options4, _props$options5, _props$options6;
679
735
  const controller = createJoinedAbortController([props.signal]);
680
736
  const { onIterationStart, onIterationEnd, onTrace, onExit, onBeforeExecution, onAfterTool, onBeforeTool } = props;
681
737
  const client = props.client ?? new Client();
@@ -689,7 +745,8 @@ const executeContextInternal = async (props) => {
689
745
  timeout: (_props$options2 = props.options) === null || _props$options2 === void 0 ? void 0 : _props$options2.timeout,
690
746
  maxTokens: (_props$options3 = props.options) === null || _props$options3 === void 0 ? void 0 : _props$options3.maxTokens,
691
747
  maxTimeToFirstToken: (_props$options4 = props.options) === null || _props$options4 === void 0 ? void 0 : _props$options4.maxTimeToFirstToken,
692
- transcriptionModel: (_props$options5 = props.options) === null || _props$options5 === void 0 ? void 0 : _props$options5.transcriptionModel,
748
+ midStreamFallback: (_props$options5 = props.options) === null || _props$options5 === void 0 ? void 0 : _props$options5.midStreamFallback,
749
+ transcriptionModel: (_props$options6 = props.options) === null || _props$options6 === void 0 ? void 0 : _props$options6.transcriptionModel,
693
750
  exits: props.exits,
694
751
  snapshot: props.snapshot,
695
752
  model: props.model,
@@ -882,7 +939,7 @@ const executeIteration = async ({ iteration, ctx, cognitive, controller, onExit,
882
939
  };
883
940
  });
884
941
  };
885
- const canExecuteEarly = typeof onBeforeExecution !== "function";
942
+ const canExecuteEarly = typeof onBeforeExecution !== "function" && !ctx.midStreamFallback;
886
943
  let earlyExecution;
887
944
  await generateCode({
888
945
  iteration,
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
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-BPmEoWKu.cjs");
3
+ const require_custom_client = require("./custom-client-BX8gx0tI.cjs");
4
4
  const require_utils = require("./utils-ZpJm00ed.cjs");
5
5
  const require_tool = require("./tool-DdPZb0JW.cjs");
6
6
  const require_errors = require("./errors-C962uJdS.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.3";
14
+ var version = "0.6.5";
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-BEaq3jkV.cjs"));
1777
+ const { executeContext } = await Promise.resolve().then(() => require("./execute-BJF4Txh2.cjs"));
1778
1778
  return executeContext(props);
1779
1779
  };
1780
1780
  /**
@@ -1783,7 +1783,7 @@ 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-BEaq3jkV.cjs"));
1786
+ await Promise.resolve().then(() => require("./execute-BJF4Txh2.cjs"));
1787
1787
  await Promise.resolve().then(() => require("./component-BwixNVH8.cjs")).then((n) => n.component_exports);
1788
1788
  await Promise.resolve().then(() => require("./tool-DdPZb0JW.cjs")).then((n) => n.tool_exports);
1789
1789
  await Promise.resolve().then(() => require("./exit-BGi0Kk0y.cjs")).then((n) => n.exit_exports);
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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-NUoxrFQh.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";
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
4
  import { t as Tool } from "./tool-CAo-BQHs.js";
5
5
  import { c as SnapshotSignal, l as ThinkSignal, o as LoopExceededError, r as CodeFormattingError } from "./errors-Cg1G0Awu.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.3";
13
+ var version = "0.6.5";
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-DYvFRs54.js");
1776
+ const { executeContext } = await import("./execute-BjBBTshA.js");
1777
1777
  return executeContext(props);
1778
1778
  };
1779
1779
  /**
@@ -1782,7 +1782,7 @@ 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-DYvFRs54.js");
1785
+ await import("./execute-BjBBTshA.js");
1786
1786
  await import("./component-DGG7d_Mz.js").then((n) => n.r);
1787
1787
  await import("./tool-CAo-BQHs.js").then((n) => n.n);
1788
1788
  await import("./exit-CDdxDBRT.js").then((n) => n.n);
@@ -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.3";
55
+ var version = "0.6.5";
56
56
  //#endregion
57
57
  //#region ../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/_freeGlobal.js
58
58
  var freeGlobal;
@@ -12215,6 +12215,11 @@ var init_context = __esmMin((() => {
12215
12215
  */
12216
12216
  maxTimeToFirstToken;
12217
12217
  /**
12218
+ * Allow Cognitive mid-stream fallback, buffering delivery until generation
12219
+ * succeeds and discarding abandoned attempts. Streaming-only; defaults to false.
12220
+ */
12221
+ midStreamFallback;
12222
+ /**
12218
12223
  * STT model used by the cognitive service to transcribe audio attachments
12219
12224
  * when the target LLM does not support audio natively. Defaults to 'fast'.
12220
12225
  */
@@ -12449,10 +12454,12 @@ var init_context = __esmMin((() => {
12449
12454
  this.snapshot = props.snapshot;
12450
12455
  this.maxTokens = props.maxTokens;
12451
12456
  this.maxTimeToFirstToken = props.maxTimeToFirstToken;
12457
+ this.midStreamFallback = props.midStreamFallback;
12452
12458
  this.transcriptionModel = props.transcriptionModel;
12453
12459
  if (this.loop < 1 || this.loop > 100) throw new Error("Invalid loop. Expected a number between 1 and 100.");
12454
12460
  if (this.maxTokens !== void 0 && (!Number.isFinite(this.maxTokens) || this.maxTokens < 1)) throw new Error("Invalid maxTokens. Expected a positive number.");
12455
12461
  if (this.maxTimeToFirstToken !== void 0 && (!Number.isFinite(this.maxTimeToFirstToken) || this.maxTimeToFirstToken < 1)) throw new Error("Invalid maxTimeToFirstToken. Expected a positive number of milliseconds.");
12462
+ if (this.midStreamFallback !== void 0 && typeof this.midStreamFallback !== "boolean") throw new Error("Invalid midStreamFallback. Expected a boolean.");
12456
12463
  }
12457
12464
  toJSON() {
12458
12465
  return {
@@ -17023,7 +17030,7 @@ var init_generate = __esmMin((() => {
17023
17030
  MAX_TOKENS: 16e3,
17024
17031
  PERCENTAGE: .1
17025
17032
  };
17026
- STREAM_INACTIVITY_TIMEOUT = 6e4;
17033
+ STREAM_INACTIVITY_TIMEOUT = 18e4;
17027
17034
  getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
17028
17035
  LeadingFenceFilter = class {
17029
17036
  _buffer = "";
@@ -17093,6 +17100,12 @@ var init_generate = __esmMin((() => {
17093
17100
  /** Milliseconds between the stream request and the first/last streamed tokens. */
17094
17101
  let timeToFirstToken;
17095
17102
  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();
17108
+ };
17096
17109
  const liveItems = /* @__PURE__ */ new Map();
17097
17110
  const liveContent = /* @__PURE__ */ new Map();
17098
17111
  let codeGenerationTraced = false;
@@ -17102,48 +17115,58 @@ var init_generate = __esmMin((() => {
17102
17115
  liveItems.set(event.item.id, event.item);
17103
17116
  if (event.item.kind === "run" && !codeGenerationTraced) {
17104
17117
  codeGenerationTraced = true;
17105
- traces.push({
17106
- type: "code_generation_started",
17107
- started_at: Date.now()
17118
+ const started_at = Date.now();
17119
+ await deliver(() => {
17120
+ traces.push({
17121
+ type: "code_generation_started",
17122
+ started_at
17123
+ });
17124
+ onRunStart?.();
17108
17125
  });
17109
- onRunStart?.();
17110
17126
  }
17111
17127
  } else if (event.type === "body-delta" && onSendDelta) {
17112
17128
  const item = liveItems.get(event.itemId);
17113
17129
  if (item?.kind !== "send") continue;
17114
17130
  const content = (liveContent.get(item.id) ?? "") + event.delta;
17115
17131
  liveContent.set(item.id, content);
17116
- try {
17117
- await onSendDelta({
17118
- id: `${iteration.id}:${item.id}`,
17119
- component: item.name,
17120
- props: item.props,
17121
- delta: event.delta,
17122
- content
17123
- });
17124
- } catch (err) {}
17125
- } else if (event.type === "item-complete") {
17126
- if (event.item.kind === "send" && onSend) await onSend({
17127
- name: event.item.name,
17128
- props: event.item.props,
17129
- body: event.item.body
17132
+ const delta = {
17133
+ id: `${iteration.id}:${item.id}`,
17134
+ component: item.name,
17135
+ props: bufferedDelivery ? structuredClone(item.props) : item.props,
17136
+ delta: event.delta,
17137
+ content
17138
+ };
17139
+ await deliver(async () => {
17140
+ try {
17141
+ await onSendDelta(delta);
17142
+ } catch (err) {}
17130
17143
  });
17131
- else if (event.item.kind === "run" && event.item.status === "complete" && !runCompleted) {
17144
+ } 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) {
17132
17153
  runCompleted = true;
17133
- onRunComplete?.((event.item.body ?? "").trim());
17154
+ const code = (event.item.body ?? "").trim();
17155
+ await deliver(() => onRunComplete?.(code));
17134
17156
  }
17135
17157
  }
17136
17158
  };
17137
17159
  if (typeof cognitive.generateTextStream === "function") {
17138
- const parser = new StreamingMessageParser();
17139
- const fence = new LeadingFenceFilter();
17160
+ let parser = new StreamingMessageParser();
17161
+ let fence = new LeadingFenceFilter();
17140
17162
  const streamController = createJoinedAbortController([controller.signal]);
17141
17163
  const requestedAt = Date.now();
17142
17164
  const stream = cognitive.generateTextStream({
17143
17165
  ...input,
17144
- ...ctx.maxTimeToFirstToken ? { options: {
17166
+ ...ctx.maxTimeToFirstToken || bufferedDelivery ? { options: {
17145
17167
  ...input.options,
17146
- maxTimeToFirstToken: ctx.maxTimeToFirstToken
17168
+ ...ctx.maxTimeToFirstToken ? { maxTimeToFirstToken: ctx.maxTimeToFirstToken } : {},
17169
+ ...bufferedDelivery ? { midStreamFallback: true } : {}
17147
17170
  } } : {}
17148
17171
  }, { signal: streamController.signal });
17149
17172
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
@@ -17162,27 +17185,67 @@ var init_generate = __esmMin((() => {
17162
17185
  }
17163
17186
  };
17164
17187
  raw = "";
17165
- while (true) {
17166
- let chunk;
17167
- try {
17168
- chunk = await nextChunk();
17169
- } catch (thrown) {
17170
- throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
17188
+ let streamCompleted = false;
17189
+ try {
17190
+ while (true) {
17191
+ let chunk;
17192
+ try {
17193
+ chunk = await nextChunk();
17194
+ } catch (thrown) {
17195
+ throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
17196
+ }
17197
+ if (chunk.done) {
17198
+ streamCompleted = true;
17199
+ break;
17200
+ }
17201
+ if (chunk.value?.restart && !bufferedDelivery) {
17202
+ streamController.abort("Unexpected LLM stream restart");
17203
+ throw new CognitiveError("LLM stream restarted without options.midStreamFallback enabled");
17204
+ }
17205
+ if (chunk.value?.restart) {
17206
+ traces.push({
17207
+ type: "llm_call_restarted",
17208
+ started_at: Date.now(),
17209
+ ...chunk.value.restart
17210
+ });
17211
+ raw = "";
17212
+ parser = new StreamingMessageParser();
17213
+ fence = new LeadingFenceFilter();
17214
+ liveItems.clear();
17215
+ liveContent.clear();
17216
+ pendingDeliveries = [];
17217
+ codeGenerationTraced = false;
17218
+ runCompleted = false;
17219
+ responseMetadata = void 0;
17220
+ timeToFirstToken = void 0;
17221
+ timeToLastToken = void 0;
17222
+ continue;
17223
+ }
17224
+ if (chunk.value?.metadata) responseMetadata = chunk.value.metadata;
17225
+ const delta = chunk.value?.output;
17226
+ if (!delta) continue;
17227
+ timeToLastToken = Date.now() - requestedAt;
17228
+ timeToFirstToken ??= timeToLastToken;
17229
+ raw += delta;
17230
+ await dispatchSends(parser.push(fence.push(delta)));
17231
+ }
17232
+ if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
17233
+ const remaining = fence.flush();
17234
+ if (remaining) await dispatchSends(parser.push(remaining));
17235
+ await dispatchSends(parser.finish());
17236
+ assistantResponse = toParsedAssistantResponse(parser.items, raw);
17237
+ } finally {
17238
+ 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();
17171
17246
  }
17172
- if (chunk.done) break;
17173
- if (chunk.value?.metadata) responseMetadata = chunk.value.metadata;
17174
- const delta = chunk.value?.output;
17175
- if (!delta) continue;
17176
- timeToLastToken = Date.now() - requestedAt;
17177
- timeToFirstToken ??= timeToLastToken;
17178
- raw += delta;
17179
- await dispatchSends(parser.push(fence.push(delta)));
17247
+ pendingDeliveries = [];
17180
17248
  }
17181
- if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
17182
- const remaining = fence.flush();
17183
- if (remaining) await dispatchSends(parser.push(remaining));
17184
- await dispatchSends(parser.finish());
17185
- assistantResponse = toParsedAssistantResponse(parser.items, raw);
17186
17249
  } else {
17187
17250
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
17188
17251
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
@@ -17224,7 +17287,7 @@ var init_generate = __esmMin((() => {
17224
17287
  type: "llm_call_success",
17225
17288
  started_at: startedAt,
17226
17289
  ended_at: iteration.llm.ended_at,
17227
- model: model.id,
17290
+ model: iteration.llm.model,
17228
17291
  code: iteration.code ?? ""
17229
17292
  });
17230
17293
  };
@@ -17614,6 +17677,7 @@ var init_execute = __esmMin((() => {
17614
17677
  timeout: props.options?.timeout,
17615
17678
  maxTokens: props.options?.maxTokens,
17616
17679
  maxTimeToFirstToken: props.options?.maxTimeToFirstToken,
17680
+ midStreamFallback: props.options?.midStreamFallback,
17617
17681
  transcriptionModel: props.options?.transcriptionModel,
17618
17682
  exits: props.exits,
17619
17683
  snapshot: props.snapshot,
@@ -17805,7 +17869,7 @@ var init_execute = __esmMin((() => {
17805
17869
  };
17806
17870
  });
17807
17871
  };
17808
- const canExecuteEarly = typeof onBeforeExecution !== "function";
17872
+ const canExecuteEarly = typeof onBeforeExecution !== "function" && !ctx.midStreamFallback;
17809
17873
  let earlyExecution;
17810
17874
  await generateCode({
17811
17875
  iteration,
@@ -122,6 +122,13 @@ type Options = Partial<Pick<Context, 'loop' | 'timeout'>> & {
122
122
  * `model` is an array of fallback models.
123
123
  */
124
124
  maxTimeToFirstToken?: number;
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.
130
+ */
131
+ midStreamFallback?: boolean;
125
132
  /**
126
133
  * STT model used to transcribe audio attachments (voice messages) when the
127
134
  * target LLM does not support audio natively. Audio-capable models receive
package/dist/types.d.ts CHANGED
@@ -55,6 +55,13 @@ export declare namespace Traces {
55
55
  model: string;
56
56
  code: string;
57
57
  }>;
58
+ /** Diagnostic only: content and effects from the preceding attempt are discarded. */
59
+ type LLMCallRestart = TraceTemplate<'llm_call_restarted', {
60
+ attempt: number;
61
+ fromModel: string;
62
+ toModel: string;
63
+ reason: string;
64
+ }>;
58
65
  /**
59
66
  * Emitted on streaming clients the moment the model starts writing a `■run`
60
67
  * block — before the code is fully generated. Useful to show a
@@ -77,7 +84,7 @@ export declare namespace Traces {
77
84
  started_at: number;
78
85
  ended_at?: number;
79
86
  } & Content;
80
- type Trace = Comment | Log | ToolCall | ToolSlow | PropertyMutation | YieldTrace | LLMCallStart | LLMCallSuccess | CodeGenerationStart | ThinkSignal | CodeExecution | CodeExecutionException | InvalidCodeExceptionTrace | AbortTrace;
87
+ type Trace = Comment | Log | ToolCall | ToolSlow | PropertyMutation | YieldTrace | LLMCallStart | LLMCallSuccess | LLMCallRestart | CodeGenerationStart | ThinkSignal | CodeExecution | CodeExecutionException | InvalidCodeExceptionTrace | AbortTrace;
81
88
  }
82
89
  export type Trace = Traces.Trace;
83
90
  export type VMExecutionResult = {
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.3",
5
+ "version": "0.6.5",
6
6
  "files": [
7
7
  "dist",
8
8
  "DOCS.md"
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@botpress/client": "2.4.0",
72
- "@botpress/cognitive": "1.1.0",
72
+ "@botpress/cognitive": "1.2.0",
73
73
  "@bpinternal/thicktoken": "^3.1.0",
74
74
  "@bpinternal/zui": "^2.4.1"
75
75
  },