llmz 0.9.12 → 0.9.13

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.
@@ -599,6 +599,13 @@ var StreamingMessageParser = class {
599
599
  };
600
600
  //#endregion
601
601
  //#region src/message-stream/response-parser.ts
602
+ /**
603
+ * Text a model may write before ■start. Some models stream junk for minutes
604
+ * without ever opening the envelope; past this many characters, give up.
605
+ */
606
+ const MAX_PREAMBLE_LENGTH = 8e3;
607
+ /** Text a model may write after ■end before the rest of its stream is ignored. */
608
+ const MAX_TRAILER_LENGTH = 2e3;
602
609
  /** One wire format: a complete ■start … ■end envelope containing valid blocks. */
603
610
  var ResponseParser = class {
604
611
  _parser = new StreamingMessageParser();
@@ -611,9 +618,19 @@ var ResponseParser = class {
611
618
  _finished = false;
612
619
  _failure;
613
620
  _trailer = "";
621
+ _preambleLength = 0;
614
622
  get framed() {
615
623
  return this._mode === "framed";
616
624
  }
625
+ /** Whether ■end was received. Later text is only kept as a diagnostic. */
626
+ get closed() {
627
+ return this._closed;
628
+ }
629
+ /** The model is writing junk past either boundary: stop reading the stream. */
630
+ get done() {
631
+ if (this._closed) return this._trailer.length > MAX_TRAILER_LENGTH;
632
+ return this._mode === "detect" && this._preambleLength > 8e3;
633
+ }
617
634
  get valid() {
618
635
  return this._finished && this._closed && !this._failure;
619
636
  }
@@ -637,7 +654,13 @@ var ResponseParser = class {
637
654
  forward = "";
638
655
  };
639
656
  for (const char of chunk) {
657
+ if (this._closed) {
658
+ this._trailer += char;
659
+ continue;
660
+ }
661
+ if (this.done) continue;
640
662
  if (this._mode === "detect") {
663
+ this._preambleLength++;
641
664
  if (this._discardPrefixLine) {
642
665
  if (char === "\n") this._discardPrefixLine = false;
643
666
  continue;
@@ -668,10 +691,6 @@ var ResponseParser = class {
668
691
  }
669
692
  continue;
670
693
  }
671
- if (this._closed) {
672
- this._trailer += char;
673
- continue;
674
- }
675
694
  if (this._boundary || char === "■") {
676
695
  this._boundary += char;
677
696
  if (this._boundary === "■end") {
@@ -693,7 +712,8 @@ var ResponseParser = class {
693
712
  this._finished = true;
694
713
  const events = [];
695
714
  if (this._mode === "detect") {
696
- if (this._prefix.trim() === "■start") this._mode = "framed";
715
+ if (this.done) this._fail(`Missing ■start: no response envelope within ${MAX_PREAMBLE_LENGTH} characters`);
716
+ else if (this._prefix.trim() === "■start") this._mode = "framed";
697
717
  else this._fail("Missing ■start: every response must begin with the response envelope");
698
718
  this._prefix = "";
699
719
  }
@@ -599,6 +599,13 @@ var StreamingMessageParser = class {
599
599
  };
600
600
  //#endregion
601
601
  //#region src/message-stream/response-parser.ts
602
+ /**
603
+ * Text a model may write before ■start. Some models stream junk for minutes
604
+ * without ever opening the envelope; past this many characters, give up.
605
+ */
606
+ const MAX_PREAMBLE_LENGTH = 8e3;
607
+ /** Text a model may write after ■end before the rest of its stream is ignored. */
608
+ const MAX_TRAILER_LENGTH = 2e3;
602
609
  /** One wire format: a complete ■start … ■end envelope containing valid blocks. */
603
610
  var ResponseParser = class {
604
611
  _parser = new StreamingMessageParser();
@@ -611,9 +618,19 @@ var ResponseParser = class {
611
618
  _finished = false;
612
619
  _failure;
613
620
  _trailer = "";
621
+ _preambleLength = 0;
614
622
  get framed() {
615
623
  return this._mode === "framed";
616
624
  }
625
+ /** Whether ■end was received. Later text is only kept as a diagnostic. */
626
+ get closed() {
627
+ return this._closed;
628
+ }
629
+ /** The model is writing junk past either boundary: stop reading the stream. */
630
+ get done() {
631
+ if (this._closed) return this._trailer.length > MAX_TRAILER_LENGTH;
632
+ return this._mode === "detect" && this._preambleLength > 8e3;
633
+ }
617
634
  get valid() {
618
635
  return this._finished && this._closed && !this._failure;
619
636
  }
@@ -637,7 +654,13 @@ var ResponseParser = class {
637
654
  forward = "";
638
655
  };
639
656
  for (const char of chunk) {
657
+ if (this._closed) {
658
+ this._trailer += char;
659
+ continue;
660
+ }
661
+ if (this.done) continue;
640
662
  if (this._mode === "detect") {
663
+ this._preambleLength++;
641
664
  if (this._discardPrefixLine) {
642
665
  if (char === "\n") this._discardPrefixLine = false;
643
666
  continue;
@@ -668,10 +691,6 @@ var ResponseParser = class {
668
691
  }
669
692
  continue;
670
693
  }
671
- if (this._closed) {
672
- this._trailer += char;
673
- continue;
674
- }
675
694
  if (this._boundary || char === "■") {
676
695
  this._boundary += char;
677
696
  if (this._boundary === "■end") {
@@ -693,7 +712,8 @@ var ResponseParser = class {
693
712
  this._finished = true;
694
713
  const events = [];
695
714
  if (this._mode === "detect") {
696
- if (this._prefix.trim() === "■start") this._mode = "framed";
715
+ if (this.done) this._fail(`Missing ■start: no response envelope within ${MAX_PREAMBLE_LENGTH} characters`);
716
+ else if (this._prefix.trim() === "■start") this._mode = "framed";
697
717
  else this._fail("Missing ■start: every response must begin with the response envelope");
698
718
  this._prefix = "";
699
719
  }
@@ -1,4 +1,4 @@
1
- const require_dual_modes = require("./dual-modes-zxiDUl2A.cjs");
1
+ const require_dual_modes = require("./dual-modes-D3cSQ8j4.cjs");
2
2
  const require__overArg = require("./_overArg-C5Hy7oCi.cjs");
3
3
  const require__baseIteratee = require("./_baseIteratee-CF4qP2a8.cjs");
4
4
  const require_utils = require("./utils-DGQ4sRke.cjs");
@@ -1,4 +1,4 @@
1
- import { a as flatRest, i as inspect, o as copyArray, r as extractType, s as baseCreate, t as DualModePrompt } from "./dual-modes-D3_Jtxv1.js";
1
+ import { a as flatRest, i as inspect, o as copyArray, r as extractType, s as baseCreate, t as DualModePrompt } from "./dual-modes-BdZuONje.js";
2
2
  import { a as Symbol, n as isArray, o as root, r as isObjectLike } from "./_overArg-CRYbj1NH.js";
3
3
  import { D as isBuffer, E as baseUnary, M as baseAssignValue, R as isObject, T as nodeUtil, _ as baseGet, c as _getTag_default, f as getSymbols, h as Stack, k as isPrototype, s as Uint8Array, u as getAllKeys, v as toKey, x as keys, y as castPath, z as arrayMap } from "./_baseIteratee-P_firB7b.js";
4
4
  import { _ as getSymbolsIn, b as keysIn, g as getAllKeysIn, i as getErrorMessage, o as getTokenizer, x as assignValue, y as baseSlice } from "./utils-UBeAFeEI.js";
@@ -1,5 +1,5 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.js";
2
- import { l as NAME_REGEX, n as parseAssistantResponse, r as replacePlaceholders } from "./common-DuEhn6u3.js";
2
+ import { l as NAME_REGEX, n as parseAssistantResponse, r as replacePlaceholders } from "./common-P6vdw8Vv.js";
3
3
  import { a as Symbol, n as isArray, r as isObjectLike } from "./_overArg-CRYbj1NH.js";
4
4
  import { A as isArrayLike, B as isSymbol, E as baseUnary, F as WeakMap, L as identity, M as baseAssignValue, O as isArguments, P as defineProperty, R as isObject, _ as baseGet, c as _getTag_default, g as arrayPush, i as mapToArray, m as arrayFilter, t as baseIteratee, x as keys, z as arrayMap } from "./_baseIteratee-P_firB7b.js";
5
5
  import { n as noop, t as uniq } from "./uniq-eZWVMz0r.js";
@@ -754,7 +754,7 @@ const renderExamples = async (examples, components, exits) => {
754
754
  if (!examples.length) return "";
755
755
  const registry = new ComponentRegistry(components.map(componentToProtocolDefinition));
756
756
  const exitDefinitions = exits.map(exitToProtocolDefinition);
757
- const { parseAssistantResponse } = await import("./common-DuEhn6u3.js").then((n) => n.t);
757
+ const { parseAssistantResponse } = await import("./common-P6vdw8Vv.js").then((n) => n.t);
758
758
  const render = (output) => {
759
759
  var _response$diagnostics;
760
760
  const response = parseAssistantResponse(`■start\n${output}\n■end`);
@@ -1,5 +1,5 @@
1
1
  const require_compiler = require("./compiler-BJJUmNS9.cjs");
2
- const require_common = require("./common-DJkSpeqw.cjs");
2
+ const require_common = require("./common-CaWHSEW4.cjs");
3
3
  const require__overArg = require("./_overArg-C5Hy7oCi.cjs");
4
4
  const require__baseIteratee = require("./_baseIteratee-CF4qP2a8.cjs");
5
5
  const require_uniq = require("./uniq-BWs20_o8.cjs");
@@ -755,7 +755,7 @@ const renderExamples = async (examples, components, exits) => {
755
755
  if (!examples.length) return "";
756
756
  const registry = new ComponentRegistry(components.map(componentToProtocolDefinition));
757
757
  const exitDefinitions = exits.map(exitToProtocolDefinition);
758
- const { parseAssistantResponse } = await Promise.resolve().then(() => require("./common-DJkSpeqw.cjs")).then((n) => n.common_exports);
758
+ const { parseAssistantResponse } = await Promise.resolve().then(() => require("./common-CaWHSEW4.cjs")).then((n) => n.common_exports);
759
759
  const render = (output) => {
760
760
  var _response$diagnostics;
761
761
  const response = parseAssistantResponse(`■start\n${output}\n■end`);
@@ -1,8 +1,8 @@
1
1
  const require_compiler = require("./compiler-BJJUmNS9.cjs");
2
- const require_dual_modes = require("./dual-modes-zxiDUl2A.cjs");
3
- const require_common = require("./common-DJkSpeqw.cjs");
2
+ const require_dual_modes = require("./dual-modes-D3cSQ8j4.cjs");
3
+ const require_common = require("./common-CaWHSEW4.cjs");
4
4
  const require__baseIteratee = require("./_baseIteratee-CF4qP2a8.cjs");
5
- const require_custom_client = require("./custom-client-BXhs5PpQ.cjs");
5
+ const require_custom_client = require("./custom-client-C3EnKARC.cjs");
6
6
  const require_utils = require("./utils-DGQ4sRke.cjs");
7
7
  const require_jsx = require("./jsx-BJ6GWXI2.cjs");
8
8
  const require_stack_traces = require("./stack-traces-ntw_qhoy.cjs");
@@ -157,11 +157,44 @@ const RESPONSE_LENGTH_BUFFER = {
157
157
  };
158
158
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
159
159
  const STREAM_INACTIVITY_TIMEOUT = 18e4;
160
+ /**
161
+ * How long to keep reading after ■end for the provider's final chunk. Models
162
+ * that ignore the stop sequence can otherwise stream junk until the caller's deadline.
163
+ */
164
+ const STREAM_END_GRACE_PERIOD = 1e3;
160
165
  /** A syntactically valid prefix must not execute when generation did not finish successfully. */
161
166
  const assertSuccessfulGeneration = (metadata) => {
162
167
  if (metadata.provider === "unknown") throw new require_errors.CognitiveError("LLM generation failed: received error metadata with unknown provider");
163
168
  if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter" || metadata.stopReason === "other") throw new require_errors.CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
164
169
  };
170
+ /**
171
+ * Cognitive sends usage only in the final chunk, which is never read when the
172
+ * stream is closed early. Estimate it from the messages sent and the output.
173
+ */
174
+ const estimateMetadata = (model, messages, output) => {
175
+ const tokenizer = require_utils.getTokenizer();
176
+ const countMessage = (message) => {
177
+ if (typeof message.content === "string") return tokenizer.count(message.content);
178
+ return (message.content ?? []).map((part) => part.type === "text" ? part.text ?? "" : "").reduce((sum, text) => sum + tokenizer.count(text), 0);
179
+ };
180
+ const inputTokens = messages.reduce((total, message) => total + countMessage(message), 0);
181
+ const outputTokens = tokenizer.count(output);
182
+ const inputCost = inputTokens * model.input.costPer1MTokens / 1e6;
183
+ const outputCost = outputTokens * model.output.costPer1MTokens / 1e6;
184
+ const [provider, ...name] = model.id.split(":");
185
+ return {
186
+ provider,
187
+ model: name.join(":"),
188
+ usage: {
189
+ inputTokens,
190
+ inputCost,
191
+ outputTokens,
192
+ outputCost
193
+ },
194
+ cost: inputCost + outputCost,
195
+ stopReason: "stop"
196
+ };
197
+ };
165
198
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
166
199
  const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, onSend, onSendDelta, onRunStart }) => {
167
200
  const startedAt = Date.now();
@@ -279,16 +312,14 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
279
312
  } } : {}
280
313
  }, { signal: streamController.signal });
281
314
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
282
- const nextChunk = async () => {
315
+ /** Resolves to undefined when no chunk arrives within the timeout. */
316
+ const nextChunk = async (timeout) => {
283
317
  let timer;
284
- const stalled = new Promise((_, reject) => {
285
- timer = setTimeout(() => {
286
- streamController.abort("LLM stream stalled");
287
- reject(/* @__PURE__ */ new Error(`LLM stream stalled: no data received for ${inactivityTimeout}ms`));
288
- }, inactivityTimeout);
318
+ const expired = new Promise((resolve) => {
319
+ timer = setTimeout(() => resolve(void 0), timeout);
289
320
  });
290
321
  try {
291
- return await Promise.race([stream.next(), stalled]);
322
+ return await Promise.race([stream.next(), expired]);
292
323
  } finally {
293
324
  clearTimeout(timer);
294
325
  }
@@ -296,15 +327,22 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
296
327
  raw = "";
297
328
  let streamCompleted = false;
298
329
  let accepted = false;
330
+ let closedAt;
331
+ let servedModel = model.id;
299
332
  try {
300
333
  while (true) {
301
334
  var _chunk$value, _chunk$value2, _chunk$value3, _chunk$value4;
302
335
  let chunk;
303
336
  try {
304
- chunk = await nextChunk();
337
+ chunk = await nextChunk(closedAt === void 0 ? inactivityTimeout : closedAt + STREAM_END_GRACE_PERIOD - Date.now());
305
338
  } catch (thrown) {
306
339
  throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
307
340
  }
341
+ if (!chunk && closedAt !== void 0) break;
342
+ if (!chunk) {
343
+ streamController.abort("LLM stream stalled");
344
+ throw new require_errors.CognitiveError(`LLM generation failed: LLM stream stalled: no data received for ${inactivityTimeout}ms`);
345
+ }
308
346
  if (chunk.done) {
309
347
  streamCompleted = true;
310
348
  break;
@@ -323,12 +361,14 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
323
361
  completions = [];
324
362
  accepted = false;
325
363
  parser = new require_common.ResponseParser();
364
+ closedAt = void 0;
326
365
  liveItems.clear();
327
366
  liveContent.clear();
328
367
  codeGenerationTraced = false;
329
368
  runCompleted = false;
330
369
  responseMetadata = void 0;
331
370
  attempt = chunk.value.restart.attempt;
371
+ servedModel = chunk.value.restart.toModel;
332
372
  await preview({
333
373
  ...chunk.value.restart,
334
374
  restart: true,
@@ -345,6 +385,12 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
345
385
  timeToFirstToken ??= timeToLastToken;
346
386
  raw += delta;
347
387
  await dispatchSends(parser.push(delta));
388
+ if (parser.done) break;
389
+ if (parser.closed) closedAt ??= Date.now();
390
+ }
391
+ if (!responseMetadata && !streamCompleted) {
392
+ const served = models.find((details) => details.id === servedModel) ?? await cognitive.getModelDetails(servedModel).catch(() => model);
393
+ responseMetadata = estimateMetadata(served, messages, raw);
348
394
  }
349
395
  if (!responseMetadata) throw new require_errors.CognitiveError("LLM streaming completed without metadata");
350
396
  assertSuccessfulGeneration(responseMetadata);
@@ -1,8 +1,8 @@
1
- import { c as toNumber } from "./dual-modes-D3_Jtxv1.js";
2
- import { a as ResponseParser, i as toParsedAssistantResponse } from "./common-DuEhn6u3.js";
1
+ import { c as toNumber } from "./dual-modes-BdZuONje.js";
2
+ import { a as ResponseParser, i as toParsedAssistantResponse } from "./common-P6vdw8Vv.js";
3
3
  import { n as baseIsEqual } from "./_baseIteratee-P_firB7b.js";
4
- 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-BvA4375q.js";
5
- import { d as stripInvalidIdentifiers, i as getErrorMessage, r as fromJSONSchemaCompat, s as init } from "./utils-UBeAFeEI.js";
4
+ 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-CZVu-wF4.js";
5
+ import { d as stripInvalidIdentifiers, i as getErrorMessage, o as getTokenizer, r as fromJSONSchemaCompat, s as init } from "./utils-UBeAFeEI.js";
6
6
  import { t as createJsxComponent } from "./jsx-CIgdGGVs.js";
7
7
  import { t as cleanStackTrace } from "./stack-traces-BIE_5Xwf.js";
8
8
  import { a as InvalidCodeError, c as SnapshotSignal, i as CognitiveError, l as ThinkSignal, n as CodeExecutionError, o as LoopExceededError, s as Signals, t as AssignmentError } from "./errors-Cg1G0Awu.js";
@@ -156,11 +156,44 @@ const RESPONSE_LENGTH_BUFFER = {
156
156
  };
157
157
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
158
158
  const STREAM_INACTIVITY_TIMEOUT = 18e4;
159
+ /**
160
+ * How long to keep reading after ■end for the provider's final chunk. Models
161
+ * that ignore the stop sequence can otherwise stream junk until the caller's deadline.
162
+ */
163
+ const STREAM_END_GRACE_PERIOD = 1e3;
159
164
  /** A syntactically valid prefix must not execute when generation did not finish successfully. */
160
165
  const assertSuccessfulGeneration = (metadata) => {
161
166
  if (metadata.provider === "unknown") throw new CognitiveError("LLM generation failed: received error metadata with unknown provider");
162
167
  if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter" || metadata.stopReason === "other") throw new CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
163
168
  };
169
+ /**
170
+ * Cognitive sends usage only in the final chunk, which is never read when the
171
+ * stream is closed early. Estimate it from the messages sent and the output.
172
+ */
173
+ const estimateMetadata = (model, messages, output) => {
174
+ const tokenizer = getTokenizer();
175
+ const countMessage = (message) => {
176
+ if (typeof message.content === "string") return tokenizer.count(message.content);
177
+ return (message.content ?? []).map((part) => part.type === "text" ? part.text ?? "" : "").reduce((sum, text) => sum + tokenizer.count(text), 0);
178
+ };
179
+ const inputTokens = messages.reduce((total, message) => total + countMessage(message), 0);
180
+ const outputTokens = tokenizer.count(output);
181
+ const inputCost = inputTokens * model.input.costPer1MTokens / 1e6;
182
+ const outputCost = outputTokens * model.output.costPer1MTokens / 1e6;
183
+ const [provider, ...name] = model.id.split(":");
184
+ return {
185
+ provider,
186
+ model: name.join(":"),
187
+ usage: {
188
+ inputTokens,
189
+ inputCost,
190
+ outputTokens,
191
+ outputCost
192
+ },
193
+ cost: inputCost + outputCost,
194
+ stopReason: "stop"
195
+ };
196
+ };
164
197
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
165
198
  const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, onSend, onSendDelta, onRunStart }) => {
166
199
  const startedAt = Date.now();
@@ -278,16 +311,14 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
278
311
  } } : {}
279
312
  }, { signal: streamController.signal });
280
313
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
281
- const nextChunk = async () => {
314
+ /** Resolves to undefined when no chunk arrives within the timeout. */
315
+ const nextChunk = async (timeout) => {
282
316
  let timer;
283
- const stalled = new Promise((_, reject) => {
284
- timer = setTimeout(() => {
285
- streamController.abort("LLM stream stalled");
286
- reject(/* @__PURE__ */ new Error(`LLM stream stalled: no data received for ${inactivityTimeout}ms`));
287
- }, inactivityTimeout);
317
+ const expired = new Promise((resolve) => {
318
+ timer = setTimeout(() => resolve(void 0), timeout);
288
319
  });
289
320
  try {
290
- return await Promise.race([stream.next(), stalled]);
321
+ return await Promise.race([stream.next(), expired]);
291
322
  } finally {
292
323
  clearTimeout(timer);
293
324
  }
@@ -295,15 +326,22 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
295
326
  raw = "";
296
327
  let streamCompleted = false;
297
328
  let accepted = false;
329
+ let closedAt;
330
+ let servedModel = model.id;
298
331
  try {
299
332
  while (true) {
300
333
  var _chunk$value, _chunk$value2, _chunk$value3, _chunk$value4;
301
334
  let chunk;
302
335
  try {
303
- chunk = await nextChunk();
336
+ chunk = await nextChunk(closedAt === void 0 ? inactivityTimeout : closedAt + STREAM_END_GRACE_PERIOD - Date.now());
304
337
  } catch (thrown) {
305
338
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
306
339
  }
340
+ if (!chunk && closedAt !== void 0) break;
341
+ if (!chunk) {
342
+ streamController.abort("LLM stream stalled");
343
+ throw new CognitiveError(`LLM generation failed: LLM stream stalled: no data received for ${inactivityTimeout}ms`);
344
+ }
307
345
  if (chunk.done) {
308
346
  streamCompleted = true;
309
347
  break;
@@ -322,12 +360,14 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
322
360
  completions = [];
323
361
  accepted = false;
324
362
  parser = new ResponseParser();
363
+ closedAt = void 0;
325
364
  liveItems.clear();
326
365
  liveContent.clear();
327
366
  codeGenerationTraced = false;
328
367
  runCompleted = false;
329
368
  responseMetadata = void 0;
330
369
  attempt = chunk.value.restart.attempt;
370
+ servedModel = chunk.value.restart.toModel;
331
371
  await preview({
332
372
  ...chunk.value.restart,
333
373
  restart: true,
@@ -344,6 +384,12 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
344
384
  timeToFirstToken ??= timeToLastToken;
345
385
  raw += delta;
346
386
  await dispatchSends(parser.push(delta));
387
+ if (parser.done) break;
388
+ if (parser.closed) closedAt ??= Date.now();
389
+ }
390
+ if (!responseMetadata && !streamCompleted) {
391
+ const served = models.find((details) => details.id === servedModel) ?? await cognitive.getModelDetails(servedModel).catch(() => model);
392
+ responseMetadata = estimateMetadata(served, messages, raw);
347
393
  }
348
394
  if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
349
395
  assertSuccessfulGeneration(responseMetadata);
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dual_modes = require("./dual-modes-zxiDUl2A.cjs");
3
- const require_common = require("./common-DJkSpeqw.cjs");
4
- const require_custom_client = require("./custom-client-BXhs5PpQ.cjs");
2
+ const require_dual_modes = require("./dual-modes-D3cSQ8j4.cjs");
3
+ const require_common = require("./common-CaWHSEW4.cjs");
4
+ const require_custom_client = require("./custom-client-C3EnKARC.cjs");
5
5
  const require_utils = require("./utils-DGQ4sRke.cjs");
6
6
  const require_tool = require("./tool-ikMsh-TB.cjs");
7
7
  const require_errors = require("./errors-C962uJdS.cjs");
@@ -12,7 +12,7 @@ const require_truncator = require("./truncator-DmCu9M4m.cjs");
12
12
  const require_quickjs_variant = require("./quickjs-variant-CsIwTVFC.cjs");
13
13
  let _bpinternal_zui = require("@bpinternal/zui");
14
14
  //#region package.json
15
- var version = "0.9.12";
15
+ var version = "0.9.13";
16
16
  //#endregion
17
17
  //#region src/hoist.ts
18
18
  /**
@@ -1693,7 +1693,7 @@ const utils = {
1693
1693
  * })
1694
1694
  */
1695
1695
  const execute = async (props) => {
1696
- const { executeContext } = await Promise.resolve().then(() => require("./execute-BTImw3Hx.cjs"));
1696
+ const { executeContext } = await Promise.resolve().then(() => require("./execute-DBMmurHH.cjs"));
1697
1697
  return executeContext(props);
1698
1698
  };
1699
1699
  /**
@@ -1702,7 +1702,7 @@ const execute = async (props) => {
1702
1702
  * It's recommended to call this function at the beginning of your application without awaiting it (void init())
1703
1703
  */
1704
1704
  const init = async () => {
1705
- await Promise.resolve().then(() => require("./execute-BTImw3Hx.cjs"));
1705
+ await Promise.resolve().then(() => require("./execute-DBMmurHH.cjs"));
1706
1706
  await Promise.resolve().then(() => require("./component-CPKXe6Ca.cjs")).then((n) => n.component_exports);
1707
1707
  await Promise.resolve().then(() => require("./tool-ikMsh-TB.cjs")).then((n) => n.tool_exports);
1708
1708
  await Promise.resolve().then(() => require("./exit-DTDB1SJ3.cjs")).then((n) => n.exit_exports);
@@ -1711,7 +1711,7 @@ const init = async () => {
1711
1711
  await Promise.resolve().then(() => require("./utils-DGQ4sRke.cjs")).then((n) => n.utils_exports);
1712
1712
  await Promise.resolve().then(() => require("./truncator-DmCu9M4m.cjs")).then((n) => n.truncator_exports);
1713
1713
  await Promise.resolve().then(() => require("./typings-DXJMu59O.cjs")).then((n) => n.typings_exports);
1714
- await Promise.resolve().then(() => require("./dual-modes-zxiDUl2A.cjs")).then((n) => n.dual_modes_exports);
1714
+ await Promise.resolve().then(() => require("./dual-modes-D3cSQ8j4.cjs")).then((n) => n.dual_modes_exports);
1715
1715
  };
1716
1716
  //#endregion
1717
1717
  exports.BaseResponse = BaseResponse;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { d as validateComponent, f as validateProps, l as Example, p as ComponentRegistry, u as generateInstructions } from "./dual-modes-D3_Jtxv1.js";
2
- import { c as MARKER, l as NAME_REGEX, o as StreamingMessageParser, s as tryParseJson } from "./common-DuEhn6u3.js";
3
- 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-BvA4375q.js";
1
+ import { d as validateComponent, f as validateProps, l as Example, p as ComponentRegistry, u as generateInstructions } from "./dual-modes-BdZuONje.js";
2
+ import { c as MARKER, l as NAME_REGEX, o as StreamingMessageParser, s as tryParseJson } from "./common-P6vdw8Vv.js";
3
+ 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-CZVu-wF4.js";
4
4
  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";
5
5
  import { t as Tool } from "./tool-BoXBgZeM.js";
6
6
  import { c as SnapshotSignal, l as ThinkSignal, o as LoopExceededError, r as CodeFormattingError } from "./errors-Cg1G0Awu.js";
@@ -11,7 +11,7 @@ import { i as wrapContent, n as truncateWrappedContent, t as stripTruncationTags
11
11
  import { t as configureQuickJS } from "./quickjs-variant-BYhMBpwU.js";
12
12
  import { transforms, z } from "@bpinternal/zui";
13
13
  //#region package.json
14
- var version = "0.9.12";
14
+ var version = "0.9.13";
15
15
  //#endregion
16
16
  //#region src/hoist.ts
17
17
  /**
@@ -1692,7 +1692,7 @@ const utils = {
1692
1692
  * })
1693
1693
  */
1694
1694
  const execute = async (props) => {
1695
- const { executeContext } = await import("./execute-Cr_TyTIE.js");
1695
+ const { executeContext } = await import("./execute-hoM7wiQS.js");
1696
1696
  return executeContext(props);
1697
1697
  };
1698
1698
  /**
@@ -1701,7 +1701,7 @@ const execute = async (props) => {
1701
1701
  * It's recommended to call this function at the beginning of your application without awaiting it (void init())
1702
1702
  */
1703
1703
  const init = async () => {
1704
- await import("./execute-Cr_TyTIE.js");
1704
+ await import("./execute-hoM7wiQS.js");
1705
1705
  await import("./component-DGG7d_Mz.js").then((n) => n.r);
1706
1706
  await import("./tool-BoXBgZeM.js").then((n) => n.n);
1707
1707
  await import("./exit-AwQk1cyW.js").then((n) => n.n);
@@ -1710,7 +1710,7 @@ const init = async () => {
1710
1710
  await import("./utils-UBeAFeEI.js").then((n) => n.h);
1711
1711
  await import("./truncator-CD7QtE84.js").then((n) => n.r);
1712
1712
  await import("./typings-CrBa7Tyy.js").then((n) => n.n);
1713
- await import("./dual-modes-D3_Jtxv1.js").then((n) => n.n);
1713
+ await import("./dual-modes-BdZuONje.js").then((n) => n.n);
1714
1714
  };
1715
1715
  //#endregion
1716
1716
  export { BaseResponse, Chat, CitationsManager, Component, ComponentRegistry, DefaultComponents, DefaultExit, ErrorExecutionResult, Example, ExecutionResult, Exit, ListenExit, LoopExceededError, MARKER, NAME_REGEX, NextResponse, ObjectInstance, PartialExecutionResult, ReplayableAsyncIterable, RunResponse, SendResponse, Snapshot, SnapshotSignal, StreamingMessageParser, SuccessExecutionResult, ThinkExit, ThinkSignal, Tool, _CustomModelClient, assertValidComponent, collectText, configureQuickJS, configureTokenizer, execute, generateInstructions, getValue, init, isAnyComponent, isComponent, normalizeComponentDefinition, normalizeComponentName, normalizePropsSchema, parseExit, parseStream, parseText, sanitizeMessageText, tryParseJson, utils, validateComponent, validateProps, version };
@@ -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.9.12", quoteExample, quotePartialExample, exampleBoundaryInstructions, quoteResponseExample;
55
+ var version = "0.9.13", quoteExample, quotePartialExample, exampleBoundaryInstructions, quoteResponseExample;
56
56
  var init_example_format = __esmMin((() => {
57
57
  quoteExample = (output) => `"""\n${output}\n"""`;
58
58
  quotePartialExample = (output) => quoteExample(`(...)\n${output}\n(...)`);
@@ -1746,10 +1746,12 @@ var init_parser = __esmMin((() => {
1746
1746
  }));
1747
1747
  //#endregion
1748
1748
  //#region src/message-stream/response-parser.ts
1749
- var ResponseParser;
1749
+ var MAX_PREAMBLE_LENGTH, MAX_TRAILER_LENGTH, ResponseParser;
1750
1750
  var init_response_parser = __esmMin((() => {
1751
1751
  init_compiler();
1752
1752
  init_parser();
1753
+ MAX_PREAMBLE_LENGTH = 8e3;
1754
+ MAX_TRAILER_LENGTH = 2e3;
1753
1755
  ResponseParser = class {
1754
1756
  _parser = new StreamingMessageParser();
1755
1757
  _mode = "detect";
@@ -1761,9 +1763,19 @@ var init_response_parser = __esmMin((() => {
1761
1763
  _finished = false;
1762
1764
  _failure;
1763
1765
  _trailer = "";
1766
+ _preambleLength = 0;
1764
1767
  get framed() {
1765
1768
  return this._mode === "framed";
1766
1769
  }
1770
+ /** Whether ■end was received. Later text is only kept as a diagnostic. */
1771
+ get closed() {
1772
+ return this._closed;
1773
+ }
1774
+ /** The model is writing junk past either boundary: stop reading the stream. */
1775
+ get done() {
1776
+ if (this._closed) return this._trailer.length > MAX_TRAILER_LENGTH;
1777
+ return this._mode === "detect" && this._preambleLength > 8e3;
1778
+ }
1767
1779
  get valid() {
1768
1780
  return this._finished && this._closed && !this._failure;
1769
1781
  }
@@ -1787,7 +1799,13 @@ var init_response_parser = __esmMin((() => {
1787
1799
  forward = "";
1788
1800
  };
1789
1801
  for (const char of chunk) {
1802
+ if (this._closed) {
1803
+ this._trailer += char;
1804
+ continue;
1805
+ }
1806
+ if (this.done) continue;
1790
1807
  if (this._mode === "detect") {
1808
+ this._preambleLength++;
1791
1809
  if (this._discardPrefixLine) {
1792
1810
  if (char === "\n") this._discardPrefixLine = false;
1793
1811
  continue;
@@ -1818,10 +1836,6 @@ var init_response_parser = __esmMin((() => {
1818
1836
  }
1819
1837
  continue;
1820
1838
  }
1821
- if (this._closed) {
1822
- this._trailer += char;
1823
- continue;
1824
- }
1825
1839
  if (this._boundary || char === "■") {
1826
1840
  this._boundary += char;
1827
1841
  if (this._boundary === "■end") {
@@ -1843,7 +1857,8 @@ var init_response_parser = __esmMin((() => {
1843
1857
  this._finished = true;
1844
1858
  const events = [];
1845
1859
  if (this._mode === "detect") {
1846
- if (this._prefix.trim() === "■start") this._mode = "framed";
1860
+ if (this.done) this._fail(`Missing ■start: no response envelope within ${MAX_PREAMBLE_LENGTH} characters`);
1861
+ else if (this._prefix.trim() === "■start") this._mode = "framed";
1847
1862
  else this._fail("Missing ■start: every response must begin with the response envelope");
1848
1863
  this._prefix = "";
1849
1864
  }
@@ -18113,7 +18128,7 @@ var init_vm = __esmMin((() => {
18113
18128
  }));
18114
18129
  //#endregion
18115
18130
  //#region src/runtime/generate.ts
18116
- var RESPONSE_LENGTH_BUFFER, STREAM_INACTIVITY_TIMEOUT, assertSuccessfulGeneration, getModelOutputLimit, generateCode;
18131
+ var RESPONSE_LENGTH_BUFFER, STREAM_INACTIVITY_TIMEOUT, STREAM_END_GRACE_PERIOD, assertSuccessfulGeneration, estimateMetadata, getModelOutputLimit, generateCode;
18117
18132
  var init_generate = __esmMin((() => {
18118
18133
  init_lodash();
18119
18134
  init_abort_signal();
@@ -18128,10 +18143,35 @@ var init_generate = __esmMin((() => {
18128
18143
  PERCENTAGE: .1
18129
18144
  };
18130
18145
  STREAM_INACTIVITY_TIMEOUT = 18e4;
18146
+ STREAM_END_GRACE_PERIOD = 1e3;
18131
18147
  assertSuccessfulGeneration = (metadata) => {
18132
18148
  if (metadata.provider === "unknown") throw new CognitiveError("LLM generation failed: received error metadata with unknown provider");
18133
18149
  if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter" || metadata.stopReason === "other") throw new CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
18134
18150
  };
18151
+ estimateMetadata = (model, messages, output) => {
18152
+ const tokenizer = getTokenizer();
18153
+ const countMessage = (message) => {
18154
+ if (typeof message.content === "string") return tokenizer.count(message.content);
18155
+ return (message.content ?? []).map((part) => part.type === "text" ? part.text ?? "" : "").reduce((sum, text) => sum + tokenizer.count(text), 0);
18156
+ };
18157
+ const inputTokens = messages.reduce((total, message) => total + countMessage(message), 0);
18158
+ const outputTokens = tokenizer.count(output);
18159
+ const inputCost = inputTokens * model.input.costPer1MTokens / 1e6;
18160
+ const outputCost = outputTokens * model.output.costPer1MTokens / 1e6;
18161
+ const [provider, ...name] = model.id.split(":");
18162
+ return {
18163
+ provider,
18164
+ model: name.join(":"),
18165
+ usage: {
18166
+ inputTokens,
18167
+ inputCost,
18168
+ outputTokens,
18169
+ outputCost
18170
+ },
18171
+ cost: inputCost + outputCost,
18172
+ stopReason: "stop"
18173
+ };
18174
+ };
18135
18175
  getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
18136
18176
  generateCode = async ({ iteration, ctx, cognitive, controller, metadata, onSend, onSendDelta, onRunStart }) => {
18137
18177
  const startedAt = Date.now();
@@ -18249,16 +18289,14 @@ var init_generate = __esmMin((() => {
18249
18289
  } } : {}
18250
18290
  }, { signal: streamController.signal });
18251
18291
  const inactivityTimeout = Math.max(STREAM_INACTIVITY_TIMEOUT, ctx.maxTimeToFirstToken ?? 0);
18252
- const nextChunk = async () => {
18292
+ /** Resolves to undefined when no chunk arrives within the timeout. */
18293
+ const nextChunk = async (timeout) => {
18253
18294
  let timer;
18254
- const stalled = new Promise((_, reject) => {
18255
- timer = setTimeout(() => {
18256
- streamController.abort("LLM stream stalled");
18257
- reject(/* @__PURE__ */ new Error(`LLM stream stalled: no data received for ${inactivityTimeout}ms`));
18258
- }, inactivityTimeout);
18295
+ const expired = new Promise((resolve) => {
18296
+ timer = setTimeout(() => resolve(void 0), timeout);
18259
18297
  });
18260
18298
  try {
18261
- return await Promise.race([stream.next(), stalled]);
18299
+ return await Promise.race([stream.next(), expired]);
18262
18300
  } finally {
18263
18301
  clearTimeout(timer);
18264
18302
  }
@@ -18266,14 +18304,21 @@ var init_generate = __esmMin((() => {
18266
18304
  raw = "";
18267
18305
  let streamCompleted = false;
18268
18306
  let accepted = false;
18307
+ let closedAt;
18308
+ let servedModel = model.id;
18269
18309
  try {
18270
18310
  while (true) {
18271
18311
  let chunk;
18272
18312
  try {
18273
- chunk = await nextChunk();
18313
+ chunk = await nextChunk(closedAt === void 0 ? inactivityTimeout : closedAt + STREAM_END_GRACE_PERIOD - Date.now());
18274
18314
  } catch (thrown) {
18275
18315
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
18276
18316
  }
18317
+ if (!chunk && closedAt !== void 0) break;
18318
+ if (!chunk) {
18319
+ streamController.abort("LLM stream stalled");
18320
+ throw new CognitiveError(`LLM generation failed: LLM stream stalled: no data received for ${inactivityTimeout}ms`);
18321
+ }
18277
18322
  if (chunk.done) {
18278
18323
  streamCompleted = true;
18279
18324
  break;
@@ -18292,12 +18337,14 @@ var init_generate = __esmMin((() => {
18292
18337
  completions = [];
18293
18338
  accepted = false;
18294
18339
  parser = new ResponseParser();
18340
+ closedAt = void 0;
18295
18341
  liveItems.clear();
18296
18342
  liveContent.clear();
18297
18343
  codeGenerationTraced = false;
18298
18344
  runCompleted = false;
18299
18345
  responseMetadata = void 0;
18300
18346
  attempt = chunk.value.restart.attempt;
18347
+ servedModel = chunk.value.restart.toModel;
18301
18348
  await preview({
18302
18349
  ...chunk.value.restart,
18303
18350
  restart: true,
@@ -18314,6 +18361,12 @@ var init_generate = __esmMin((() => {
18314
18361
  timeToFirstToken ??= timeToLastToken;
18315
18362
  raw += delta;
18316
18363
  await dispatchSends(parser.push(delta));
18364
+ if (parser.done) break;
18365
+ if (parser.closed) closedAt ??= Date.now();
18366
+ }
18367
+ if (!responseMetadata && !streamCompleted) {
18368
+ const served = models.find((details) => details.id === servedModel) ?? await cognitive.getModelDetails(servedModel).catch(() => model);
18369
+ responseMetadata = estimateMetadata(served, messages, raw);
18317
18370
  }
18318
18371
  if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
18319
18372
  assertSuccessfulGeneration(responseMetadata);
@@ -1,5 +1,12 @@
1
1
  import type { StopReason } from '@botpress/cognitive';
2
2
  import type { Diagnostic, MessageStreamEvent, ParsedItem } from './types.js';
3
+ /**
4
+ * Text a model may write before ■start. Some models stream junk for minutes
5
+ * without ever opening the envelope; past this many characters, give up.
6
+ */
7
+ export declare const MAX_PREAMBLE_LENGTH = 8000;
8
+ /** Text a model may write after ■end before the rest of its stream is ignored. */
9
+ export declare const MAX_TRAILER_LENGTH = 2000;
3
10
  /** One wire format: a complete ■start … ■end envelope containing valid blocks. */
4
11
  export declare class ResponseParser {
5
12
  private _parser;
@@ -12,7 +19,12 @@ export declare class ResponseParser {
12
19
  private _finished;
13
20
  private _failure;
14
21
  private _trailer;
22
+ private _preambleLength;
15
23
  get framed(): boolean;
24
+ /** Whether ■end was received. Later text is only kept as a diagnostic. */
25
+ get closed(): boolean;
26
+ /** The model is writing junk past either boundary: stop reading the stream. */
27
+ get done(): boolean;
16
28
  get valid(): boolean;
17
29
  get items(): ParsedItem[];
18
30
  get diagnostics(): Diagnostic[];
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.9.12",
5
+ "version": "0.9.13",
6
6
  "files": [
7
7
  "dist",
8
8
  "DOCS.md"