llmz 1.0.1 → 1.0.2

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.
Files changed (51) hide show
  1. package/DOCS.md +8 -4
  2. package/ERRORS.md +5 -2
  3. package/dist/{component-BpMVZYOa.cjs → component-CMFoCHyu.cjs} +3 -3
  4. package/dist/{component-C27qu6Zv.js → component-DncqP4fB.js} +3 -3
  5. package/dist/context.d.ts +1 -1
  6. package/dist/{custom-client-BDkgBR2w.js → custom-client-C-WXvLD9.js} +37 -13
  7. package/dist/{custom-client-MytucGI-.cjs → custom-client-Cgt9_0r1.cjs} +37 -13
  8. package/dist/errors/catalog.d.ts +7 -0
  9. package/dist/{errors-IhnTKUp7.cjs → errors-C6ppQUZX.cjs} +22 -0
  10. package/dist/{errors-H4JJFH3S.js → errors-Crk5P8jo.js} +17 -1
  11. package/dist/errors.d.ts +6 -0
  12. package/dist/{execute-BYI_U5sR.js → execute-CiYvPs5Q.js} +196 -78
  13. package/dist/{execute-DLas4V2n.cjs → execute-DTTfacsi.cjs} +196 -78
  14. package/dist/{exit-BUgJ5qi5.cjs → exit-BOtKWPuT.cjs} +2 -2
  15. package/dist/{exit-CPNk3vbz.js → exit-DMPwCSba.js} +2 -2
  16. package/dist/index.cjs +18 -17
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +18 -18
  19. package/dist/index.workerd.js +252 -79
  20. package/dist/inspection.d.ts +2 -0
  21. package/dist/{native-Bm6L9zOb.cjs → native-0bq5ATZM.cjs} +3 -3
  22. package/dist/{native-DvTH9vFe.js → native-n_718Z-T.js} +3 -3
  23. package/dist/{quickjs-variant-DJwYDSsb.cjs → quickjs-variant-21IO97PF.cjs} +15 -8
  24. package/dist/{quickjs-variant-DlPi9-Sx.js → quickjs-variant-CtuGagsb.js} +10 -9
  25. package/dist/runtime/chat-completion.test.d.ts +1 -0
  26. package/dist/runtime/chat-last-speaker.test.d.ts +1 -0
  27. package/dist/runtime/empty-completion.test.d.ts +1 -0
  28. package/dist/runtime/execution-report.d.ts +3 -0
  29. package/dist/runtime/forced-inspection.d.ts +13 -0
  30. package/dist/runtime/forced-inspection.test.d.ts +1 -0
  31. package/dist/runtime/javascript-api.d.ts +3 -0
  32. package/dist/runtime/think-signal.test.d.ts +1 -0
  33. package/dist/runtime/tool-wrapper.d.ts +4 -2
  34. package/dist/runtime/types.d.ts +8 -0
  35. package/dist/{schema-DBkjo0b7.cjs → schema-CXlnbb3n.cjs} +1 -1
  36. package/dist/{schema-Aer_W7Al.js → schema-GE4ISPCR.js} +1 -1
  37. package/dist/session/messages.d.ts +2 -0
  38. package/dist/session/serialization.d.ts +2 -0
  39. package/dist/session/session.d.ts +4 -0
  40. package/dist/session/speaker.test.d.ts +1 -0
  41. package/dist/{tool--gK91eez.js → tool-DstssiAs.js} +4 -4
  42. package/dist/{tool-Da_F-N-5.cjs → tool-_wK9hQOb.cjs} +4 -4
  43. package/dist/{truncate-BSoH2-nY.js → truncate-BEKbdo3t.js} +1 -1
  44. package/dist/{truncate-nV3Mxr6-.cjs → truncate-uomAMGPs.cjs} +1 -1
  45. package/dist/types.d.ts +6 -0
  46. package/dist/{typings-C1ihexWE.cjs → typings-BA_XxAcY.cjs} +1 -1
  47. package/dist/{typings-D9_aC8aq.js → typings-BEVDvtuk.js} +1 -1
  48. package/dist/vm/types.d.ts +2 -0
  49. package/dist/{vm-8jxt8PTK.js → vm-C7SfutHn.js} +28 -3
  50. package/dist/{vm-XuVPuW3Y.cjs → vm-gmekHc0i.cjs} +28 -2
  51. package/package.json +1 -1
package/DOCS.md CHANGED
@@ -138,10 +138,12 @@ console.log(restored.status) // pending
138
138
 
139
139
  Restoration checks format versions, chronology, identities, call/result pairing, exact result encodings, and memory capacity. Unknown versions are rejected. Conversation/provider fields must be acyclic JSON data; encode binary values explicitly. This is conversation and memory persistence, not a paused JavaScript stack.
140
140
 
141
- After a failed execution, the session retains the active input batch and any completed effects/results. Executing it again continues that turn. Newly appended messages remain queued. Applications should inspect failures before retrying operations that produce external effects.
141
+ After a failed execution, the session retains the active input batch and any completed effects/results. Executing it again continues that turn. Newly appended messages remain queued. A successful retry can therefore leave `session.status === "pending"`; call `execute()` again to process that next batch. Its user messages reset the chat response guard when the next turn starts. Applications should inspect failures before retrying operations that produce external effects.
142
142
 
