@mlx-node/lm 0.0.6 → 0.0.8
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 +71 -46
- package/dist/chat-session.d.ts +700 -0
- package/dist/chat-session.d.ts.map +1 -0
- package/dist/chat-session.js +1467 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -6
- package/dist/interfaces.d.ts +2 -7
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/models/lfm2-configs.d.ts +6 -0
- package/dist/models/lfm2-configs.d.ts.map +1 -0
- package/dist/models/lfm2-configs.js +107 -0
- package/dist/models/model-loader.d.ts +194 -4
- package/dist/models/model-loader.d.ts.map +1 -1
- package/dist/models/model-loader.js +253 -21
- package/dist/models/paged-config-override.d.ts +60 -0
- package/dist/models/paged-config-override.d.ts.map +1 -0
- package/dist/models/paged-config-override.js +254 -0
- package/dist/models/qwen3_5-configs.d.ts.map +1 -1
- package/dist/models/qwen3_5-configs.js +5 -0
- package/dist/stream.d.ts +234 -29
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +294 -52
- package/dist/tools/index.d.ts +38 -11
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +38 -11
- package/dist/tools/types.d.ts +5 -25
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +5 -30
- package/package.json +3 -3
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import type { ChatConfig, ChatMessage, ChatResult, ToolDefinition } from '@mlx-node/core';
|
|
2
|
+
import type { ChatStreamEvent } from './stream.js';
|
|
3
|
+
/**
|
|
4
|
+
* Stable, provider-neutral error raised before native inference when a
|
|
5
|
+
* rendered prompt cannot fit in the model's physically available hot KV
|
|
6
|
+
* window. The marker is intentionally the canonical string recognized by
|
|
7
|
+
* pi's overflow recovery, so managed agent sessions compact and retry while
|
|
8
|
+
* stateless HTTP callers receive a clean request error instead of a native
|
|
9
|
+
* `BlockAllocator exhausted` failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ContextCapacityError extends Error {
|
|
12
|
+
readonly promptTokens: number;
|
|
13
|
+
readonly effectiveWindowTokens: number;
|
|
14
|
+
readonly code = "context_length_exceeded";
|
|
15
|
+
constructor(promptTokens: number, effectiveWindowTokens: number);
|
|
16
|
+
}
|
|
17
|
+
/** Recognize both the typed JS preflight and the native hard backstop. */
|
|
18
|
+
export declare function isContextCapacityError(error: unknown): boolean;
|
|
19
|
+
/** Physical and trained context limits captured by a native model at load. */
|
|
20
|
+
export interface SessionContextLimits {
|
|
21
|
+
trainedWindowTokens: number;
|
|
22
|
+
effectiveWindowTokens: number;
|
|
23
|
+
pagedBlockCapacity: number;
|
|
24
|
+
pagedBlockSize: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Structural interface matched by every generative model wrapper
|
|
28
|
+
* (`Qwen35Model`, `Qwen35MoeModel`, `Lfm2Model`, `Gemma4Model`,
|
|
29
|
+
* `Qwen3Model`, and the Qianfan-OCR VLM wrapper). `ChatSession<M>` is
|
|
30
|
+
* generic over `M extends SessionCapableModel` so each session
|
|
31
|
+
* instance statically binds to a specific model's concrete type
|
|
32
|
+
* (handy for IDE autocomplete) while the implementation remains
|
|
33
|
+
* fully structural.
|
|
34
|
+
*/
|
|
35
|
+
export interface SessionCapableModel {
|
|
36
|
+
/**
|
|
37
|
+
* Optional non-generating chat-template tokenizer. Exposed by
|
|
38
|
+
* wrappers that can count prompt tokens without running inference
|
|
39
|
+
* (used by Anthropic `/v1/messages/count_tokens`).
|
|
40
|
+
*/
|
|
41
|
+
applyChatTemplate?(messages: ChatMessage[], addGenerationPrompt?: boolean | null, tools?: ToolDefinition[] | null, enableThinking?: boolean | null): Promise<Uint32Array> | Uint32Array;
|
|
42
|
+
/**
|
|
43
|
+
* Optional model-native planner for prompt formats whose media placeholders
|
|
44
|
+
* expand after chat-template rendering. It returns the exact token length
|
|
45
|
+
* that inference will prefill, without mutating model/session state.
|
|
46
|
+
*
|
|
47
|
+
* Qwen3.5 dense/MoE implement this with their loaded image processor. Models
|
|
48
|
+
* without post-template expansion omit it and retain raw template counting.
|
|
49
|
+
*/
|
|
50
|
+
expandedPromptTokenCount?(promptTokens: Uint32Array, messages: ChatMessage[]): Promise<number> | number;
|
|
51
|
+
/**
|
|
52
|
+
* Optional synchronous load-time snapshot of the model's usable context.
|
|
53
|
+
* Qwen3.5 dense/MoE expose this when adaptive paged-cache sizing is active.
|
|
54
|
+
*/
|
|
55
|
+
contextLimits?(): SessionContextLimits;
|
|
56
|
+
/**
|
|
57
|
+
* Whether this loaded model instance has a complete image-input path.
|
|
58
|
+
*
|
|
59
|
+
* Optional so text-only and older wrappers continue to satisfy the
|
|
60
|
+
* structural contract. Supporting native wrappers snapshot this value after
|
|
61
|
+
* load, once the vision encoder/processor and any required cache backend are
|
|
62
|
+
* known to be available.
|
|
63
|
+
*/
|
|
64
|
+
supportsImages?(): boolean;
|
|
65
|
+
chatSessionStart(messages: ChatMessage[], config?: ChatConfig | null): Promise<ChatResult>;
|
|
66
|
+
chatSessionContinue(userMessage: string, images: Uint8Array[] | null, audio: Uint8Array[] | null, config?: ChatConfig | null): Promise<ChatResult>;
|
|
67
|
+
chatSessionContinueTool(toolCallId: string, content: string, config?: ChatConfig | null, isError?: boolean | null): Promise<ChatResult>;
|
|
68
|
+
/**
|
|
69
|
+
* The optional `signal` parameter on every streaming entry point is
|
|
70
|
+
* plumbed into the `_runChatStream` fast-abort path in the wrapper
|
|
71
|
+
* implementations. Callers that need client-disconnect-aware
|
|
72
|
+
* cancellation (e.g. HTTP endpoints flipping an AbortController on
|
|
73
|
+
* socket close) can attach one here and the native decode unwinds
|
|
74
|
+
* at the next safepoint via `ChatStreamHandle.cancel()`. Non-signal
|
|
75
|
+
* callers (the common direct-use path) just omit it.
|
|
76
|
+
*/
|
|
77
|
+
chatStreamSessionStart(messages: ChatMessage[], config?: ChatConfig | null, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
|
|
78
|
+
chatStreamSessionContinue(userMessage: string, images: Uint8Array[] | null, audio: Uint8Array[] | null, config?: ChatConfig | null, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
|
|
79
|
+
chatStreamSessionContinueTool(toolCallId: string, content: string, config?: ChatConfig | null, signal?: AbortSignal, isError?: boolean | null): AsyncGenerator<ChatStreamEvent>;
|
|
80
|
+
resetCaches(): void;
|
|
81
|
+
/**
|
|
82
|
+
* Whether the underlying native model has the block-paged KV cache
|
|
83
|
+
* adapter (`PagedKVCacheAdapter` + `BlockAllocator` + `LayerKVPool`)
|
|
84
|
+
* active.
|
|
85
|
+
*
|
|
86
|
+
* `true` iff the adapter was successfully constructed at load time
|
|
87
|
+
* (driven by the per-model `use_block_paged_cache` config flag, which
|
|
88
|
+
* defaults to ON for Qwen3 + LFM2 after parity verification and OFF
|
|
89
|
+
* for Gemma4 + Qwen3.5 + Qwen3.5 MoE pending parity validation; also
|
|
90
|
+
* always `false` on Qwen3.5 VLM checkpoints where
|
|
91
|
+
* `set_vision_encoder` rejects when the adapter is populated).
|
|
92
|
+
*
|
|
93
|
+
* When `true`, the native cache reuses SYS blocks across requests via
|
|
94
|
+
* content-addressing in the `BlockAllocator`'s prefix-hash table —
|
|
95
|
+
* the JS-side warm slot in
|
|
96
|
+
* `SessionRegistry.getOrCreateWarmAny(requestedSystem)` becomes
|
|
97
|
+
* redundant for stateless `/v1/messages` traffic. The server
|
|
98
|
+
* endpoint reads this getter to decide whether to allocate a fresh
|
|
99
|
+
* `ChatSession` per request (paged-active) or to lease the warm slot
|
|
100
|
+
* (non-paged); see `packages/server/src/endpoints/messages.ts`.
|
|
101
|
+
*
|
|
102
|
+
* Optional on the structural interface so models that pre-date the
|
|
103
|
+
* NAPI getter (notably `QianfanOCRModel` from `@mlx-node/vlm`, which
|
|
104
|
+
* has no paged-adapter wiring) still satisfy the type contract — a
|
|
105
|
+
* missing getter is treated as `false` (not paged) by callers.
|
|
106
|
+
* Surfaced as a synchronous method on every native wrapper that DOES
|
|
107
|
+
* support paged so the routing decision in the server doesn't need a
|
|
108
|
+
* model-thread roundtrip per request — the value is captured at load
|
|
109
|
+
* time and never changes for a given model instance.
|
|
110
|
+
*/
|
|
111
|
+
hasBlockPagedCache?(): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* MTP: whether the underlying native model can run speculative
|
|
114
|
+
* decoding. Surfaced by `Qwen3_5Model` / `Qwen3_5MoeModel` (an MTP
|
|
115
|
+
* head shipped in the checkpoint and loaded by persistence) and by
|
|
116
|
+
* `Gemma4Model` (an external draft model — DSpark or Google gemma-4
|
|
117
|
+
* assistant, auto-detected from the draft's config.json — attached
|
|
118
|
+
* via `loadModel` / `loadSession` `draftModelPath`; NOT in-checkpoint
|
|
119
|
+
* MTP heads); all other native wrappers omit the method, in
|
|
120
|
+
* which case callers treat a missing getter as `false` (no MTP).
|
|
121
|
+
*
|
|
122
|
+
* When `true`, {@link ChatSession#mergeConfig} auto-defaults the
|
|
123
|
+
* per-request `enableMtp` flag to `true` — the speculative-decode
|
|
124
|
+
* path takes over unless the caller explicitly opts out by passing
|
|
125
|
+
* `enableMtp: false` in their `ChatConfig` overlay. When `false`
|
|
126
|
+
* (or the method is missing), `enableMtp` is left untouched.
|
|
127
|
+
*
|
|
128
|
+
* Synchronous on every supporting wrapper so the auto-default check
|
|
129
|
+
* doesn't need a model-thread roundtrip per call — the value is
|
|
130
|
+
* captured at load time and never changes for a given model
|
|
131
|
+
* instance.
|
|
132
|
+
*
|
|
133
|
+
* ## Companion `ChatConfig` knobs (only meaningful when `enableMtp` is on)
|
|
134
|
+
*
|
|
135
|
+
* Two related `ChatConfig` fields tune the speculative-decode loop.
|
|
136
|
+
* They are forwarded verbatim to the native side via
|
|
137
|
+
* {@link SendOptions.config} (per-call overlay) or
|
|
138
|
+
* {@link ChatSessionOptions.defaultConfig} (session default), and
|
|
139
|
+
* `mergeConfig` shallow-merges per-call over per-session so an
|
|
140
|
+
* explicit per-send value always wins over the session default for
|
|
141
|
+
* the same field.
|
|
142
|
+
*
|
|
143
|
+
* - **`mtpDepth`** — pins the MTP draft depth per speculative cycle.
|
|
144
|
+
* On Qwen3.5 native MTP heads it is clamped to `[1, 5]` by the
|
|
145
|
+
* verify FFI contract, and when unset native code currently pins
|
|
146
|
+
* depth 1. Setting `mtpDepth` explicitly pins that value unless
|
|
147
|
+
* the caller also passes `mtpAdaptiveDepth: true` to opt into
|
|
148
|
+
* adaptive depth with the supplied maximum/seed. Gemma4 external
|
|
149
|
+
* drafts resolve the field per draft variant instead — see the
|
|
150
|
+
* Gemma4 section below.
|
|
151
|
+
* - **`mtpAdaptiveDepth`** — toggles the adaptive depth policy.
|
|
152
|
+
* Defaults to OFF for native MTP and assistants (Gemma4 DSpark has the
|
|
153
|
+
* family override documented below). When ON, the native-MTP default
|
|
154
|
+
* mode runs a 5-state machine
|
|
155
|
+
* (`Explore` → `Full` → {`NeighborProbe` | `Reduced` → `Probe`})
|
|
156
|
+
* with per-depth EMA tracking of
|
|
157
|
+
* `accepted_tokens / cycle_wall_ns` and picks the depth that
|
|
158
|
+
* maximizes that rate (DFlash-style, EMA decay α=0.3, drop-back
|
|
159
|
+
* threshold 0.75). `MLX_MTP_ADAPTIVE_DEPTH_MODE=expected-value`
|
|
160
|
+
* instead uses the MTPLX-style intra-cycle expected-value gate; by
|
|
161
|
+
* default it stops at its base depth, with deeper expansion kept
|
|
162
|
+
* research-only behind `MLX_MTP_EV_ALLOW_DEEPEN=1`.
|
|
163
|
+
* An explicit `false` always wins, pinning the chosen `mtpDepth` for
|
|
164
|
+
* every cycle. When `enableMtp` is false (or the model has no MTP
|
|
165
|
+
* head) the field is ignored.
|
|
166
|
+
*
|
|
167
|
+
* The defaults for both `mtpDepth` and `mtpAdaptiveDepth` are
|
|
168
|
+
* applied on the native side (see
|
|
169
|
+
* `crates/mlx-core/src/models/qwen3_5/chat_common.rs` MTP runtime
|
|
170
|
+
* flag inventory), so omitting them from `defaultConfig` /
|
|
171
|
+
* `SendOptions.config` is the recommended path for callers that
|
|
172
|
+
* just want speculative decoding "on with sensible defaults".
|
|
173
|
+
*
|
|
174
|
+
* ## Gemma4 external drafts (`draftModelPath`)
|
|
175
|
+
*
|
|
176
|
+
* Gemma4 reinterprets the knobs per draft variant (resolved in
|
|
177
|
+
* `gemma4/model.rs` `resolve_params`, always from the RAW config
|
|
178
|
+
* value — the engine's central `[1, 5]` clamp is an MTP-head
|
|
179
|
+
* contract that does not apply to external drafts):
|
|
180
|
+
*
|
|
181
|
+
* - **DSpark**: with both knobs unset, full draft blocks (the
|
|
182
|
+
* checkpoint's block size — 7 tokens on
|
|
183
|
+
* `dspark_gemma4_12b_block7`) run behind a short per-turn measurement
|
|
184
|
+
* against target-only AR. If DSpark loses on the current host/context,
|
|
185
|
+
* that turn permanently falls back to exact target-only decoding. An
|
|
186
|
+
* insufficiently long generation budget preserves the fixed-block path.
|
|
187
|
+
* An explicit `mtpDepth` acts as a CAP on the block (clamped to
|
|
188
|
+
* `[1, blockSize]`) and pins it unless `mtpAdaptiveDepth: true` opts the
|
|
189
|
+
* guard back in. Explicit `mtpAdaptiveDepth: false` disables the guard.
|
|
190
|
+
* - **Assistant** (Google `gemma-4-*-it-assistant`): chained AR
|
|
191
|
+
* drafting has no checkpoint-pinned block size — an unset
|
|
192
|
+
* `mtpDepth` drafts 3 tokens per cycle (`ASSISTANT_DEFAULT_DEPTH`,
|
|
193
|
+
* a quality/latency tradeoff, not a checkpoint contract), and an
|
|
194
|
+
* explicit `mtpDepth` clamps to `[1, 8]` (`ASSISTANT_MAX_DEPTH`).
|
|
195
|
+
*
|
|
196
|
+
* `mtpAdaptiveDepth` remains ignored for the assistant variant. Qwen3.5
|
|
197
|
+
* native-MTP semantics above are unchanged.
|
|
198
|
+
*/
|
|
199
|
+
hasMtpWeights?(): boolean;
|
|
200
|
+
}
|
|
201
|
+
/** Per-call options for {@link ChatSession#send} / `sendStream`. */
|
|
202
|
+
export interface SendOptions {
|
|
203
|
+
/**
|
|
204
|
+
* Optional image bytes attached to this user turn. When the image
|
|
205
|
+
* set differs from the session's current `lastImagesKey`, the
|
|
206
|
+
* session forcibly restarts via `chatSessionStart`.
|
|
207
|
+
*/
|
|
208
|
+
images?: Uint8Array[];
|
|
209
|
+
/**
|
|
210
|
+
* Optional audio bytes (encoded WAV) attached to this user turn. When
|
|
211
|
+
* the audio set differs from the session's current `lastAudioKey`, the
|
|
212
|
+
* session forcibly restarts via `chatSessionStart` (mirrors `images`).
|
|
213
|
+
* Only the unified Gemma 4 audio checkpoint consumes this.
|
|
214
|
+
*/
|
|
215
|
+
audio?: Uint8Array[];
|
|
216
|
+
/**
|
|
217
|
+
* Per-call `ChatConfig` overlay applied on top of the session's
|
|
218
|
+
* `defaultConfig`. `reuseCache` is always forced on regardless of
|
|
219
|
+
* what the caller passes. The overlay is shallow-merged on top of
|
|
220
|
+
* the session default, so per-call values always win over per-session
|
|
221
|
+
* values for the same field — including the speculative-decode
|
|
222
|
+
* knobs `enableMtp`, `mtpDepth`, and `mtpAdaptiveDepth`. See
|
|
223
|
+
* {@link SessionCapableModel.hasMtpWeights} for the full MTP knob
|
|
224
|
+
* surface and default-resolution rules.
|
|
225
|
+
*/
|
|
226
|
+
config?: ChatConfig;
|
|
227
|
+
/**
|
|
228
|
+
* Optional AbortSignal plumbed into the streaming fast-abort path.
|
|
229
|
+
*
|
|
230
|
+
* Only honored by the streaming entry points (`sendStream`,
|
|
231
|
+
* `sendToolResultStream`, `startFromHistoryStream`) — the
|
|
232
|
+
* non-streaming `send` / `sendToolResult` / `startFromHistory`
|
|
233
|
+
* calls have NO native cancel surface, so a signal passed to them
|
|
234
|
+
* is ignored. Pass one here and the inner `_runChatStream`
|
|
235
|
+
* adapter wakes from `waitForItem()` on abort, calls
|
|
236
|
+
* `handle.cancel()` on the native handle, and unwinds the stream
|
|
237
|
+
* without throwing an AbortError — the outer consumer's `for await`
|
|
238
|
+
* just ends early. Intended for HTTP endpoints that flip a
|
|
239
|
+
* controller on `res.once('close', …)` so client disconnect stops
|
|
240
|
+
* the native decode at the next safepoint rather than running it
|
|
241
|
+
* to completion under the per-model mutex.
|
|
242
|
+
*/
|
|
243
|
+
signal?: AbortSignal;
|
|
244
|
+
}
|
|
245
|
+
/** Constructor options for {@link ChatSession}. */
|
|
246
|
+
export interface ChatSessionOptions {
|
|
247
|
+
/**
|
|
248
|
+
* Optional system prompt prepended as the first message on turn 1.
|
|
249
|
+
* Subsequent turns don't re-inject the system prompt — the cache
|
|
250
|
+
* already holds it.
|
|
251
|
+
*/
|
|
252
|
+
system?: string;
|
|
253
|
+
/**
|
|
254
|
+
* Default `ChatConfig` applied to every `send()` / `sendStream()`
|
|
255
|
+
* / `sendToolResult()` call. Per-call config is shallow-merged on
|
|
256
|
+
* top of this, and `reuseCache` is forced on. Speculative-decode
|
|
257
|
+
* knobs (`enableMtp`, `mtpDepth`, `mtpAdaptiveDepth`) can be parked
|
|
258
|
+
* here as session-wide defaults and overridden per call via
|
|
259
|
+
* {@link SendOptions.config}; see
|
|
260
|
+
* {@link SessionCapableModel.hasMtpWeights} for the MTP knob surface
|
|
261
|
+
* and default-resolution rules.
|
|
262
|
+
*/
|
|
263
|
+
defaultConfig?: ChatConfig;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Cross-model chat session. See module docstring for design notes.
|
|
267
|
+
*
|
|
268
|
+
* The generic parameter `M` statically captures the concrete model
|
|
269
|
+
* type so the structural interface stays as expressive as the
|
|
270
|
+
* concrete one. Internally the class only uses the
|
|
271
|
+
* `SessionCapableModel` surface.
|
|
272
|
+
*/
|
|
273
|
+
export declare class ChatSession<M extends SessionCapableModel = SessionCapableModel> {
|
|
274
|
+
private readonly model;
|
|
275
|
+
private readonly system;
|
|
276
|
+
private readonly defaultConfig;
|
|
277
|
+
/**
|
|
278
|
+
* Full conversation history tracked on the TS side. Appended to on
|
|
279
|
+
* every successful turn. Only read back when the image-change path
|
|
280
|
+
* triggers a restart — normal text continues use the server-side
|
|
281
|
+
* cache, not this array.
|
|
282
|
+
*/
|
|
283
|
+
private history;
|
|
284
|
+
/**
|
|
285
|
+
* Hex-encoded byte-identity key of the image set currently bound
|
|
286
|
+
* to the server's KV cache (SHA-256; see `computeImagesKey`).
|
|
287
|
+
* `null` when no images are cached. A `send()` whose new key
|
|
288
|
+
* differs triggers a full `chatSessionStart` restart.
|
|
289
|
+
*/
|
|
290
|
+
private lastImagesKey;
|
|
291
|
+
/**
|
|
292
|
+
* Hex-encoded byte-identity key of the audio set currently bound to the
|
|
293
|
+
* server's KV cache (see {@link computeAudioKey}). `null` when no audio is
|
|
294
|
+
* cached. A `send()` whose new key differs triggers a full
|
|
295
|
+
* `chatSessionStart` restart — the audio counterpart of `lastImagesKey`.
|
|
296
|
+
*/
|
|
297
|
+
private lastAudioKey;
|
|
298
|
+
private turnCount;
|
|
299
|
+
private inFlight;
|
|
300
|
+
/** A failed/abandoned native delta must be followed by a full replay. */
|
|
301
|
+
private needsFullReplay;
|
|
302
|
+
/**
|
|
303
|
+
* Count of `ok` tool calls emitted by the prior assistant turn, or
|
|
304
|
+
* `null` when the prior turn produced none. Gates every continuation
|
|
305
|
+
* entry point on the tool-call resolution invariant because each
|
|
306
|
+
* native `chat_session_continue*` dispatch re-opens the assistant
|
|
307
|
+
* turn:
|
|
308
|
+
*
|
|
309
|
+
* - A plain text `send` / `sendStream` after ANY outstanding tool
|
|
310
|
+
* call would orphan the call(s) by weaving a fresh user turn
|
|
311
|
+
* between the assistant's `tool_call` and any response.
|
|
312
|
+
* - A `sendToolResult` / `sendToolResultStream` is only servable
|
|
313
|
+
* when exactly one tool call is outstanding. A multi-call
|
|
314
|
+
* fan-out (`> 1`) cannot be resolved one result at a time — the
|
|
315
|
+
* siblings would be separated by fresh assistant replies — so
|
|
316
|
+
* those entry points also reject.
|
|
317
|
+
*
|
|
318
|
+
* Cleared on every successful commit whose new turn emits zero `ok`
|
|
319
|
+
* tool calls, and on `reset()`. See `assertCanSendPlain` /
|
|
320
|
+
* `assertCanSendToolResult` for the per-entry-point gate logic.
|
|
321
|
+
*/
|
|
322
|
+
private unresolvedOkToolCallCount;
|
|
323
|
+
constructor(model: M, options?: ChatSessionOptions);
|
|
324
|
+
/**
|
|
325
|
+
* Number of completed turns. Increments only after a successful
|
|
326
|
+
* round-trip — in-flight or failed calls leave this untouched.
|
|
327
|
+
*/
|
|
328
|
+
get turns(): number;
|
|
329
|
+
/** Whether the session currently has images bound to its cache. */
|
|
330
|
+
get hasImages(): boolean;
|
|
331
|
+
/** Load-time physical context snapshot, when exposed by the model. */
|
|
332
|
+
contextLimits(): SessionContextLimits | undefined;
|
|
333
|
+
/** Authoritative image-input capability of the loaded native model. */
|
|
334
|
+
supportsImages(): boolean;
|
|
335
|
+
/**
|
|
336
|
+
* Render and validate a complete message list against the model's physical
|
|
337
|
+
* context window without starting inference or mutating session/native cache
|
|
338
|
+
* state.
|
|
339
|
+
*
|
|
340
|
+
* HTTP streaming callers use this before committing SSE headers so an
|
|
341
|
+
* oversized prompt can still receive a protocol-shaped 400 response without
|
|
342
|
+
* delaying those headers until image processing, prefill, or the first
|
|
343
|
+
* generated token. The returned config carries the same output-budget clamp
|
|
344
|
+
* applied by the send entry points; those entry points intentionally repeat
|
|
345
|
+
* the check against their own authoritative history before native dispatch.
|
|
346
|
+
*/
|
|
347
|
+
preflightContextCapacity(messages: readonly ChatMessage[], config?: ChatConfig): Promise<ChatConfig>;
|
|
348
|
+
/**
|
|
349
|
+
* Capacity-preflight one pending user/tool message against this session's
|
|
350
|
+
* preserved history without starting inference or mutating cache state.
|
|
351
|
+
*
|
|
352
|
+
* This is the exact counterpart of the delta `send*` paths. It matters for
|
|
353
|
+
* server-side prompt-cache hits where the HTTP request contains only the new
|
|
354
|
+
* message while the leased ChatSession owns the earlier conversation.
|
|
355
|
+
*/
|
|
356
|
+
preflightPendingContextCapacity(pending: ChatMessage, config?: ChatConfig): Promise<ChatConfig>;
|
|
357
|
+
/**
|
|
358
|
+
* Count of `ok` tool calls from the most recent assistant turn, or
|
|
359
|
+
* `null` when the trailing turn produced none. Non-null means the
|
|
360
|
+
* session is parked on an unresolved tool-call turn and the only
|
|
361
|
+
* forward-progress move is `sendToolResult*()` against one of the
|
|
362
|
+
* outstanding ids — and only when the count is exactly 1. A
|
|
363
|
+
* multi-call fan-out (`> 1`) cannot be served by the chat-session
|
|
364
|
+
* API at all; server endpoints should pre-check this getter and
|
|
365
|
+
* route around a fan-out via `reset()` + `primeHistory()` +
|
|
366
|
+
* `startFromHistory()` cold replay that resolves every sibling in
|
|
367
|
+
* one atomic jinja render.
|
|
368
|
+
*
|
|
369
|
+
* The flag updates after every successful `send` / `sendStream` /
|
|
370
|
+
* `sendToolResult` / `sendToolResultStream` / `startFromHistory*`
|
|
371
|
+
* commit, and after `primeHistory()` (from the trailing assistant
|
|
372
|
+
* message's `toolCalls.length`). `reset()` clears it.
|
|
373
|
+
*/
|
|
374
|
+
get pendingUnresolvedToolCallCount(): number | null;
|
|
375
|
+
/**
|
|
376
|
+
* Send a user message and resolve with the assistant reply.
|
|
377
|
+
*
|
|
378
|
+
* Turn 0 and any turn whose image set has changed dispatch through
|
|
379
|
+
* `chatSessionStart` with the full history. All other turns use
|
|
380
|
+
* the cheap `chatSessionContinue` delta path.
|
|
381
|
+
*/
|
|
382
|
+
send(userMessage: string, opts?: SendOptions): Promise<ChatResult>;
|
|
383
|
+
/**
|
|
384
|
+
* Streaming variant of {@link ChatSession#send}.
|
|
385
|
+
*
|
|
386
|
+
* Routing matches `send()`. The assistant reply is accumulated
|
|
387
|
+
* from stream deltas and pushed to `history` only after a
|
|
388
|
+
* successful terminal chunk (`done: true` with non-error
|
|
389
|
+
* `finishReason`). Caller break, mid-stream exceptions, and error
|
|
390
|
+
* finishes all leave `turnCount` untouched and the history
|
|
391
|
+
* un-appended for the turn so the next call re-routes through the
|
|
392
|
+
* start path.
|
|
393
|
+
*/
|
|
394
|
+
sendStream(userMessage: string, opts?: SendOptions): AsyncGenerator<ChatStreamEvent>;
|
|
395
|
+
/**
|
|
396
|
+
* Send a tool-result turn. Always dispatches
|
|
397
|
+
* `chatSessionContinueTool` — tool turns never change image state,
|
|
398
|
+
* so there is no restart path here.
|
|
399
|
+
*
|
|
400
|
+
* Rejects if the prior assistant turn emitted more than one `ok`
|
|
401
|
+
* tool call: the chat-session API only supports exactly one tool
|
|
402
|
+
* call per assistant turn because each `sendToolResult` dispatch
|
|
403
|
+
* immediately re-opens the assistant turn, so responding to the
|
|
404
|
+
* remaining calls would interleave new assistant replies between
|
|
405
|
+
* the results and corrupt the conversation structure. Callers that
|
|
406
|
+
* hit this must tighten the prompt / tool spec or reset the
|
|
407
|
+
* session.
|
|
408
|
+
*
|
|
409
|
+
* `isError` is the structured tool-error signal. When `true`, the
|
|
410
|
+
* native renderer prepends a short, model-facing error marker to
|
|
411
|
+
* `content` inside the wire-format tool block so the model
|
|
412
|
+
* receives a clear text-level cue that the tool result represents
|
|
413
|
+
* a failure. The structured field is stored verbatim on the
|
|
414
|
+
* appended `{ role: 'tool', ... }` history entry so cold-replay
|
|
415
|
+
* (image-change restart, `startFromHistory*`, server-side
|
|
416
|
+
* `SessionRegistry` cache-miss rebuild) re-renders the marker
|
|
417
|
+
* consistently with the live turn. Defaults to `undefined` (no
|
|
418
|
+
* marker). Pass through verbatim — we do NOT infer error from
|
|
419
|
+
* `content`.
|
|
420
|
+
*
|
|
421
|
+
* Appends a `{ role: 'tool', ... }` message to history on success.
|
|
422
|
+
*/
|
|
423
|
+
sendToolResult(toolCallId: string, content: string, opts?: {
|
|
424
|
+
isError?: boolean;
|
|
425
|
+
config?: ChatConfig;
|
|
426
|
+
}): Promise<ChatResult>;
|
|
427
|
+
/**
|
|
428
|
+
* Cold-replay a tool result through the start path: re-render the
|
|
429
|
+
* full preserved history (including the prior media turn and the
|
|
430
|
+
* unresolved tool-call assistant turn) plus this tool message. Used
|
|
431
|
+
* when the native session is cold (turnCount===0 — e.g. after an
|
|
432
|
+
* interrupted media-held replay rolled the cache back) and by the
|
|
433
|
+
* media-held rejection catch. `mediaChanged=true` forces a
|
|
434
|
+
* resetCaches so the prefill always starts from a guaranteed-clean
|
|
435
|
+
* cache; `isFirstTurn=false` because a tool result always follows a
|
|
436
|
+
* prior tool-call turn.
|
|
437
|
+
*/
|
|
438
|
+
private replayToolResultThroughStartPath;
|
|
439
|
+
/**
|
|
440
|
+
* Streaming variant of {@link ChatSession#sendToolResult}.
|
|
441
|
+
*
|
|
442
|
+
* `isError` mirrors the non-streaming entry point — when `true`,
|
|
443
|
+
* the native renderer prepends a short, model-facing error marker
|
|
444
|
+
* to `content` inside the wire-format tool block. The structured
|
|
445
|
+
* field is stored verbatim on the appended `{ role: 'tool', ... }`
|
|
446
|
+
* history entry so cold-replay re-renders the marker consistently
|
|
447
|
+
* with the live streaming turn.
|
|
448
|
+
*/
|
|
449
|
+
sendToolResultStream(toolCallId: string, content: string, opts?: {
|
|
450
|
+
isError?: boolean;
|
|
451
|
+
config?: ChatConfig;
|
|
452
|
+
signal?: AbortSignal;
|
|
453
|
+
}): AsyncGenerator<ChatStreamEvent>;
|
|
454
|
+
/**
|
|
455
|
+
* Streaming counterpart of {@link replayToolResultThroughStartPath}:
|
|
456
|
+
* cold-replay a tool result through the start stream. Used by the
|
|
457
|
+
* turn-0 precheck and the media-held rejection catch in
|
|
458
|
+
* {@link sendToolResultStream}. The start stream owns the history
|
|
459
|
+
* push, turnCount increment, and media-key rehydration; callers keep
|
|
460
|
+
* `delegated`/early-return semantics so the commit `finally` stays
|
|
461
|
+
* off.
|
|
462
|
+
*/
|
|
463
|
+
private replayToolResultThroughStartStreamPath;
|
|
464
|
+
/**
|
|
465
|
+
* Reset the session state.
|
|
466
|
+
*
|
|
467
|
+
* Clears the underlying model's KV caches and wipes local history,
|
|
468
|
+
* image key, and turn counter so the next `send()` goes through
|
|
469
|
+
* `chatSessionStart` again.
|
|
470
|
+
*
|
|
471
|
+
* This is a full wipe — safe default for public callers. It always
|
|
472
|
+
* calls `model.resetCaches()`, which is the ONLY behavior exposed
|
|
473
|
+
* on the public API because the underlying `SessionCapableModel`
|
|
474
|
+
* is shared across every `ChatSession` lifetime via the native
|
|
475
|
+
* `ModelRegistry`: a partial wipe that leaves the shared native
|
|
476
|
+
* KV cache intact would leak a previous (unrelated) request's
|
|
477
|
+
* cached prefix into the next `chat_session_start_sync` call. The
|
|
478
|
+
* server-side warm-lease replay path (where preserving the native
|
|
479
|
+
* cache is correct) uses its own server-private helper gated by
|
|
480
|
+
* the `SessionRegistry` HIT signal — the only authoritative proof
|
|
481
|
+
* that the native cache genuinely belongs to this chain. That
|
|
482
|
+
* helper lives inside `@mlx-node/server`, never touches the
|
|
483
|
+
* `@mlx-node/lm` export map, and is not reachable from downstream
|
|
484
|
+
* consumers. Public consumers of `@mlx-node/lm` have no such HIT
|
|
485
|
+
* signal, so the public API intentionally offers only the full-wipe
|
|
486
|
+
* option.
|
|
487
|
+
*
|
|
488
|
+
* Returns `Promise<void>` for an async-friendly signature even
|
|
489
|
+
* though `resetCaches()` is currently synchronous.
|
|
490
|
+
*/
|
|
491
|
+
reset(): Promise<void>;
|
|
492
|
+
/**
|
|
493
|
+
* Prime the session history without running inference.
|
|
494
|
+
*
|
|
495
|
+
* Used by the server-side `SessionRegistry` cold-start fallback: when
|
|
496
|
+
* a request arrives with a `previous_response_id` that the cache has
|
|
497
|
+
* missed, the endpoint reconstructs the full conversation from the
|
|
498
|
+
* `ResponseStore` and primes a fresh session with it, then calls
|
|
499
|
+
* `startFromHistory()` to replay it through the native KV cache.
|
|
500
|
+
*
|
|
501
|
+
* Rejects if the session is in flight or has already taken a turn.
|
|
502
|
+
* Replaces the internal history with a shallow copy of `messages`.
|
|
503
|
+
*/
|
|
504
|
+
primeHistory(messages: ChatMessage[]): void;
|
|
505
|
+
/**
|
|
506
|
+
* Run a cold-start `chatSessionStart` using the currently primed
|
|
507
|
+
* history.
|
|
508
|
+
*
|
|
509
|
+
* Intended pairing with {@link primeHistory}: call
|
|
510
|
+
* `primeHistory(fullHistory)` first, then `startFromHistory()` to
|
|
511
|
+
* replay the conversation through the native chat-session API. The
|
|
512
|
+
* final history entry must be a user or tool turn — this is what the
|
|
513
|
+
* native side treats as the "current input" to generate against.
|
|
514
|
+
*
|
|
515
|
+
* Pushes the assistant reply onto the history, advances `turnCount`
|
|
516
|
+
* to 1, and computes `lastImagesKey` from the most recent user
|
|
517
|
+
* message that carries images (so subsequent text-only continues
|
|
518
|
+
* stay on the delta path, and subsequent image turns correctly
|
|
519
|
+
* trigger restart).
|
|
520
|
+
*/
|
|
521
|
+
startFromHistory(config?: ChatConfig): Promise<ChatResult>;
|
|
522
|
+
/**
|
|
523
|
+
* Streaming counterpart to {@link startFromHistory}.
|
|
524
|
+
*
|
|
525
|
+
* Iterates `model.chatStreamSessionStart(history.slice(), config)`,
|
|
526
|
+
* accumulates text, and only commits history + `turnCount` +
|
|
527
|
+
* `lastImagesKey` in the `finally` block when a successful terminal
|
|
528
|
+
* chunk was observed (`done: true` with non-error finishReason).
|
|
529
|
+
* Because history is primed (not appended to), rollback on failure
|
|
530
|
+
* is a no-op: the primed state stays intact so the caller can retry.
|
|
531
|
+
*/
|
|
532
|
+
startFromHistoryStream(config?: ChatConfig, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
|
|
533
|
+
/**
|
|
534
|
+
* Gate plain-text continuation entry points (`send`, `sendStream`)
|
|
535
|
+
* on the tool-call resolution invariant. Any outstanding `ok` tool
|
|
536
|
+
* call from the prior assistant turn — single or multi — makes a
|
|
537
|
+
* plain text continuation unsafe: the native chat-session API
|
|
538
|
+
* re-opens the assistant turn on each continue, so a new user delta
|
|
539
|
+
* would weave a fresh user message between the assistant's
|
|
540
|
+
* `tool_call` and any response, orphaning the call. Callers must
|
|
541
|
+
* resolve outstanding calls via `sendToolResult*()` (single-call
|
|
542
|
+
* case) or re-enter via `reset()` + `primeHistory()` +
|
|
543
|
+
* `startFromHistory()` with a resolved conversation (multi-call
|
|
544
|
+
* fan-out). `reset()` clears the flag and `startFromHistory*`
|
|
545
|
+
* overwrites it via `recordToolCallFanout` on the new response, so
|
|
546
|
+
* legitimate recovery paths are unaffected.
|
|
547
|
+
*/
|
|
548
|
+
private assertCanSendPlain;
|
|
549
|
+
/**
|
|
550
|
+
* Gate tool-result entry points (`sendToolResult`,
|
|
551
|
+
* `sendToolResultStream`) on the single-tool-call-per-turn
|
|
552
|
+
* invariant. Exactly one outstanding tool call is servable — that
|
|
553
|
+
* is the case these methods exist for.
|
|
554
|
+
*
|
|
555
|
+
* Zero outstanding calls (`null`) is also unservable: without a
|
|
556
|
+
* preceding assistant turn that emitted a tool call, a tool-result
|
|
557
|
+
* dispatch would synthesize a `<tool_response>` delta for a call
|
|
558
|
+
* that never existed, corrupting the conversation structure. The
|
|
559
|
+
* native backends do not authenticate `tool_call_id` against prior
|
|
560
|
+
* state — several simply append the tool-response delta verbatim —
|
|
561
|
+
* so rejecting here is the only gate that prevents forged tool
|
|
562
|
+
* state from reaching the model. Callers that want to start a
|
|
563
|
+
* conversation on a resolved tool turn must prime an unresolved
|
|
564
|
+
* single-call assistant turn via `primeHistory()` +
|
|
565
|
+
* `startFromHistory()` first.
|
|
566
|
+
*
|
|
567
|
+
* A multi-call fan-out (`> 1`) cannot be resolved one result at a
|
|
568
|
+
* time because each `sendToolResult` dispatch immediately re-opens
|
|
569
|
+
* the assistant turn, so responding to the siblings would
|
|
570
|
+
* interleave new assistant replies between the results.
|
|
571
|
+
*/
|
|
572
|
+
private assertCanSendToolResult;
|
|
573
|
+
/**
|
|
574
|
+
* Inspect a just-committed turn's tool calls and store the count of
|
|
575
|
+
* `ok` entries in `unresolvedOkToolCallCount`. Any non-zero count
|
|
576
|
+
* parks the session on an unresolved tool-call turn, which gates
|
|
577
|
+
* the next entry point:
|
|
578
|
+
*
|
|
579
|
+
* - count === 0 → flag is `null`: `send`/`sendStream` ok,
|
|
580
|
+
* `sendToolResult*` throws (no outstanding call to resolve)
|
|
581
|
+
* - count === 1 → `send`/`sendStream` throw; `sendToolResult*` ok
|
|
582
|
+
* - count > 1 → every entry point throws (fan-out unservable)
|
|
583
|
+
*
|
|
584
|
+
* See `assertCanSendPlain` / `assertCanSendToolResult` for the full
|
|
585
|
+
* rationale.
|
|
586
|
+
*/
|
|
587
|
+
private recordToolCallFanout;
|
|
588
|
+
/**
|
|
589
|
+
* Merge default + per-call config and force `reuseCache: true`.
|
|
590
|
+
* The session path is a session-reuse operation by construction —
|
|
591
|
+
* `reuseCache: false` on the continue path would wipe the very
|
|
592
|
+
* cache the delta depends on.
|
|
593
|
+
*
|
|
594
|
+
* MTP auto-default: if neither `defaultConfig` nor `overlay`
|
|
595
|
+
* sets `enableMtp` AND the underlying model exposes
|
|
596
|
+
* `hasMtpWeights()` returning `true`, set `enableMtp = true` so the
|
|
597
|
+
* speculative-decode path runs out of the box on MTP-capable
|
|
598
|
+
* checkpoints. An explicit `false` from either source wins (the
|
|
599
|
+
* undefined-check below preserves it). This duck-typed check also
|
|
600
|
+
* covers Gemma4 with an external draft attached — DSpark or Google
|
|
601
|
+
* assistant (`hasMtpWeights()` reports the external draft there,
|
|
602
|
+
* not in-checkpoint MTP heads).
|
|
603
|
+
*/
|
|
604
|
+
private mergeConfig;
|
|
605
|
+
/**
|
|
606
|
+
* Render the exact full prompt and constrain generation to the physical KV
|
|
607
|
+
* window before native code allocates a block. Models that do not expose
|
|
608
|
+
* both the tokenizer seam and a load-time context snapshot retain their
|
|
609
|
+
* existing behavior.
|
|
610
|
+
*
|
|
611
|
+
* The returned config is a copy only when `maxNewTokens` needs clamping.
|
|
612
|
+
* An omitted output budget stays omitted when the native default fits; it is
|
|
613
|
+
* made explicit only when the remaining window is smaller than that default.
|
|
614
|
+
*/
|
|
615
|
+
private constrainToContextCapacity;
|
|
616
|
+
/** Full history that a pending user/tool turn would render, without mutation. */
|
|
617
|
+
private historyWithPending;
|
|
618
|
+
/**
|
|
619
|
+
* Shared start-path logic for `send()`. Handles both the turn-0
|
|
620
|
+
* first-ever-send case and the image-change mid-session restart
|
|
621
|
+
* case. The image-change restart preserves prior history so the
|
|
622
|
+
* native side gets the full conversation re-rendered with the new
|
|
623
|
+
* image set.
|
|
624
|
+
*/
|
|
625
|
+
private runStartPath;
|
|
626
|
+
/**
|
|
627
|
+
* Core of {@link runStartPath} that takes a PRE-BUILT pending
|
|
628
|
+
* `ChatMessage` (user or tool) instead of building a user message
|
|
629
|
+
* itself. The cold-restart catch in `sendToolResult()` replays the
|
|
630
|
+
* conversation through this core with a pending `{ role: 'tool', ... }`
|
|
631
|
+
* message so the tool-result turn is re-rendered against the full
|
|
632
|
+
* history without duplicating the start-path bookkeeping.
|
|
633
|
+
*/
|
|
634
|
+
private runStartPathWithMessage;
|
|
635
|
+
/** Streaming counterpart to {@link runStartPath}. */
|
|
636
|
+
private runStartStreamPath;
|
|
637
|
+
/**
|
|
638
|
+
* Streaming counterpart to {@link runStartPathWithMessage}: replays
|
|
639
|
+
* through the cold start stream from a PRE-BUILT pending
|
|
640
|
+
* `ChatMessage`. The cold-restart catch in `sendToolResultStream()`
|
|
641
|
+
* delegates here with a pending `{ role: 'tool', ... }` message.
|
|
642
|
+
*/
|
|
643
|
+
private runStartStreamPathWithMessage;
|
|
644
|
+
/**
|
|
645
|
+
* Shared pre-start bookkeeping for both `send()` and `sendStream()`:
|
|
646
|
+
*
|
|
647
|
+
* - On an image-change restart (turn >= 1), reset the native KV
|
|
648
|
+
* caches so the new image set gets a fresh prefill. History is
|
|
649
|
+
* intentionally preserved — `chatSessionStart` receives the full
|
|
650
|
+
* accumulated conversation plus the new user turn so the jinja
|
|
651
|
+
* render walks every prior turn and every prior image again
|
|
652
|
+
* (see plan's Turn 3 example: "full jinja on 3-turn history +
|
|
653
|
+
* image B"). `lastImagesKey` will be overwritten by the
|
|
654
|
+
* successful start path right after, and `turnCount` is
|
|
655
|
+
* incremented by the start path the same way as for any other
|
|
656
|
+
* turn.
|
|
657
|
+
* - On a fresh / reset history, re-inject the system prompt.
|
|
658
|
+
*/
|
|
659
|
+
private prepareStartPath;
|
|
660
|
+
/** Build a user `ChatMessage` with or without attached images/audio. */
|
|
661
|
+
private buildUserMessage;
|
|
662
|
+
/**
|
|
663
|
+
* Walk the history backward to find the most recent user message
|
|
664
|
+
* with images and return its SHA-256 key. Used by
|
|
665
|
+
* {@link startFromHistory} and {@link startFromHistoryStream} to
|
|
666
|
+
* hydrate `lastImagesKey` after a cold replay, so subsequent delta
|
|
667
|
+
* continues correctly detect image changes.
|
|
668
|
+
*/
|
|
669
|
+
private computeTrailingImagesKey;
|
|
670
|
+
/**
|
|
671
|
+
* Audio counterpart of {@link computeTrailingImagesKey}: walk history
|
|
672
|
+
* backward to the most recent user message carrying audio and return its
|
|
673
|
+
* SHA-256 key, so a cold replay hydrates `lastAudioKey` correctly.
|
|
674
|
+
*/
|
|
675
|
+
private computeTrailingAudioKey;
|
|
676
|
+
/**
|
|
677
|
+
* Derive the post-prime value of `unresolvedOkToolCallCount` from
|
|
678
|
+
* the primed history. Walks backward to the most recent assistant
|
|
679
|
+
* turn, then walks forward from that assistant to the end of history
|
|
680
|
+
* subtracting any `tool:` message that references one of the turn's
|
|
681
|
+
* `call_id`s. A fully-resolved history (every outstanding id matched
|
|
682
|
+
* by a sibling `tool:` message) returns `null`; any leftover count is
|
|
683
|
+
* the number of still-unresolved tool calls.
|
|
684
|
+
*
|
|
685
|
+
* Matches the runtime `recordToolCallFanout` semantics on the hot
|
|
686
|
+
* path: zero unresolved → `null` (no pending obligation); one →
|
|
687
|
+
* `1` (servable via `sendToolResult*()` only); two or more → the
|
|
688
|
+
* count itself (unservable fan-out — must be resolved via cold
|
|
689
|
+
* replay). The distinction between "ok" vs. other statuses only
|
|
690
|
+
* exists in the live `ToolCallResult[]` emitted by the native side —
|
|
691
|
+
* the persisted `ChatMessage.toolCalls` on an assistant message only
|
|
692
|
+
* carries successfully parsed calls (i.e. what would have been "ok"
|
|
693
|
+
* in the original live turn), so counting the array length is
|
|
694
|
+
* equivalent. Tool calls whose `id` is missing or empty can't be
|
|
695
|
+
* matched against subsequent `tool_call_id`s, so in that case we
|
|
696
|
+
* fall back to returning the raw `calls.length` (err safe).
|
|
697
|
+
*/
|
|
698
|
+
private computeTrailingAssistantUnresolvedToolCallCount;
|
|
699
|
+
}
|
|
700
|
+
//# sourceMappingURL=chat-session.d.ts.map
|