llmz 0.6.7 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -79,6 +79,10 @@ return { ...payment, date: dates[0] }
79
79
 
80
80
  Because the protocol is parsed incrementally, messages stream to your UI token-by-token, tool calls surface live, and code execution can start before the response has even finished streaming.
81
81
 
82
+ Only explicit `■send` blocks produce messages or message deltas. Unexpected text outside protocol blocks is discarded in streaming, fallback restarts, and nonstreaming execution. Explicit Markdown (`■send=md`) is supported. A response with only unexpected text follows the normal invalid-response retry/error path. For debugging, `iteration.llm.output` retains the raw response and `iteration.llm.diagnostics` records parser diagnostics, including `unexpected-text`.
83
+
84
+ As of 0.7.0, the parser options `strict` and `recoveryComponent` have been removed. All parsing requires explicit send blocks; callers relying on implicit messages must emit `■send=<component>` instead.
85
+
82
86
  ---
83
87
 
84
88
  ## Quick Start
@@ -323,15 +327,22 @@ With a streaming client (`CognitiveBeta` / Cognitive v2), everything an executio
323
327
  const chat = new Chat({
324
328
  components: [DefaultComponents.Text],
325
329
  transcript: () => transcript,
326
- handler: async (component) => finalizeBubble(component),
330
+ handler: async (component, metadata) => finalizeBubble(component, metadata),
327
331
  // Fires per token-chunk while the LLM is still generating
328
- onMessageDelta: (delta) => appendToBubble(delta.id, delta.delta),
332
+ onMessageDelta: (delta) => {
333
+ if (delta.restart) {
334
+ // Remove ALL this iteration's messages, including completed handler sends.
335
+ return clearIterationMessages(delta.iterationId)
336
+ }
337
+ return updateBubble(delta.iterationId, delta.id, delta.content)
338
+ },
329
339
  })
330
340
 