143
143
  `result.toJSON()` is a compact outcome summary. `result.diagnostics()` exports run diagnostics. Neither replaces `session.toJSON()`.
144
144
 
145
+ Chat requires the assistant to be the last participant who spoke before `exit("listen")` can complete. Successfully delivered text and components count across iterations, executions, and saved sessions. A new user message requires a new response; events, tool results, failed deliveries, and whitespace-only content do not change the last speaker. An empty or whitespace-only model response with `stopReason: "stop"` and no tool calls follows the same listen flow and guard. Silent completion raises a recoverable `MissingChatResponseError` before `onExit`, preserving tool results for the next attempt. Set `options: { requireChatResponse: false }` for intentionally silent handling. Custom task exits and worker mode are unaffected. Older session snapshots without delivery tracking restore with an unknown last speaker and require a response by default.
146
+
145
147
  ## Tools and exits
146
148
 
147
149
  A `Tool` has a name, async handler, optional description, input/output schemas, aliases, metadata, and retry callback. Its original input schema validates and normalizes arguments before the handler runs. Its output schema describes likely returned data for TypeScript and the model: results are never validated, normalized, stripped, or rejected against that schema. Dynamic tools, objects, exits, instructions, and model configuration can be supplied as getters evaluated for each iteration.
@@ -177,7 +179,9 @@ Model-facing schemas describe accepted input shapes, not arbitrary JavaScript be
177
179
 
178
180
  Use `result.is(done)` to narrow the result and its output type. `result.isError()` exposes an execution failure. Without custom exits, workers receive `DefaultExit`. An explicit empty `exits` array supplies no completion exits. Chat adds `ListenExit`; an accepted plain assistant answer can complete the chat turn.
179
181
 
180
- Tools return business data. Generated code sends rich messages through registered `chat.<component>(props)` methods. A `ThinkSignal` requests another reasoning iteration; it is not durable pause/resume.
182
+ Tools return business data. Generated code sends rich messages through registered `chat.<component>(props)` methods. A tool may throw or return `new ThinkSignal(reason, context)` to require inspection of a successful result. JavaScript receives `context` as the tool return value: assignments, subsequent calls, and awaited parallel tools continue normally. At the end of that iteration, inspection takes precedence over an attempted exit or a later recoverable error; exit hooks do not run. The next model turn receives every signaling tool's name, source line, reason, and result together inside `<forced_inspection>`, and is instructed to reuse those results rather than repeat successful calls. Explicit `inspect()` output is also preserved. Tool-result display budgets and `truncate()` policies still apply. Only the business tool handler may produce a `ThinkSignal`; tool hooks must return their documented input/output overrides. Forced inspection uses the output accepted by `onAfterTool`. If that hook rejects or cancels, the rejected result is withheld from guest memory and inspection.
183
+
184
+ Forced inspection delays completion, not business actions: code after a signaling tool still runs, including other tools and chat component deliveries. It is not an authorization barrier. Cancellation and critical failures still stop execution completely. `ThinkSignal` is not durable pause/resume.
181
185
 
182
186
  ## Components
183
187
 
@@ -208,7 +212,7 @@ The model calls `chat.cards(...)`. `render` parses props once and captures an im
208
212
 
209
213
  `response.onDelta` receives previews. Its normal event has `delta`, accumulated `content`, `id`, and `iterationId`. A restart event has `restart: true`, an iteration ID, attempt number, models, and reason. Retract the prior preview before displaying replacement text.
210
214
 
211
- `response.handler` receives the accepted complete text and its message metadata. Components use their own handlers. Without handlers, native text is still retained in session history.
215
+ `response.handler` receives the accepted complete text and its message metadata. Components use their own handlers. Without handlers, native text is still retained in session history, but does not count as delivered speech for `requireChatResponse`.
212
216
 
213
217
  Complete JavaScript calls can execute while streaming continues. If a stream fails after dispatch, the runtime retains completed effects and reports the interruption; it does not replay the program on another model. `options.midStreamFallback` enables restarts before dispatch and requires a preview consumer capable of handling retractions. Normal preview callback failures are ignored; failed retractions stop generation. A final delivery failure can fail execution.
214
218
 
@@ -283,7 +287,7 @@ const getEvidence = new Tool({
283
287
 
284
288
  Inventories and diagnostic previews keep their own small budgets even if a value carries a larger inspection override. Truncation markers are included in the final measured output. Unicode boundaries, cyclic structures, throwing formatters, and accessor properties are handled without changing retained data.
285
289
 
286
- Standalone text is displayed directly. Strings nested in objects or arrays use JSON quoting so source indentation, line endings, and literal escapes remain distinct from the preview's formatting.
290
+ Both `return inspect(value)` and forced ThinkSignal inspection use the native inspector. Standalone text is displayed directly, preserving whitespace, code, and literal tags within its preview budget. Report sections add no XML entity escaping or CDATA. Strings nested in objects or arrays use JSON quoting so source indentation, line endings, and literal escapes remain distinct from the preview's formatting.
287
291
 
288
292
  `onInspect` customizes previews by purpose and identity. Return `undefined` for the default formatter. The hook receives an isolated read-only snapshot; custom text is still bounded. A formatter failure falls back to default inspection.
289
293
 
package/ERRORS.md CHANGED
@@ -58,6 +58,7 @@ A **critical** failure stops the execution without another model call. Critical
58
58
  | `INVALID_COMPONENT_INPUT` | `ComponentInputError` | Recoverable. `componentName`, `issues`, `expectedInput`. No message was sent. |
59
59
  | `INVALID_NATIVE_CALL` | `NativeProtocolError` | Recoverable. Malformed native call, invalid JavaScript envelope, or multiple calls in one response. The rejected batch did not execute. |
60
60
  | `HOST_OPERATION_FAILED` | `HostOperationError` | Recoverable. Unawaited tool or operation after JavaScript completion. Started work is joined; feedback distinguishes its effects. |
61
+ | `MISSING_CHAT_RESPONSE` | `MissingChatResponseError` | Recoverable. Chat tried to complete through `listen` while the assistant was not the last participant to speak; disabled by `options.requireChatResponse: false`. |
61
62
  | `DELIVERY_FAILED` | `DeliveryError` | Recoverable. Text/component handler failed. Earlier acknowledged sends remain delivered; later queued sends are skipped. Inspect `cause`. |
62
63
  | `HOOK_FAILED` | `HookError` | Recoverable. A hook rejected an operation; original `cause` is retained. |
63
64
  | `INVALID_MEMORY_VALUE` | `MemoryValueError` | Recoverable. Unsupported/cyclic memory or inspection value. Failed captures also appear in `iteration.errors` and the memory report. |
@@ -89,10 +90,12 @@ Validation errors include a path/message list and the expected argument shape as
89
90
 
90
91
  [`src/runtime/error-recovery.test.ts`](./src/runtime/error-recovery.test.ts) runs scripted, mocked LLM responses through the real runtime and both VM drivers. Its `.toMatchInlineSnapshot()` assertions show the tool-result feedback actually present in the next model request, including the expected input types. Memory and iteration-budget footers are excluded from the catalogue snapshot so the failure feedback is easy to review. Critical-error tests in [`src/runtime/errors.test.ts`](./src/runtime/errors.test.ts) assert that no additional provider call or business action occurs.
91
92
 
92
- Recovery feedback uses XML-style sections: `<error>` contains the specific error code and readable message, `<stack_trace>` shows source-mapped guest code with line numbers and carets, and `<recovery>` explains how to continue. Tool calls, sent messages, memory changes, and inspection results each have their own section. Content containing XML characters is enclosed in CDATA so code examples, entity literals, and retrieval tags stay unchanged without closing a section. Validation messages preserve line breaks and the expected TypeScript shape.
93
+ Recovery feedback uses XML-style sections: `<error>` contains the specific error code and readable message, `<stack_trace>` shows source-mapped guest code with line numbers and carets, and `<recovery>` explains how to continue. Tool calls, sent messages, memory changes, and inspection results each have their own section. Section tags are readable separators, not a strict XML document. Content is shown verbatim without entity escaping or CDATA, so code examples, entity literals, and retrieval tags stay unchanged. Validation messages preserve line breaks and the expected TypeScript shape.
93
94
 
94
95
  Source traces are bounded previews around the failure sites, including failures near the end of long programs. The complete annotated source remains available as `CodeExecutionError.stacktrace`. Node and QuickJS may report different column precision; the inline snapshots cover each. Compiler errors show the invalid source. Host failures outside guest execution retain their original `cause` for consumers; the model is not shown a misleading guest stack or internal host paths.
95
96
 
96
- A `ThinkSignal` report separates its reason, guest interruption site (when available), and context. It tells the model to review that context and continue from retained values and acknowledged effects. It does not resume the suspended JavaScript stack.
97
+ A tool's thrown or returned `ThinkSignal` counts as successful execution, with its `context` returned to JavaScript. Multiple signals are collected while code continues. At settlement, they force inspection before any exit hook can run, even when later code raises a recoverable error. The `<forced_inspection>` section groups each tool's name, original call-site line (when available), reason, and result. It explicitly describes this as equivalent to `inspect()` and tells the model not to repeat those calls. Actual downstream failures remain in `iteration.errors` and the report's error sections; cancellation and critical errors still stop execution.
98
+
99
+ `think_signal` traces include the responsible tool name, tool-call ID, source line, reason, context, and optional metadata. Results are captured after output hooks and before guest mutations, and displayed through the normal inspection hooks and token budgets.
97
100
 
98
101
  `toJSON()` and `result.diagnostics()` retain codes, messages, criticality, and nested error diagnostics. They are plain data: JSON does not retain the shared symbol brand or Error prototypes, so class guards intentionally do not accept parsed diagnostic JSON. The VM restores typed errors internally before exposing them on iterations.
@@ -1,7 +1,7 @@
1
1
  const require_utils = require("./utils-CTlsXvdj.cjs");
2
- const require_errors = require("./errors-IhnTKUp7.cjs");
3
- const require_schema = require("./schema-DBkjo0b7.cjs");
4
- const require_typings = require("./typings-C1ihexWE.cjs");
2
+ const require_errors = require("./errors-C6ppQUZX.cjs");
3
+ const require_schema = require("./schema-CXlnbb3n.cjs");
4
+ const require_typings = require("./typings-BA_XxAcY.cjs");
5
5
  let _bpinternal_zui = require("@bpinternal/zui");
6
6
  //#region ../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/_baseCreate.js
7
7
  /** Built-in value references. */
@@ -1,7 +1,7 @@
1
1
  import { A as keys, B as baseAssignValue, C as getAllKeysIn, D as Stack, E as getSymbols, F as isBuffer, G as isObjectLike, H as isObject, K as Symbol, L as isPrototype, N as nodeUtil, O as getPrototype, P as baseUnary, T as getSymbolsIn, U as isArray, Y as __exportAll, b as Uint8Array, k as keysIn, q as root, w as getAllKeys, x as _getTag_default, z as assignValue } from "./utils-BOFzb8Ok.js";
2
- import { N as ReservedIdentifierError, d as ComponentInputError, y as InvalidComponentError } from "./errors-H4JJFH3S.js";
3
- import { n as parseSchemaSync, r as schemaInput } from "./schema-Aer_W7Al.js";
4
- import { n as schemaToTypeScript } from "./typings-D9_aC8aq.js";
2
+ import { P as ReservedIdentifierError, d as ComponentInputError, y as InvalidComponentError } from "./errors-Crk5P8jo.js";
3
+ import { n as parseSchemaSync, r as schemaInput } from "./schema-GE4ISPCR.js";
4
+ import { n as schemaToTypeScript } from "./typings-BEVDvtuk.js";
5
5
  import { z } from "@bpinternal/zui";
6
6
  //#region ../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/_baseCreate.js
7
7
  /** Built-in value references. */
package/dist/context.d.ts CHANGED
@@ -93,7 +93,7 @@ export declare namespace IterationStatuses {
93
93
  /** The value returned by the executed code (or the context provided by a ThinkSignal). */
94
94
  variables: unknown;
95
95
  metadata?: Record<string, unknown>;
96
- /** A tool paused execution; it did not finish or return normally. */
96
+ /** A legacy VM control-flow signal paused execution. Tool ThinkSignals now use forced inspection. */
97
97
  interrupted?: boolean;
98
98
  };
99
99
  };
@@ -1,11 +1,11 @@
1
1
  import { a as getTokenizer } from "./utils-BOFzb8Ok.js";
2
- import { C as InvalidMessageError, F as TokenOverflowError, H as isLLMzError, N as ReservedIdentifierError, O as LoopExceededError, P as SessionStateError, T as InvalidSessionError, _ as InternalError, b as InvalidConfigurationError, i as describeError, l as CognitiveError, p as ExecutionAbortedError, t as Signals, u as CompactionError, x as InvalidEventError } from "./errors-H4JJFH3S.js";
3
- import { i as createComponentRegistry, n as assertValidComponent } from "./component-C27qu6Zv.js";
4
- import { t as Tool } from "./tool--gK91eez.js";
5
- import { t as Exit } from "./exit-CPNk3vbz.js";
6
- import { t as DEFAULT_TOOL_RESULT_MAX_TOKENS } from "./truncate-BSoH2-nY.js";
7
- import { a as cloneMemoryValue, c as freezeMemoryValue, f as createInspector, i as renderMemory, l as RESERVED_RUNTIME_NAMES, o as decodeMemoryValue, r as Memory, s as encodeMemoryValue } from "./quickjs-variant-DlPi9-Sx.js";
8
- import { o as resolveResponse, t as getNativeSystemMessage } from "./native-DvTH9vFe.js";
2
+ import { C as InvalidMessageError, F as SessionStateError, I as TokenOverflowError, O as LoopExceededError, P as ReservedIdentifierError, T as InvalidSessionError, U as isLLMzError, _ as InternalError, b as InvalidConfigurationError, i as describeError, l as CognitiveError, p as ExecutionAbortedError, t as Signals, u as CompactionError, x as InvalidEventError } from "./errors-Crk5P8jo.js";
3
+ import { i as createComponentRegistry, n as assertValidComponent } from "./component-DncqP4fB.js";
4
+ import { t as Tool } from "./tool-DstssiAs.js";
5
+ import { t as Exit } from "./exit-DMPwCSba.js";
6
+ import { t as DEFAULT_TOOL_RESULT_MAX_TOKENS } from "./truncate-BEKbdo3t.js";
7
+ import { a as cloneMemoryValue, c as freezeMemoryValue, f as createInspector, i as renderMemory, l as RESERVED_RUNTIME_NAMES, o as decodeMemoryValue, r as Memory, s as encodeMemoryValue } from "./quickjs-variant-CtuGagsb.js";
8
+ import { o as resolveResponse, t as getNativeSystemMessage } from "./native-n_718Z-T.js";
9
9
  import { z } from "@bpinternal/zui";
10
10
  import { ulid } from "ulid";
11
11
  import { Cognitive } from "@botpress/cognitive";
@@ -325,6 +325,14 @@ function normalizeMessage(message) {
325
325
  validateInputMessage(normalized);
326
326
  return normalized;
327
327
  }
328
+ /** Only actual participant content counts as speech, including image/audio-only messages. */
329
+ function inputSpeaker(message) {
330
+ var _message$content;
331
+ return (Array.isArray(message.content) ? message.content.some((part) => {
332
+ var _part$text;
333
+ return part.type !== "text" || Boolean((_part$text = part.text) === null || _part$text === void 0 ? void 0 : _part$text.trim());
334
+ }) : Boolean((_message$content = message.content) === null || _message$content === void 0 ? void 0 : _message$content.trim())) && (message.role === "user" || message.role === "assistant") ? message.role : null;
335
+ }
328
336
  function createAssistantMessage(response) {
329
337
  var _response$toolCalls;
330
338
  if (response.assistantMessage) return asSessionMessage(response.assistantMessage);
@@ -493,6 +501,7 @@ function validateRestoredHistory(state) {
493
501
  if (!Array.isArray(state.groups) || !Array.isArray(state.pendingInputs)) throw new InvalidSessionError("Session history and queued inputs must be arrays.");
494
502
  if (!Number.isSafeInteger(state.turn) || state.turn < 0 || !Number.isSafeInteger(state.iteration) || state.iteration < 0) throw new InvalidSessionError("Session turn and iteration counters must be non-negative safe integers");
495
503
  if (typeof state.activeTurn !== "boolean" || state.activeTurn && (!state.turn || !state.turnId)) throw new InvalidSessionError("Session processing state must identify an active turn.");
504
+ if (state.lastSpeaker !== void 0 && state.lastSpeaker !== null && !["user", "assistant"].includes(state.lastSpeaker)) throw new InvalidSessionError("Session last speaker must be user, assistant, or null.");
496
505
  const groupIds = /* @__PURE__ */ new Set();
497
506
  const callIds = /* @__PURE__ */ new Set();
498
507
  let previousIteration = 0;
@@ -593,6 +602,7 @@ var Session = class Session {
593
602
  #pendingInputs = [];
594
603
  #activeTurn = false;
595
604
  #locked = false;
605
+ #lastSpeaker = null;
596
606
  constructor(options = {}) {
597
607
  this.id = `session_${ulid()}`;
598
608
  this.memory = new Memory({
@@ -617,6 +627,15 @@ var Session = class Session {
617
627
  get transcript() {
618
628
  return structuredClone([...this.#allGroups().flatMap((group) => group.source ? [group.source] : group.messages), ...this.#pendingInputs.map((input) => input.source ?? input.message)]);
619
629
  }
630
+ /** Last participant who spoke in the active history. Events and queued inputs do not count. */
631
+ get lastSpeaker() {
632
+ return this.#lastSpeaker;
633
+ }
634
+ /** Record acknowledged text or component delivery, never generation alone. */
635
+ recordAssistantDelivery(iterationId) {
636
+ this.#getActiveIteration(iterationId);
637
+ this.#lastSpeaker = "assistant";
638
+ }
620
639
  get pendingMessages() {
621
640
  return structuredClone(this.#pendingInputs.map((input) => input.message));
622
641
  }
@@ -705,12 +724,15 @@ var Session = class Session {
705
724
  this.#turn++;
706
725
  this.#turnId = `turn_${ulid()}`;
707
726
  this.#activeTurn = true;
708
- for (const input of this.#pendingInputs) this.#groups.push({
709
- id: input.id,
710
- turn: this.#turn,
711
- messages: [input.message],
712
- ...input.source ? { source: input.source } : {}
713
- });
727
+ for (const input of this.#pendingInputs) {
728
+ if (!input.source) this.#lastSpeaker = inputSpeaker(input.message) ?? this.#lastSpeaker;
729
+ this.#groups.push({
730
+ id: input.id,
731
+ turn: this.#turn,
732
+ messages: [input.message],
733
+ ...input.source ? { source: input.source } : {}
734
+ });
735
+ }
714
736
  this.#pendingInputs = [];
715
737
  }
716
738
  /** Mark the active input batch complete without consuming newly queued input. */
@@ -1033,6 +1055,7 @@ var Session = class Session {
1033
1055
  groups: this.#groups.map(serializeGroup),
1034
1056
  memory: this.memory.serialize(),
1035
1057
  pendingInputs: structuredClone(this.#pendingInputs),
1058
+ lastSpeaker: this.#lastSpeaker,
1036
1059
  activeTurn: this.#activeTurn,
1037
1060
  latestResultId: this.#latestResultId,
1038
1061
  compaction
@@ -1051,6 +1074,7 @@ var Session = class Session {
1051
1074
  session.#groups = state.groups.map(restoreGroup);
1052
1075
  session.#pendingInputs = structuredClone(state.pendingInputs);
1053
1076
  session.#activeTurn = state.activeTurn;
1077
+ session.#lastSpeaker = state.lastSpeaker ?? null;
1054
1078
  session.#latestResultId = state.latestResultId;
1055
1079
  Object.defineProperty(session, "memory", {
1056
1080
  value: Memory.restore(state.memory, () => session.#resultBytes()),
@@ -1,11 +1,11 @@
1
1
  const require_utils = require("./utils-CTlsXvdj.cjs");
2
- const require_errors = require("./errors-IhnTKUp7.cjs");
3
- const require_component = require("./component-BpMVZYOa.cjs");
4
- const require_tool = require("./tool-Da_F-N-5.cjs");
5
- const require_exit = require("./exit-BUgJ5qi5.cjs");
6
- const require_truncate = require("./truncate-nV3Mxr6-.cjs");
7
- const require_quickjs_variant = require("./quickjs-variant-DJwYDSsb.cjs");
8
- const require_native = require("./native-Bm6L9zOb.cjs");
2
+ const require_errors = require("./errors-C6ppQUZX.cjs");
3
+ const require_component = require("./component-CMFoCHyu.cjs");
4
+ const require_tool = require("./tool-_wK9hQOb.cjs");
5
+ const require_exit = require("./exit-BOtKWPuT.cjs");
6
+ const require_truncate = require("./truncate-uomAMGPs.cjs");
7
+ const require_quickjs_variant = require("./quickjs-variant-21IO97PF.cjs");
8
+ const require_native = require("./native-0bq5ATZM.cjs");
9
9
  let _bpinternal_zui = require("@bpinternal/zui");
10
10
  let ulid = require("ulid");
11
11
  let _botpress_cognitive = require("@botpress/cognitive");
@@ -325,6 +325,14 @@ function normalizeMessage(message) {
325
325
  validateInputMessage(normalized);
326
326
  return normalized;
327
327
  }
328
+ /** Only actual participant content counts as speech, including image/audio-only messages. */
329
+ function inputSpeaker(message) {
330
+ var _message$content;
331
+ return (Array.isArray(message.content) ? message.content.some((part) => {
332
+ var _part$text;
333
+ return part.type !== "text" || Boolean((_part$text = part.text) === null || _part$text === void 0 ? void 0 : _part$text.trim());
334
+ }) : Boolean((_message$content = message.content) === null || _message$content === void 0 ? void 0 : _message$content.trim())) && (message.role === "user" || message.role === "assistant") ? message.role : null;
335
+ }
328
336
  function createAssistantMessage(response) {
329
337
  var _response$toolCalls;
330
338
  if (response.assistantMessage) return asSessionMessage(response.assistantMessage);
@@ -493,6 +501,7 @@ function validateRestoredHistory(state) {
493
501
  if (!Array.isArray(state.groups) || !Array.isArray(state.pendingInputs)) throw new require_errors.InvalidSessionError("Session history and queued inputs must be arrays.");
494
502
  if (!Number.isSafeInteger(state.turn) || state.turn < 0 || !Number.isSafeInteger(state.iteration) || state.iteration < 0) throw new require_errors.InvalidSessionError("Session turn and iteration counters must be non-negative safe integers");
495
503
  if (typeof state.activeTurn !== "boolean" || state.activeTurn && (!state.turn || !state.turnId)) throw new require_errors.InvalidSessionError("Session processing state must identify an active turn.");
504
+ if (state.lastSpeaker !== void 0 && state.lastSpeaker !== null && !["user", "assistant"].includes(state.lastSpeaker)) throw new require_errors.InvalidSessionError("Session last speaker must be user, assistant, or null.");
496
505
  const groupIds = /* @__PURE__ */ new Set();
497
506
  const callIds = /* @__PURE__ */ new Set();
498
507
  let previousIteration = 0;
@@ -593,6 +602,7 @@ var Session = class Session {
593
602
  #pendingInputs = [];
594
603
  #activeTurn = false;
595
604
  #locked = false;
605
+ #lastSpeaker = null;
596
606
  constructor(options = {}) {
597
607
  this.id = `session_${(0, ulid.ulid)()}`;
598
608
  this.memory = new require_quickjs_variant.Memory({
@@ -617,6 +627,15 @@ var Session = class Session {
617
627
  get transcript() {
618
628
  return structuredClone([...this.#allGroups().flatMap((group) => group.source ? [group.source] : group.messages), ...this.#pendingInputs.map((input) => input.source ?? input.message)]);
619
629
  }
630
+ /** Last participant who spoke in the active history. Events and queued inputs do not count. */
631
+ get lastSpeaker() {
632
+ return this.#lastSpeaker;
633
+ }
634
+ /** Record acknowledged text or component delivery, never generation alone. */
635
+ recordAssistantDelivery(iterationId) {
636
+ this.#getActiveIteration(iterationId);
637
+ this.#lastSpeaker = "assistant";
638
+ }
620
639
  get pendingMessages() {
621
640
  return structuredClone(this.#pendingInputs.map((input) => input.message));
622
641
  }
@@ -705,12 +724,15 @@ var Session = class Session {
705
724
  this.#turn++;
706
725
  this.#turnId = `turn_${(0, ulid.ulid)()}`;
707
726
  this.#activeTurn = true;
708
- for (const input of this.#pendingInputs) this.#groups.push({
709
- id: input.id,
710
- turn: this.#turn,
711
- messages: [input.message],
712
- ...input.source ? { source: input.source } : {}
713
- });
727
+ for (const input of this.#pendingInputs) {
728
+ if (!input.source) this.#lastSpeaker = inputSpeaker(input.message) ?? this.#lastSpeaker;
729
+ this.#groups.push({
730
+ id: input.id,
731
+ turn: this.#turn,
732
+ messages: [input.message],
733
+ ...input.source ? { source: input.source } : {}
734
+ });
735
+ }
714
736
  this.#pendingInputs = [];
715
737
  }
716
738
  /** Mark the active input batch complete without consuming newly queued input. */
@@ -1033,6 +1055,7 @@ var Session = class Session {
1033
1055
  groups: this.#groups.map(serializeGroup),
1034
1056
  memory: this.memory.serialize(),
1035
1057
  pendingInputs: structuredClone(this.#pendingInputs),
1058
+ lastSpeaker: this.#lastSpeaker,
1036
1059
  activeTurn: this.#activeTurn,
1037
1060
  latestResultId: this.#latestResultId,
1038
1061
  compaction
@@ -1051,6 +1074,7 @@ var Session = class Session {
1051
1074
  session.#groups = state.groups.map(restoreGroup);
1052
1075
  session.#pendingInputs = structuredClone(state.pendingInputs);
1053
1076
  session.#activeTurn = state.activeTurn;
1077
+ session.#lastSpeaker = state.lastSpeaker ?? null;
1054
1078
  session.#latestResultId = state.latestResultId;
1055
1079
  Object.defineProperty(session, "memory", {
1056
1080
  value: require_quickjs_variant.Memory.restore(state.memory, () => session.#resultBytes()),
@@ -211,6 +211,12 @@ export declare class HostOperationError extends LLMzError<'HOST_OPERATION_FAILED
211
211
  readonly code = "HOST_OPERATION_FAILED";
212
212
  constructor(message: string, options?: ErrorOptions);
213
213
  }
214
+ /** Chat tried to wait for the user without delivering any message. */
215
+ export declare class MissingChatResponseError extends LLMzError<'MISSING_CHAT_RESPONSE'> {
216
+ static readonly is: (value: unknown) => value is MissingChatResponseError;
217
+ readonly code = "MISSING_CHAT_RESPONSE";
218
+ constructor(message: string, options?: ErrorOptions);
219
+ }
214
220
  /** Message delivery failed; external delivery may have partially completed. */
215
221
  export declare class DeliveryError extends LLMzError<'DELIVERY_FAILED'> {
216
222
  static readonly is: (value: unknown) => value is DeliveryError;
@@ -268,6 +274,7 @@ export declare const errorClasses: {
268
274
  NativeProtocolError: typeof NativeProtocolError;
269
275
  HostOperationError: typeof HostOperationError;
270
276
  DeliveryError: typeof DeliveryError;
277
+ MissingChatResponseError: typeof MissingChatResponseError;
271
278
  HookError: typeof HookError;
272
279
  ExecutionAbortedError: typeof ExecutionAbortedError;
273
280
  InternalError: typeof InternalError;
@@ -306,6 +306,14 @@ var HostOperationError = class extends LLMzError {
306
306
  super(message, false, options);
307
307
  }
308
308
  };
309
+ /** Chat tried to wait for the user without delivering any message. */
310
+ var MissingChatResponseError = class extends LLMzError {
311
+ static is = (value) => isLLMzError(value, "MISSING_CHAT_RESPONSE");
312
+ code = "MISSING_CHAT_RESPONSE";
313
+ constructor(message, options) {
314
+ super(message, false, options);
315
+ }
316
+ };
309
317
  /** Message delivery failed; external delivery may have partially completed. */
310
318
  var DeliveryError = class extends LLMzError {
311
319
  static is = (value) => isLLMzError(value, "DELIVERY_FAILED");
@@ -371,6 +379,7 @@ const errorClasses$1 = {
371
379
  NativeProtocolError,
372
380
  HostOperationError,
373
381
  DeliveryError,
382
+ MissingChatResponseError,
374
383
  HookError,
375
384
  ExecutionAbortedError,
376
385
  InternalError
@@ -407,6 +416,7 @@ const ERROR_CODES = /* @__PURE__ */ new Set([
407
416
  "INVALID_NATIVE_CALL",
408
417
  "HOST_OPERATION_FAILED",
409
418
  "DELIVERY_FAILED",
419
+ "MISSING_CHAT_RESPONSE",
410
420
  "HOOK_FAILED",
411
421
  "EXECUTION_ABORTED",
412
422
  "INTERNAL_ERROR"
@@ -595,6 +605,12 @@ var VMLoopSignal = class extends VMSignal {
595
605
  this.message = Signals.serializeError(this);
596
606
  }
597
607
  };
608
+ /**
609
+ * A successful tool result that requires model inspection before completion.
610
+ * Tools may throw or return this signal. Its context becomes the JavaScript
611
+ * return value; calls and assignments continue, and inspection overrides any
612
+ * attempted exit at iteration settlement. Cancellation and critical errors still stop execution.
613
+ */
598
614
  var ThinkSignal = class extends VMLoopSignal {
599
615
  reason;
600
616
  context;
@@ -767,6 +783,12 @@ Object.defineProperty(exports, "MemoryValueError", {
767
783
  return MemoryValueError;
768
784
  }
769
785
  });
786
+ Object.defineProperty(exports, "MissingChatResponseError", {
787
+ enumerable: true,
788
+ get: function() {
789
+ return MissingChatResponseError;
790
+ }
791
+ });
770
792
  Object.defineProperty(exports, "NativeProtocolError", {
771
793
  enumerable: true,
772
794
  get: function() {
@@ -306,6 +306,14 @@ var HostOperationError = class extends LLMzError {
306
306
  super(message, false, options);
307
307
  }
308
308
  };
309
+ /** Chat tried to wait for the user without delivering any message. */
310
+ var MissingChatResponseError = class extends LLMzError {
311
+ static is = (value) => isLLMzError(value, "MISSING_CHAT_RESPONSE");
312
+ code = "MISSING_CHAT_RESPONSE";
313
+ constructor(message, options) {
314
+ super(message, false, options);
315
+ }
316
+ };
309
317
  /** Message delivery failed; external delivery may have partially completed. */
310
318
  var DeliveryError = class extends LLMzError {
311
319
  static is = (value) => isLLMzError(value, "DELIVERY_FAILED");
@@ -371,6 +379,7 @@ const errorClasses$1 = {
371
379
  NativeProtocolError,
372
380
  HostOperationError,
373
381
  DeliveryError,
382
+ MissingChatResponseError,
374
383
  HookError,
375
384
  ExecutionAbortedError,
376
385
  InternalError
@@ -407,6 +416,7 @@ const ERROR_CODES = /* @__PURE__ */ new Set([
407
416
  "INVALID_NATIVE_CALL",
408
417
  "HOST_OPERATION_FAILED",
409
418
  "DELIVERY_FAILED",
419
+ "MISSING_CHAT_RESPONSE",
410
420
  "HOOK_FAILED",
411
421
  "EXECUTION_ABORTED",
412
422
  "INTERNAL_ERROR"
@@ -595,6 +605,12 @@ var VMLoopSignal = class extends VMSignal {
595
605
  this.message = Signals.serializeError(this);
596
606
  }
597
607
  };
608
+ /**
609
+ * A successful tool result that requires model inspection before completion.
610
+ * Tools may throw or return this signal. Its context becomes the JavaScript
611
+ * return value; calls and assignments continue, and inspection overrides any
612
+ * attempted exit at iteration settlement. Cancellation and critical errors still stop execution.
613
+ */
598
614
  var ThinkSignal = class extends VMLoopSignal {
599
615
  reason;
600
616
  context;
@@ -617,4 +633,4 @@ registerErrorClass("VMSignal", VMSignal);
617
633
  registerErrorClass("VMLoopSignal", VMLoopSignal);
618
634
  registerErrorClass("ThinkSignal", ThinkSignal);
619
635
  //#endregion
620
- export { MemoryValueError as A, UnknownToolError as B, InvalidMessageError as C, LLMzError as D, InvalidToolError as E, TokenOverflowError as F, isLLMzError as H, ToolExecutionError as I, ToolInputError as L, ObjectPropertyError as M, ReservedIdentifierError as N, LoopExceededError as O, SessionStateError as P, UnknownComponentError as R, InvalidExitError as S, InvalidSessionError as T, isCriticalError as V, InternalError as _, cleanStackTrace as a, InvalidConfigurationError as b, CodeFormattingError as c, ComponentInputError as d, DeliveryError as f, HostOperationError as g, HookError as h, describeError as i, NativeProtocolError as j, MemoryCapacityError as k, CognitiveError as l, ExitInputError as m, ThinkSignal as n, AssignmentError as o, ExecutionAbortedError as p, VMSignal as r, CodeExecutionError as s, Signals as t, CompactionError as u, InvalidCodeError as v, InvalidObjectError as w, InvalidEventError as x, InvalidComponentError as y, UnknownExitError as z };
636
+ export { MemoryValueError as A, UnknownExitError as B, InvalidMessageError as C, LLMzError as D, InvalidToolError as E, SessionStateError as F, isCriticalError as H, TokenOverflowError as I, ToolExecutionError as L, NativeProtocolError as M, ObjectPropertyError as N, LoopExceededError as O, ReservedIdentifierError as P, ToolInputError as R, InvalidExitError as S, InvalidSessionError as T, isLLMzError as U, UnknownToolError as V, InternalError as _, cleanStackTrace as a, InvalidConfigurationError as b, CodeFormattingError as c, ComponentInputError as d, DeliveryError as f, HostOperationError as g, HookError as h, describeError as i, MissingChatResponseError as j, MemoryCapacityError as k, CognitiveError as l, ExitInputError as m, ThinkSignal as n, AssignmentError as o, ExecutionAbortedError as p, VMSignal as r, CodeExecutionError as s, Signals as t, CompactionError as u, InvalidCodeError as v, InvalidObjectError as w, InvalidEventError as x, InvalidComponentError as y, UnknownComponentError as z };
package/dist/errors.d.ts CHANGED
@@ -39,6 +39,12 @@ export declare class VMLoopSignal extends VMSignal {
39
39
  static readonly is: (value: unknown) => value is VMLoopSignal;
40
40
  constructor(message: string);
41
41
  }
42
+ /**
43
+ * A successful tool result that requires model inspection before completion.
44
+ * Tools may throw or return this signal. Its context becomes the JavaScript
45
+ * return value; calls and assignments continue, and inspection overrides any
46
+ * attempted exit at iteration settlement. Cancellation and critical errors still stop execution.
47
+ */
42
48
  export declare class ThinkSignal extends VMLoopSignal {
43
49
  reason: string;
44
50
  context?: any;