331
341
  const result = await execute({
332
342
  client, // CognitiveBeta
333
343
  chat,
334
344
  tools,
345
+ options: { midStreamFallback: true },
335
346
  onTrace: ({ trace }) => {
336
347
  if (trace.type === 'llm_call_started') showSpinner()
337
348
  if (trace.type === 'code_generation_started') showStatus('writing code…')
@@ -343,7 +354,14 @@ const result = await execute({
343
354
 
344
355
  - **Message deltas** stream to your UI token-by-token (`handler` remains the authoritative delivery)
345
356
  - **Live traces** cover the full turn lifecycle: `llm_call_started` → message deltas → `code_generation_started` → `llm_call_success` (with the code) → `tool_call`s → exit
346
- - **Early execution**: the `■run` block starts executing while the tail of the response is still streaming, and the VM pre-warms the moment the model starts writing code
357
+ - **Fallback**: a `restart: true` delta invalidates the current iteration's messages before replacement output arrives. Replacement messages have fresh IDs under the same `iterationId`. Code executes only after the replacement stream succeeds; abandoned code never runs.
358
+ - **Early execution**: without `midStreamFallback`, a closed `■run` block can start executing while the response tail is still streaming. A later generation failure returns an error without retrying the code. The VM pre-warms when code generation begins in either mode.
359
+
360
+ Terminal failures return an `ErrorExecutionResult`; they do not emit a restart delta because there is no replacement attempt. Handle that result to clear or mark unfinished messages as failed. Already delivered messages remain delivered unless your consumer retracts them. Nonstreaming Cognitive fallback exposes only the successful response, so no message reset is needed.
361
+
362
+ LLMz rejects explicit Cognitive errors, error metadata (`provider: "unknown"`), and responses stopped by a token limit or content filter before finalizing unfinished blocks. Such failures cannot turn a partial code prefix into executable code. With fallback disabled, this cannot undo code that already ran after a block boundary.
363
+
364
+ `■run` followed by `■send` is currently accepted, but that message was generated without seeing the code's result. Prefer sending an acknowledgement before `■run`, returning the needed data, then answering in the next iteration. A top-level `return` always requests another iteration; side-effect-only code can finish immediately with `■next`.
347
365
 
348
366
  Forward these events over a websocket or SSE stream and your frontend renders the agent live — see [example 22](https://github.com/botpress/botpress/tree/master/packages/llmz/examples/22_chat_streaming).
349
367
 
package/dist/context.d.ts CHANGED
@@ -6,6 +6,7 @@ import { SnapshotSignal } from './errors.js';
6
6
  import { Exit } from './exit.js';
7
7
  import { ValueOrGetter } from './getter.js';
8
8
  import { HookedArray } from './handlers.js';
9
+ import type { Diagnostic } from './message-stream/types.js';
9
10
  import { ObjectInstance } from './objects.js';
10
11
  import { LLMzPrompts, ParsedNext, ParsedSend, Prompt } from './prompts/prompt.js';
11
12
  import { Snapshot } from './snapshots.js';
@@ -365,6 +366,7 @@ export declare namespace Iteration {
365
366
  tokens: number;
366
367
  spend: number;
367
368
  output: string;
369
+ diagnostics?: Diagnostic[];
368
370
  model: string;
369
371
  time_to_first_token?: number;
370
372
  time_to_last_token?: number;
@@ -420,6 +422,8 @@ export declare class Iteration implements Serializable<Iteration.JSON> {
420
422
  tokens: number;
421
423
  spend: number;
422
424
  output: string;
425
+ /** Syntax diagnostics from the response parser; raw output remains available above. */
426
+ diagnostics?: Diagnostic[];
423
427
  model: string;
424
428
  /** Milliseconds between the LLM call start and the first streamed token. Only set on streaming clients. */
425
429
  time_to_first_token?: number;
@@ -475,6 +479,8 @@ export declare class Iteration implements Serializable<Iteration.JSON> {
475
479
  tokens: number;
476
480
  spend: number;
477
481
  output: string;
482
+ /** Syntax diagnostics from the response parser; raw output remains available above. */
483
+ diagnostics?: Diagnostic[];
478
484
  model: string;
479
485
  /** Milliseconds between the LLM call start and the first streamed token. Only set on streaming clients. */
480
486
  time_to_first_token?: number;
@@ -637,6 +643,8 @@ export declare class Context implements Serializable<Context.JSON> {
637
643
  tokens: number;
638
644
  spend: number;
639
645
  output: string;
646
+ /** Syntax diagnostics from the response parser; raw output remains available above. */
647
+ diagnostics?: Diagnostic[];
640
648
  model: string;
641
649
  /** Milliseconds between the LLM call start and the first streamed token. Only set on streaming clients. */
642
650
  time_to_first_token?: number;
@@ -1,6 +1,6 @@
1
1
  const require__overArg = require("./_overArg-C5Hy7oCi.cjs");
2
2
  const require__baseIteratee = require("./_baseIteratee-CF4qP2a8.cjs");
3
- const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
3
+ const require_dual_modes = require("./dual-modes-CfqTk9M8.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");
@@ -1,6 +1,6 @@
1
1
  import { a as Symbol, n as isArray, o as root, r as isObjectLike } from "./_overArg-CRYbj1NH.js";
2
2
  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";
3
- import { f as flatRest, l as extractType, m as baseCreate, p as copyArray, t as DualModePrompt, u as inspect } from "./dual-modes-D_p-jedw.js";
3
+ import { f as flatRest, l as extractType, m as baseCreate, p as copyArray, t as DualModePrompt, u as inspect } from "./dual-modes-mNyMThtf.js";
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";
@@ -1615,6 +1615,8 @@ const tryParseJson = (text) => {
1615
1615
  * The parser is purely syntactic: it knows the reserved `■` symbol and the
1616
1616
  * directive grammar, but nothing about registered components, exits or their
1617
1617
  * schemas. Semantic validation is a separate step (see `validator.ts`).
1618
+ * Text outside protocol blocks is always discarded with a diagnostic; only
1619
+ * explicit `■send` blocks can produce messages.
1618
1620
  *
1619
1621
  * Pushing the same text split across arbitrary chunk boundaries always produces
1620
1622
  * the same items and the same concatenated body deltas. For a higher-level
@@ -1622,10 +1624,9 @@ const tryParseJson = (text) => {
1622
1624
  */
1623
1625
  var StreamingMessageParser = class {
1624
1626
  _maxPropsLength;
1625
- _strict;
1626
- _recoveryComponent;
1627
1627
  _state = "idle";
1628
1628
  _items = [];
1629
+ _diagnostics = [];
1629
1630
  _current;
1630
1631
  _currentReady = false;
1631
1632
  _counter = 0;
@@ -1641,13 +1642,15 @@ var StreamingMessageParser = class {
1641
1642
  _pendingWhitespace = "";
1642
1643
  constructor(options = {}) {
1643
1644
  this._maxPropsLength = options.maxPropsLength ?? 1e5;
1644
- this._strict = options.strict ?? false;
1645
- this._recoveryComponent = options.recoveryComponent ?? "md";
1646
1645
  }
1647
1646
  /** All items parsed so far, in order of appearance. */
1648
1647
  get items() {
1649
1648
  return [...this._items];
1650
1649
  }
1650
+ /** All syntax diagnostics, including discarded text that has no protocol item. */
1651
+ get diagnostics() {
1652
+ return [...this._diagnostics];
1653
+ }
1651
1654
  push(chunk) {
1652
1655
  if (this._finished) throw new Error("Parser has already finished. Call reset() before parsing a new stream.");
1653
1656
  const events = [];
@@ -1693,6 +1696,7 @@ var StreamingMessageParser = class {
1693
1696
  reset() {
1694
1697
  this._state = "idle";
1695
1698
  this._items = [];
1699
+ this._diagnostics = [];
1696
1700
  this._current = void 0;
1697
1701
  this._currentReady = false;
1698
1702
  this._counter = 0;
@@ -1711,7 +1715,7 @@ var StreamingMessageParser = class {
1711
1715
  switch (this._state) {
1712
1716
  case "idle":
1713
1717
  if (char === "■") this._beginItem();
1714
- else if (!isWhitespace(char)) this._beginRecovery(char, events);
1718
+ else if (!isWhitespace(char)) this._skipUnexpectedText(events);
1715
1719
  return;
1716
1720
  case "directive":
1717
1721
  if (char === "=") this._endDirective(events, "=");
@@ -1790,7 +1794,7 @@ var StreamingMessageParser = class {
1790
1794
  if (isWhitespace(char)) return;
1791
1795
  if (item.kind === "next") {
1792
1796
  this._completeCurrent(events);
1793
- this._beginRecovery(char, events);
1797
+ this._skipUnexpectedText(events);
1794
1798
  return;
1795
1799
  }
1796
1800
  this._startBody(events);
@@ -2036,46 +2040,15 @@ var StreamingMessageParser = class {
2036
2040
  this._current = void 0;
2037
2041
  this._currentReady = false;
2038
2042
  }
2039
- _beginRecovery(char, events) {
2040
- if (this._strict) {
2041
- this._diagnostic(events, {
2042
- code: "unexpected-text",
2043
- message: "Encountered text outside of a ■ block"
2044
- });
2045
- this._state = "skip";
2046
- return;
2047
- }
2048
- const item = {
2049
- id: `item-${this._counter++}`,
2050
- kind: "send",
2051
- name: this._recoveryComponent,
2052
- props: {},
2053
- body: "",
2054
- status: "pending",
2055
- diagnostics: []
2056
- };
2057
- this._items.push(item);
2058
- this._current = item;
2059
- this._currentReady = false;
2060
- this._pendingWhitespace = "";
2043
+ _skipUnexpectedText(events) {
2061
2044
  this._diagnostic(events, {
2062
2045
  code: "unexpected-text",
2063
- message: `Encountered text outside of a ■ block; recovered it into an implicit ■send=${this._recoveryComponent}`,
2064
- itemId: item.id
2065
- });
2066
- events.push({
2067
- type: "item-start",
2068
- item
2046
+ message: "Encountered text outside of a ■ block"
2069
2047
  });
2070
- this._ready(events);
2071
- events.push({
2072
- type: "body-start",
2073
- itemId: item.id
2074
- });
2075
- this._state = "body";
2076
- this._appendBody(char);
2048
+ this._state = "skip";
2077
2049
  }
2078
2050
  _diagnostic(events, diagnostic) {
2051
+ this._diagnostics.push(diagnostic);
2079
2052
  if (diagnostic.itemId) {
2080
2053
  const item = this._items.find((i) => i.id === diagnostic.itemId);
2081
2054
  item === null || item === void 0 || item.diagnostics.push(diagnostic);
@@ -2094,7 +2067,7 @@ const stripWrappingFences = (text) => text.trim().split("\n").filter((line, inde
2094
2067
  return true;
2095
2068
  }).join("\n");
2096
2069
  /** Builds a {@link ParsedAssistantResponse} from parsed protocol items. */
2097
- const toParsedAssistantResponse = (items, raw) => {
2070
+ const toParsedAssistantResponse = (items, raw, diagnostics = []) => {
2098
2071
  var _run$body;
2099
2072
  const sends = items.filter((item) => item.kind === "send").map((item) => ({
2100
2073
  name: item.name,
@@ -2106,6 +2079,7 @@ const toParsedAssistantResponse = (items, raw) => {
2106
2079
  return {
2107
2080
  raw,
2108
2081
  items,
2082
+ diagnostics,
2109
2083
  sends,
2110
2084
  code: (run === null || run === void 0 || (_run$body = run.body) === null || _run$body === void 0 ? void 0 : _run$body.trim()) || void 0,
2111
2085
  next: next ? {
@@ -2118,7 +2092,7 @@ const parseAssistantResponse = (response) => {
2118
2092
  const parser = new StreamingMessageParser();
2119
2093
  parser.push(stripWrappingFences(response));
2120
2094
  parser.finish();
2121
- return toParsedAssistantResponse(parser.items, response);
2095
+ return toParsedAssistantResponse(parser.items, response, parser.diagnostics);
2122
2096
  };
2123
2097
  const replacePlaceholders = (prompt, values) => {
2124
2098
  const regex = /* @__PURE__ */ new RegExp("■■■([A-Z0-9_\\.-]+)■■■", "gi");
@@ -1614,6 +1614,8 @@ const tryParseJson = (text) => {
1614
1614
  * The parser is purely syntactic: it knows the reserved `■` symbol and the
1615
1615
  * directive grammar, but nothing about registered components, exits or their
1616
1616
  * schemas. Semantic validation is a separate step (see `validator.ts`).
1617
+ * Text outside protocol blocks is always discarded with a diagnostic; only
1618
+ * explicit `■send` blocks can produce messages.
1617
1619
  *
1618
1620
  * Pushing the same text split across arbitrary chunk boundaries always produces
1619
1621
  * the same items and the same concatenated body deltas. For a higher-level
@@ -1621,10 +1623,9 @@ const tryParseJson = (text) => {
1621
1623
  */
1622
1624
  var StreamingMessageParser = class {
1623
1625
  _maxPropsLength;
1624
- _strict;
1625
- _recoveryComponent;
1626
1626
  _state = "idle";
1627
1627
  _items = [];
1628
+ _diagnostics = [];
1628
1629
  _current;
1629
1630
  _currentReady = false;
1630
1631
  _counter = 0;
@@ -1640,13 +1641,15 @@ var StreamingMessageParser = class {
1640
1641
  _pendingWhitespace = "";
1641
1642
  constructor(options = {}) {
1642
1643
  this._maxPropsLength = options.maxPropsLength ?? 1e5;
1643
- this._strict = options.strict ?? false;
1644
- this._recoveryComponent = options.recoveryComponent ?? "md";
1645
1644
  }
1646
1645
  /** All items parsed so far, in order of appearance. */
1647
1646
  get items() {
1648
1647
  return [...this._items];
1649
1648
  }
1649
+ /** All syntax diagnostics, including discarded text that has no protocol item. */
1650
+ get diagnostics() {
1651
+ return [...this._diagnostics];
1652
+ }
1650
1653
  push(chunk) {
1651
1654
  if (this._finished) throw new Error("Parser has already finished. Call reset() before parsing a new stream.");
1652
1655
  const events = [];
@@ -1692,6 +1695,7 @@ var StreamingMessageParser = class {
1692
1695
  reset() {
1693
1696
  this._state = "idle";
1694
1697
  this._items = [];
1698
+ this._diagnostics = [];
1695
1699
  this._current = void 0;
1696
1700
  this._currentReady = false;
1697
1701
  this._counter = 0;
@@ -1710,7 +1714,7 @@ var StreamingMessageParser = class {
1710
1714
  switch (this._state) {
1711
1715
  case "idle":
1712
1716
  if (char === "■") this._beginItem();
1713
- else if (!isWhitespace(char)) this._beginRecovery(char, events);
1717
+ else if (!isWhitespace(char)) this._skipUnexpectedText(events);
1714
1718
  return;
1715
1719
  case "directive":
1716
1720
  if (char === "=") this._endDirective(events, "=");
@@ -1789,7 +1793,7 @@ var StreamingMessageParser = class {
1789
1793
  if (isWhitespace(char)) return;
1790
1794
  if (item.kind === "next") {
1791
1795
  this._completeCurrent(events);
1792
- this._beginRecovery(char, events);
1796
+ this._skipUnexpectedText(events);
1793
1797
  return;
1794
1798
  }
1795
1799
  this._startBody(events);
@@ -2035,46 +2039,15 @@ var StreamingMessageParser = class {
2035
2039
  this._current = void 0;
2036
2040
  this._currentReady = false;
2037
2041
  }
2038
- _beginRecovery(char, events) {
2039
- if (this._strict) {
2040
- this._diagnostic(events, {
2041
- code: "unexpected-text",
2042
- message: "Encountered text outside of a ■ block"
2043
- });
2044
- this._state = "skip";
2045
- return;
2046
- }
2047
- const item = {
2048
- id: `item-${this._counter++}`,
2049
- kind: "send",
2050
- name: this._recoveryComponent,
2051
- props: {},
2052
- body: "",
2053
- status: "pending",
2054
- diagnostics: []
2055
- };
2056
- this._items.push(item);
2057
- this._current = item;
2058
- this._currentReady = false;
2059
- this._pendingWhitespace = "";
2042
+ _skipUnexpectedText(events) {
2060
2043
  this._diagnostic(events, {
2061
2044
  code: "unexpected-text",
2062
- message: `Encountered text outside of a ■ block; recovered it into an implicit ■send=${this._recoveryComponent}`,
2063
- itemId: item.id
2064
- });
2065
- events.push({
2066
- type: "item-start",
2067
- item
2045
+ message: "Encountered text outside of a ■ block"
2068
2046
  });
2069
- this._ready(events);
2070
- events.push({
2071
- type: "body-start",
2072
- itemId: item.id
2073
- });
2074
- this._state = "body";
2075
- this._appendBody(char);
2047
+ this._state = "skip";
2076
2048
  }
2077
2049
  _diagnostic(events, diagnostic) {
2050
+ this._diagnostics.push(diagnostic);
2078
2051
  if (diagnostic.itemId) {
2079
2052
  const item = this._items.find((i) => i.id === diagnostic.itemId);
2080
2053
  item === null || item === void 0 || item.diagnostics.push(diagnostic);
@@ -2093,7 +2066,7 @@ const stripWrappingFences = (text) => text.trim().split("\n").filter((line, inde
2093
2066
  return true;
2094
2067
  }).join("\n");
2095
2068
  /** Builds a {@link ParsedAssistantResponse} from parsed protocol items. */
2096
- const toParsedAssistantResponse = (items, raw) => {
2069
+ const toParsedAssistantResponse = (items, raw, diagnostics = []) => {
2097
2070
  var _run$body;
2098
2071
  const sends = items.filter((item) => item.kind === "send").map((item) => ({
2099
2072
  name: item.name,
@@ -2105,6 +2078,7 @@ const toParsedAssistantResponse = (items, raw) => {
2105
2078
  return {
2106
2079
  raw,
2107
2080
  items,
2081
+ diagnostics,
2108
2082
  sends,
2109
2083
  code: (run === null || run === void 0 || (_run$body = run.body) === null || _run$body === void 0 ? void 0 : _run$body.trim()) || void 0,
2110
2084
  next: next ? {
@@ -2117,7 +2091,7 @@ const parseAssistantResponse = (response) => {
2117
2091
  const parser = new StreamingMessageParser();
2118
2092
  parser.push(stripWrappingFences(response));
2119
2093
  parser.finish();
2120
- return toParsedAssistantResponse(parser.items, response);
2094
+ return toParsedAssistantResponse(parser.items, response, parser.diagnostics);
2121
2095
  };
2122
2096
  const replacePlaceholders = (prompt, values) => {
2123
2097
  const regex = /* @__PURE__ */ new RegExp("■■■([A-Z0-9_\\.-]+)■■■", "gi");
@@ -1,5 +1,5 @@
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-CJIThG2H.js";
1
+ import { a as StreamingMessageParser, d as isEqual, h as toNumber, i as toParsedAssistantResponse } from "./dual-modes-mNyMThtf.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-CMuy7fXU.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";
@@ -120,11 +120,16 @@ const RESPONSE_LENGTH_BUFFER = {
120
120
  };
121
121
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
122
122
  const STREAM_INACTIVITY_TIMEOUT = 18e4;
123
+ /** A syntactically valid prefix must not execute when generation did not finish successfully. */
124
+ const assertSuccessfulGeneration = (metadata) => {
125
+ if (metadata.provider === "unknown") throw new CognitiveError("LLM generation failed: received error metadata with unknown provider");
126
+ if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter") throw new CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
127
+ };
123
128
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
124
129
  /**
125
130
  * Models sometimes wrap their whole response in a code fence. The fence has to
126
- * be removed before it reaches the incremental parser, otherwise it would be
127
- * recovered as an unexpected-text message. Works on arbitrary chunk boundaries
131
+ * be removed before it reaches the incremental parser to avoid a spurious
132
+ * unexpected-text diagnostic. Works on arbitrary chunk boundaries
128
133
  * by holding content back until the first newline.
129
134
  */
130
135
  var LeadingFenceFilter = class {
@@ -330,10 +335,11 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
330
335
  await dispatchSends(parser.push(fence.push(delta)));
331
336
  }
332
337
  if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
338
+ assertSuccessfulGeneration(responseMetadata);
333
339
  const remaining = fence.flush();
334
340
  if (remaining) await dispatchSends(parser.push(remaining));
335
341
  await dispatchSends(parser.finish());
336
- assistantResponse = toParsedAssistantResponse(parser.items, raw);
342
+ assistantResponse = toParsedAssistantResponse(parser.items, raw, parser.diagnostics);
337
343
  } finally {
338
344
  streamController.abort("LLM stream closed");
339
345
  if (!streamCompleted) stream.return(void 0).catch((err) => {});
@@ -343,8 +349,10 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
343
349
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
344
350
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
345
351
  });
352
+ if (response.error) throw new CognitiveError(`LLM generation failed: ${response.error}`);
346
353
  if (!response.output) throw new CognitiveError("LLM did not return any text output");
347
354
  responseMetadata = response.metadata;
355
+ assertSuccessfulGeneration(responseMetadata);
348
356
  raw = response.output;
349
357
  assistantResponse = ctx.version.parseAssistantResponse(raw);
350
358
  for (const [index, send] of assistantResponse.sends.entries()) await (onSend === null || onSend === void 0 ? void 0 : onSend(send, messageMetadata(`send-${index}`)));
@@ -361,6 +369,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
361
369
  tokens: usage.inputTokens + usage.outputTokens,
362
370
  spend: responseMetadata.cost ?? usage.inputCost + usage.outputCost,
363
371
  output: assistantResponse.raw,
372
+ diagnostics: assistantResponse.diagnostics,
364
373
  model: `${responseMetadata.provider}:${responseMetadata.model}`,
365
374
  time_to_first_token: timeToFirstToken,
366
375
  time_to_last_token: timeToLastToken,
@@ -1,6 +1,6 @@
1
1
  const require__baseForOwn = require("./_baseForOwn-buOZDFbK.cjs");
2
- const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
3
- const require_custom_client = require("./custom-client-Cr33K60k.cjs");
2
+ const require_dual_modes = require("./dual-modes-CfqTk9M8.cjs");
3
+ const require_custom_client = require("./custom-client-Bp1TgTKk.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");
@@ -122,11 +122,16 @@ const RESPONSE_LENGTH_BUFFER = {
122
122
  };
123
123
  /** Maximum time to wait between two stream chunks before considering the stream stalled. */
124
124
  const STREAM_INACTIVITY_TIMEOUT = 18e4;
125
+ /** A syntactically valid prefix must not execute when generation did not finish successfully. */
126
+ const assertSuccessfulGeneration = (metadata) => {
127
+ if (metadata.provider === "unknown") throw new require_errors.CognitiveError("LLM generation failed: received error metadata with unknown provider");
128
+ if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter") throw new require_errors.CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
129
+ };
125
130
  const getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
126
131
  /**
127
132
  * Models sometimes wrap their whole response in a code fence. The fence has to
128
- * be removed before it reaches the incremental parser, otherwise it would be
129
- * recovered as an unexpected-text message. Works on arbitrary chunk boundaries
133
+ * be removed before it reaches the incremental parser to avoid a spurious
134
+ * unexpected-text diagnostic. Works on arbitrary chunk boundaries
130
135
  * by holding content back until the first newline.
131
136
  */
132
137
  var LeadingFenceFilter = class {
@@ -332,10 +337,11 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
332
337
  await dispatchSends(parser.push(fence.push(delta)));
333
338
  }
334
339
  if (!responseMetadata) throw new require_errors.CognitiveError("LLM streaming completed without metadata");
340
+ assertSuccessfulGeneration(responseMetadata);
335
341
  const remaining = fence.flush();
336
342
  if (remaining) await dispatchSends(parser.push(remaining));
337
343
  await dispatchSends(parser.finish());
338
- assistantResponse = require_dual_modes.toParsedAssistantResponse(parser.items, raw);
344
+ assistantResponse = require_dual_modes.toParsedAssistantResponse(parser.items, raw, parser.diagnostics);
339
345
  } finally {
340
346
  streamController.abort("LLM stream closed");
341
347
  if (!streamCompleted) stream.return(void 0).catch((err) => {});
@@ -345,8 +351,10 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
345
351
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
346
352
  throw new require_errors.CognitiveError(`LLM generation failed: ${require_utils.getErrorMessage(thrown)}`);
347
353
  });
354
+ if (response.error) throw new require_errors.CognitiveError(`LLM generation failed: ${response.error}`);
348
355
  if (!response.output) throw new require_errors.CognitiveError("LLM did not return any text output");
349
356
  responseMetadata = response.metadata;
357
+ assertSuccessfulGeneration(responseMetadata);
350
358
  raw = response.output;
351
359
  assistantResponse = ctx.version.parseAssistantResponse(raw);
352
360
  for (const [index, send] of assistantResponse.sends.entries()) await (onSend === null || onSend === void 0 ? void 0 : onSend(send, messageMetadata(`send-${index}`)));
@@ -363,6 +371,7 @@ const generateCode = async ({ iteration, ctx, cognitive, controller, metadata, o
363
371
  tokens: usage.inputTokens + usage.outputTokens,
364
372
  spend: responseMetadata.cost ?? usage.inputCost + usage.outputCost,
365
373
  output: assistantResponse.raw,
374
+ diagnostics: assistantResponse.diagnostics,
366
375
  model: `${responseMetadata.provider}:${responseMetadata.model}`,
367
376
  time_to_first_token: timeToFirstToken,
368
377
  time_to_last_token: timeToLastToken,
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dual_modes = require("./dual-modes-wAOAlaBS.cjs");
3
- const require_custom_client = require("./custom-client-Cr33K60k.cjs");
2
+ const require_dual_modes = require("./dual-modes-CfqTk9M8.cjs");
3
+ const require_custom_client = require("./custom-client-Bp1TgTKk.cjs");
4
4
  const require_utils = require("./utils-ZpJm00ed.cjs");
5
5
  const require_tool = require("./tool-ReA9gxMp.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.7";
14
+ var version = "0.7.0";
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-BmEjJjka.cjs"));
1777
+ const { executeContext } = await Promise.resolve().then(() => require("./execute-msui51b2.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-BmEjJjka.cjs"));
1786
+ await Promise.resolve().then(() => require("./execute-msui51b2.cjs"));
1787
1787
  await Promise.resolve().then(() => require("./component-BwixNVH8.cjs")).then((n) => n.component_exports);
1788
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);
@@ -1792,7 +1792,7 @@ const init = async () => {
1792
1792
  await Promise.resolve().then(() => require("./utils-ZpJm00ed.cjs")).then((n) => n.utils_exports);
1793
1793
  await Promise.resolve().then(() => require("./truncator-B6aaJaN1.cjs")).then((n) => n.truncator_exports);
1794
1794
  await Promise.resolve().then(() => require("./typings-BwAz_TM9.cjs")).then((n) => n.typings_exports);
1795
- await Promise.resolve().then(() => require("./dual-modes-wAOAlaBS.cjs")).then((n) => n.dual_modes_exports);
1795
+ await Promise.resolve().then(() => require("./dual-modes-CfqTk9M8.cjs")).then((n) => n.dual_modes_exports);
1796
1796
  };
1797
1797
  //#endregion
1798
1798
  exports.BaseResponse = BaseResponse;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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-CJIThG2H.js";
1
+ import { a as StreamingMessageParser, c as NAME_REGEX, o as tryParseJson, r as generateInstructions, s as MARKER } from "./dual-modes-mNyMThtf.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-CMuy7fXU.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-Bv8N4IPj.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.7";
13
+ var version = "0.7.0";
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-sn9RT_IN.js");
1776
+ const { executeContext } = await import("./execute-DP_17lYZ.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-sn9RT_IN.js");
1785
+ await import("./execute-DP_17lYZ.js");
1786
1786
  await import("./component-DGG7d_Mz.js").then((n) => n.r);
1787
1787
  await import("./tool-Bv8N4IPj.js").then((n) => n.n);
1788
1788
  await import("./exit-CDdxDBRT.js").then((n) => n.n);
@@ -1791,7 +1791,7 @@ const init = async () => {
1791
1791
  await import("./utils-UBeAFeEI.js").then((n) => n.h);
1792
1792
  await import("./truncator-DZqEqnpD.js").then((n) => n.r);
1793
1793
  await import("./typings-CrBa7Tyy.js").then((n) => n.n);
1794
- await import("./dual-modes-D_p-jedw.js").then((n) => n.n);
1794
+ await import("./dual-modes-mNyMThtf.js").then((n) => n.n);
1795
1795
  };
1796
1796
  //#endregion
1797
1797
  export { BaseResponse, Chat, CitationsManager, Component, ComponentRegistry, DefaultComponents, DefaultExit, ErrorExecutionResult, 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.6.7";
55
+ var version = "0.7.0";
56
56
  //#endregion
57
57
  //#region ../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/_freeGlobal.js
58
58
  var freeGlobal;
@@ -10830,10 +10830,9 @@ var init_parser = __esmMin((() => {
10830
10830
  };
10831
10831
  StreamingMessageParser = class {
10832
10832
  _maxPropsLength;
10833
- _strict;
10834
- _recoveryComponent;
10835
10833
  _state = "idle";
10836
10834
  _items = [];
10835
+ _diagnostics = [];
10837
10836
  _current;
10838
10837
  _currentReady = false;
10839
10838
  _counter = 0;
@@ -10849,13 +10848,15 @@ var init_parser = __esmMin((() => {
10849
10848
  _pendingWhitespace = "";
10850
10849
  constructor(options = {}) {
10851
10850
  this._maxPropsLength = options.maxPropsLength ?? 1e5;
10852
- this._strict = options.strict ?? false;
10853
- this._recoveryComponent = options.recoveryComponent ?? "md";
10854
10851
  }
10855
10852
  /** All items parsed so far, in order of appearance. */
10856
10853
  get items() {
10857
10854
  return [...this._items];
10858
10855
  }
10856
+ /** All syntax diagnostics, including discarded text that has no protocol item. */
10857
+ get diagnostics() {
10858
+ return [...this._diagnostics];
10859
+ }
10859
10860
  push(chunk) {
10860
10861
  if (this._finished) throw new Error("Parser has already finished. Call reset() before parsing a new stream.");
10861
10862
  const events = [];
@@ -10901,6 +10902,7 @@ var init_parser = __esmMin((() => {
10901
10902
  reset() {
10902
10903
  this._state = "idle";
10903
10904
  this._items = [];
10905
+ this._diagnostics = [];
10904
10906
  this._current = void 0;
10905
10907
  this._currentReady = false;
10906
10908
  this._counter = 0;
@@ -10919,7 +10921,7 @@ var init_parser = __esmMin((() => {
10919
10921
  switch (this._state) {
10920
10922
  case "idle":
10921
10923
  if (char === "■") this._beginItem();
10922
- else if (!isWhitespace(char)) this._beginRecovery(char, events);
10924
+ else if (!isWhitespace(char)) this._skipUnexpectedText(events);
10923
10925
  return;
10924
10926
  case "directive":
10925
10927
  if (char === "=") this._endDirective(events, "=");
@@ -10998,7 +11000,7 @@ var init_parser = __esmMin((() => {
10998
11000
  if (isWhitespace(char)) return;
10999
11001
  if (item.kind === "next") {
11000
11002
  this._completeCurrent(events);
11001
- this._beginRecovery(char, events);
11003
+ this._skipUnexpectedText(events);
11002
11004
  return;
11003
11005
  }
11004
11006
  this._startBody(events);
@@ -11244,46 +11246,15 @@ var init_parser = __esmMin((() => {
11244
11246
  this._current = void 0;
11245
11247
  this._currentReady = false;
11246
11248
  }
11247
- _beginRecovery(char, events) {
11248
- if (this._strict) {
11249
- this._diagnostic(events, {
11250
- code: "unexpected-text",
11251
- message: "Encountered text outside of a ■ block"
11252
- });
11253
- this._state = "skip";
11254
- return;
11255
- }
11256
- const item = {
11257
- id: `item-${this._counter++}`,
11258
- kind: "send",
11259
- name: this._recoveryComponent,
11260
- props: {},
11261
- body: "",
11262
- status: "pending",
11263
- diagnostics: []
11264
- };
11265
- this._items.push(item);
11266
- this._current = item;
11267
- this._currentReady = false;
11268
- this._pendingWhitespace = "";
11249
+ _skipUnexpectedText(events) {
11269
11250
  this._diagnostic(events, {
11270
11251
  code: "unexpected-text",
11271
- message: `Encountered text outside of a ■ block; recovered it into an implicit ■send=${this._recoveryComponent}`,
11272
- itemId: item.id
11273
- });
11274
- events.push({
11275
- type: "item-start",
11276
- item
11277
- });
11278
- this._ready(events);
11279
- events.push({
11280
- type: "body-start",
11281
- itemId: item.id
11252
+ message: "Encountered text outside of a ■ block"
11282
11253
  });
11283
- this._state = "body";
11284
- this._appendBody(char);
11254
+ this._state = "skip";
11285
11255
  }
11286
11256
  _diagnostic(events, diagnostic) {
11257
+ this._diagnostics.push(diagnostic);
11287
11258
  if (diagnostic.itemId) this._items.find((i) => i.id === diagnostic.itemId)?.diagnostics.push(diagnostic);
11288
11259
  events.push({
11289
11260
  type: "diagnostic",
@@ -11301,7 +11272,7 @@ var init_common = __esmMin((() => {
11301
11272
  if ((index === 0 || index === arr.length - 1) && line.trim().startsWith("```")) return false;
11302
11273
  return true;
11303
11274
  }).join("\n");
11304
- toParsedAssistantResponse = (items, raw) => {
11275
+ toParsedAssistantResponse = (items, raw, diagnostics = []) => {
11305
11276
  const sends = items.filter((item) => item.kind === "send").map((item) => ({
11306
11277
  name: item.name,
11307
11278
  props: item.props,
@@ -11312,6 +11283,7 @@ var init_common = __esmMin((() => {
11312
11283
  return {
11313
11284
  raw,
11314
11285
  items,
11286
+ diagnostics,
11315
11287
  sends,
11316
11288
  code: run?.body?.trim() || void 0,
11317
11289
  next: next ? {
@@ -11324,7 +11296,7 @@ var init_common = __esmMin((() => {
11324
11296
  const parser = new StreamingMessageParser();
11325
11297
  parser.push(stripWrappingFences(response));
11326
11298
  parser.finish();
11327
- return toParsedAssistantResponse(parser.items, response);
11299
+ return toParsedAssistantResponse(parser.items, response, parser.diagnostics);
11328
11300
  };
11329
11301
  replacePlaceholders = (prompt, values) => {
11330
11302
  const regex = /* @__PURE__ */ new RegExp("■■■([A-Z0-9_\\.-]+)■■■", "gi");
@@ -17025,7 +16997,7 @@ var init_vm = __esmMin((() => {
17025
16997
  }));
17026
16998
  //#endregion
17027
16999
  //#region src/runtime/generate.ts
17028
- var RESPONSE_LENGTH_BUFFER, STREAM_INACTIVITY_TIMEOUT, getModelOutputLimit, LeadingFenceFilter, generateCode;
17000
+ var RESPONSE_LENGTH_BUFFER, STREAM_INACTIVITY_TIMEOUT, assertSuccessfulGeneration, getModelOutputLimit, LeadingFenceFilter, generateCode;
17029
17001
  var init_generate = __esmMin((() => {
17030
17002
  init_lodash();
17031
17003
  init_abort_signal();
@@ -17040,6 +17012,10 @@ var init_generate = __esmMin((() => {
17040
17012
  PERCENTAGE: .1
17041
17013
  };
17042
17014
  STREAM_INACTIVITY_TIMEOUT = 18e4;
17015
+ assertSuccessfulGeneration = (metadata) => {
17016
+ if (metadata.provider === "unknown") throw new CognitiveError("LLM generation failed: received error metadata with unknown provider");
17017
+ if (metadata.stopReason === "max_tokens" || metadata.stopReason === "content_filter") throw new CognitiveError(`LLM generation did not complete: stopReason=${metadata.stopReason}`);
17018
+ };
17043
17019
  getModelOutputLimit = (inputLength) => clamp(RESPONSE_LENGTH_BUFFER.PERCENTAGE * inputLength, RESPONSE_LENGTH_BUFFER.MIN_TOKENS, RESPONSE_LENGTH_BUFFER.MAX_TOKENS);
17044
17020
  LeadingFenceFilter = class {
17045
17021
  _buffer = "";
@@ -17243,10 +17219,11 @@ var init_generate = __esmMin((() => {
17243
17219
  await dispatchSends(parser.push(fence.push(delta)));
17244
17220
  }
17245
17221
  if (!responseMetadata) throw new CognitiveError("LLM streaming completed without metadata");
17222
+ assertSuccessfulGeneration(responseMetadata);
17246
17223
  const remaining = fence.flush();
17247
17224
  if (remaining) await dispatchSends(parser.push(remaining));
17248
17225
  await dispatchSends(parser.finish());
17249
- assistantResponse = toParsedAssistantResponse(parser.items, raw);
17226
+ assistantResponse = toParsedAssistantResponse(parser.items, raw, parser.diagnostics);
17250
17227
  } finally {
17251
17228
  streamController.abort("LLM stream closed");
17252
17229
  if (!streamCompleted) stream.return(void 0).catch((err) => {});
@@ -17256,8 +17233,10 @@ var init_generate = __esmMin((() => {
17256
17233
  const response = await cognitive.generateText(input, { signal: controller.signal }).catch((thrown) => {
17257
17234
  throw new CognitiveError(`LLM generation failed: ${getErrorMessage(thrown)}`);
17258
17235
  });
17236
+ if (response.error) throw new CognitiveError(`LLM generation failed: ${response.error}`);
17259
17237
  if (!response.output) throw new CognitiveError("LLM did not return any text output");
17260
17238
  responseMetadata = response.metadata;
17239
+ assertSuccessfulGeneration(responseMetadata);
17261
17240
  raw = response.output;
17262
17241
  assistantResponse = ctx.version.parseAssistantResponse(raw);
17263
17242
  for (const [index, send] of assistantResponse.sends.entries()) await onSend?.(send, messageMetadata(`send-${index}`));
@@ -17274,6 +17253,7 @@ var init_generate = __esmMin((() => {
17274
17253
  tokens: usage.inputTokens + usage.outputTokens,
17275
17254
  spend: responseMetadata.cost ?? usage.inputCost + usage.outputCost,
17276
17255
  output: assistantResponse.raw,
17256
+ diagnostics: assistantResponse.diagnostics,
17277
17257
  model: `${responseMetadata.provider}:${responseMetadata.model}`,
17278
17258
  time_to_first_token: timeToFirstToken,
17279
17259
  time_to_last_token: timeToLastToken,
@@ -1,11 +1,7 @@
1
- import { type MessageStreamEvent, type ParsedItem } from './types.js';
1
+ import { type Diagnostic, type MessageStreamEvent, type ParsedItem } from './types.js';
2
2
  export type StreamingParserOptions = {
3
3
  /** Maximum number of characters buffered for a props object before the item is marked invalid. Default: 100 000. */
4
4
  maxPropsLength?: number;
5
- /** When true, unexpected free text is dropped (with a diagnostic) instead of being recovered into an implicit `send`. Default: false. */
6
- strict?: boolean;
7
- /** Component used to recover unexpected free text into an implicit `■send`. Default: 'md'. */
8
- recoveryComponent?: string;
9
5
  };
10
6
  /**
11
7
  * Lenient JSON parsing: JSON5 first (unquoted keys, single quotes, trailing
@@ -18,6 +14,8 @@ export declare const tryParseJson: (text: string) => unknown;
18
14
  * The parser is purely syntactic: it knows the reserved `■` symbol and the
19
15
  * directive grammar, but nothing about registered components, exits or their
20
16
  * schemas. Semantic validation is a separate step (see `validator.ts`).
17
+ * Text outside protocol blocks is always discarded with a diagnostic; only
18
+ * explicit `■send` blocks can produce messages.
21
19
  *
22
20
  * Pushing the same text split across arbitrary chunk boundaries always produces
23
21
  * the same items and the same concatenated body deltas. For a higher-level
@@ -25,10 +23,9 @@ export declare const tryParseJson: (text: string) => unknown;
25
23
  */
26
24
  export declare class StreamingMessageParser {
27
25
  private _maxPropsLength;
28
- private _strict;
29
- private _recoveryComponent;
30
26
  private _state;
31
27
  private _items;
28
+ private _diagnostics;
32
29
  private _current;
33
30
  private _currentReady;
34
31
  private _counter;
@@ -45,6 +42,8 @@ export declare class StreamingMessageParser {
45
42
  constructor(options?: StreamingParserOptions);
46
43
  /** All items parsed so far, in order of appearance. */
47
44
  get items(): ParsedItem[];
45
+ /** All syntax diagnostics, including discarded text that has no protocol item. */
46
+ get diagnostics(): Diagnostic[];
48
47
  push(chunk: string): MessageStreamEvent[];
49
48
  /**
50
49
  * Signals the end of the transport stream.
@@ -76,6 +75,6 @@ export declare class StreamingMessageParser {
76
75
  private _flushBodyDelta;
77
76
  private _ready;
78
77
  private _completeCurrent;
79
- private _beginRecovery;
78
+ private _skipUnexpectedText;
80
79
  private _diagnostic;
81
80
  }
@@ -1,6 +1,6 @@
1
- import type { ParsedItem } from '../message-stream/types.js';
1
+ import type { Diagnostic, ParsedItem } from '../message-stream/types.js';
2
2
  import { ParsedAssistantResponse } from './prompt.js';
3
3
  /** Builds a {@link ParsedAssistantResponse} from parsed protocol items. */
4
- export declare const toParsedAssistantResponse: (items: ParsedItem[], raw: string) => ParsedAssistantResponse;
4
+ export declare const toParsedAssistantResponse: (items: ParsedItem[], raw: string, diagnostics?: Diagnostic[]) => ParsedAssistantResponse;
5
5
  export declare const parseAssistantResponse: (response: string) => ParsedAssistantResponse;
6
6
  export declare const replacePlaceholders: (prompt: string, values: Record<string, unknown>) => string;
@@ -1,7 +1,7 @@
1
1
  import { type CognitiveMessage } from '@botpress/cognitive';
2
2
  import { Component } from '../component.js';
3
3
  import { Exit } from '../exit.js';
4
- import type { ParsedItem } from '../message-stream/types.js';
4
+ import type { Diagnostic, ParsedItem } from '../message-stream/types.js';
5
5
  import type { ObjectInstance } from '../objects.js';
6
6
  import { Snapshot } from '../snapshots.js';
7
7
  import { type Tool } from '../tool.js';
@@ -21,6 +21,8 @@ export type ParsedAssistantResponse = {
21
21
  raw: string;
22
22
  /** All protocol items, in order of appearance. */
23
23
  items: ParsedItem[];
24
+ /** Syntax diagnostics, including unexpected text discarded outside protocol blocks. */
25
+ diagnostics?: Diagnostic[];
24
26
  /** Messages to send to the user, in order. */
25
27
  sends: ParsedSend[];
26
28
  /** The body of the `■run` block, if any. */
package/dist/result.d.ts CHANGED
@@ -427,6 +427,7 @@ export declare class SuccessExecutionResult<TOutput = unknown> extends Execution
427
427
  tokens: number;
428
428
  spend: number;
429
429
  output: string;
430
+ diagnostics?: import("./index.js").Diagnostic[];
430
431
  model: string;
431
432
  time_to_first_token?: number;
432
433
  time_to_last_token?: number;
@@ -592,6 +593,7 @@ export declare class ErrorExecutionResult extends ExecutionResult implements Ser
592
593
  tokens: number;
593
594
  spend: number;
594
595
  output: string;
596
+ diagnostics?: import("./index.js").Diagnostic[];
595
597
  model: string;
596
598
  time_to_first_token?: number;
597
599
  time_to_last_token?: number;
@@ -777,6 +779,7 @@ export declare class PartialExecutionResult extends ExecutionResult implements S
777
779
  tokens: number;
778
780
  spend: number;
779
781
  output: string;
782
+ diagnostics?: import("./index.js").Diagnostic[];
780
783
  model: string;
781
784
  time_to_first_token?: number;
782
785
  time_to_last_token?: number;
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.7",
5
+ "version": "0.7.0",
6
6
  "files": [
7
7
  "dist",
8
8
  "DOCS.md"
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "peerDependencies": {
71
71
  "@botpress/client": "2.5.0",
72
- "@botpress/cognitive": "1.2.0",
72
+ "@botpress/cognitive": "1.2.1",
73
73
  "@bpinternal/thicktoken": "^3.1.0",
74
74
  "@bpinternal/zui": "^2.4.1"
75
75
  },