@mlx-node/core 0.0.5 → 0.0.7

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 (3) hide show
  1. package/index.cjs +61 -52
  2. package/index.d.cts +1166 -415
  3. package/package.json +5 -5
package/index.d.cts CHANGED
@@ -26,7 +26,12 @@ export declare class BatchGenerationResult {
26
26
  get groupSize(): number;
27
27
  }
28
28
 
29
- /** Handle returned by `chat_stream()` to control an in-progress streaming generation. */
29
+ /**
30
+ * Handle returned by the streaming chat-session entry points
31
+ * (`chat_stream_session_start`, `chat_stream_session_continue`,
32
+ * `chat_stream_session_continue_tool`) to control an in-progress
33
+ * streaming generation.
34
+ */
30
35
  export declare class ChatStreamHandle {
31
36
  cancel(): void;
32
37
  }
@@ -103,6 +108,115 @@ export declare class DocUnwarpModel {
103
108
  unwarp(imageData: Uint8Array): UnwarpResult;
104
109
  }
105
110
 
111
+ /**
112
+ * Gemma 4 dense language model.
113
+ *
114
+ * Supports E2B (2.3B), E4B (4.5B), and 31B variants.
115
+ * Features: hybrid attention (sliding + global), GeGLU MLP, logit softcapping,
116
+ * embedding scaling, and optional per-layer embeddings.
117
+ *
118
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
119
+ * commands via channels and await responses.
120
+ */
121
+ export declare class Gemma4Model {
122
+ constructor(config: Gemma4Config);
123
+ modelId(): number;
124
+ /** Load a Gemma4 model from a directory. */
125
+ static load(modelPath: string): Promise<Gemma4Model>;
126
+ /**
127
+ * Reset all caches and clear cached token history. Exposed so
128
+ * tests and session-management code can start from a known clean
129
+ * state between turns.
130
+ *
131
+ * Synchronous on the NAPI boundary — every other `SessionCapableModel`
132
+ * exposes `resetCaches(): void` and the `ChatSession<M>` cross-model
133
+ * wrapper calls this inline during the image-change restart and
134
+ * `reset()` flows. Running it as an async NAPI method would break
135
+ * that contract and silently drop reset failures because
136
+ * `ChatSession.reset()` and the session-start restart path invoke
137
+ * `model.resetCaches()` without awaiting.
138
+ */
139
+ resetCaches(): void;
140
+ /**
141
+ * Start a new chat session.
142
+ *
143
+ * Runs the full jinja chat template once, decodes until Gemma4's
144
+ * `<turn|>` delimiter, and leaves the KV caches on a clean turn
145
+ * boundary so subsequent `chatSessionContinue` /
146
+ * `chatSessionContinueTool` calls can append a raw delta on top
147
+ * without re-rendering the chat template.
148
+ */
149
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
150
+ /**
151
+ * Continue an existing chat session with a new user message.
152
+ *
153
+ * Appends a raw Gemma4 user/model delta to the session's cached KV
154
+ * state, then decodes the model reply. Stops on `<turn|>` so the
155
+ * cache remains on a clean turn boundary for the next turn.
156
+ *
157
+ * Requires a live session started via `chatSessionStart`. Errors
158
+ * if the session is empty, carries image state, or if
159
+ * `config.reuse_cache` is explicitly set to `false`.
160
+ *
161
+ * `images` is an opt-in guard parameter: when non-empty the native
162
+ * side returns an error whose message begins with
163
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
164
+ * `ChatSession` layer can catch the prefix and route image-changes
165
+ * back through a fresh `chatSessionStart` uniformly across all
166
+ * model backends.
167
+ */
168
+ chatSessionContinue(
169
+ userMessage: string,
170
+ images: Uint8Array[] | null | undefined,
171
+ config: ChatConfig | null | undefined,
172
+ ): Promise<ChatResult>;
173
+ /**
174
+ * Continue an existing chat session with a tool-result turn.
175
+ *
176
+ * Builds a Gemma4-format tool delta
177
+ * (`
178
+ <|turn>tool
179
+ {content}<turn|>
180
+ <|turn>model
181
+ `) from
182
+ * `content` and prefills it on top of the live session caches,
183
+ * then decodes the model reply. Stops on `<turn|>` so the cache
184
+ * stays on a clean turn boundary for the next turn.
185
+ *
186
+ * The `tool_call_id` is currently dropped by the wire format —
187
+ * Gemma4's chat template identifies tool responses positionally,
188
+ * not via an explicit id. Callers may still log it for their own
189
+ * bookkeeping.
190
+ *
191
+ * Requires a live session started via `chatSessionStart`.
192
+ */
193
+ chatSessionContinueTool(
194
+ toolCallId: string,
195
+ content: string,
196
+ config?: ChatConfig | undefined | null,
197
+ ): Promise<ChatResult>;
198
+ /** Streaming variant of `chatSessionStart`. */
199
+ chatStreamSessionStart(
200
+ messages: ChatMessage[],
201
+ config: ChatConfig | null | undefined,
202
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
203
+ ): Promise<ChatStreamHandle>;
204
+ /** Streaming variant of `chatSessionContinue`. */
205
+ chatStreamSessionContinue(
206
+ userMessage: string,
207
+ images: Uint8Array[] | null | undefined,
208
+ config: ChatConfig | null | undefined,
209
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
210
+ ): Promise<ChatStreamHandle>;
211
+ /** Streaming variant of `chatSessionContinueTool`. */
212
+ chatStreamSessionContinueTool(
213
+ toolCallId: string,
214
+ content: string,
215
+ config: ChatConfig | null | undefined,
216
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
217
+ ): Promise<ChatStreamHandle>;
218
+ }
219
+
106
220
  /** Result from text generation with detailed metadata */
107
221
  export declare class GenerationResult {
108
222
  /** Get the decoded text */
@@ -120,14 +234,15 @@ export declare class GenerationResult {
120
234
  /**
121
235
  * GRPO Training Engine
122
236
  *
123
- * Complete training engine that runs entirely in Rust.
237
+ * Thin coordinator that routes all MLX operations through the model thread.
238
+ * No MxArrays or model state live here — only plain data crosses the boundary.
124
239
  */
125
240
  export declare class GrpoTrainingEngine {
126
241
  /**
127
242
  * Create a new training engine from a Qwen3 model
128
243
  *
129
244
  * # Arguments
130
- * * `model` - The Qwen3 model to train (will be cloned internally)
245
+ * * `model` - The Qwen3 model (must be loaded via load())
131
246
  * * `config` - Engine configuration
132
247
  */
133
248
  constructor(model: Qwen3Model, config: GrpoEngineConfig);
@@ -143,8 +258,8 @@ export declare class GrpoTrainingEngine {
143
258
  * This method performs the complete training cycle:
144
259
  * 1. Generate completions for each prompt (G times per prompt)
145
260
  * 2. Use provided rewards to compute advantages
146
- * 3. Compute GRPO loss and gradients
147
- * 4. Apply gradients (respecting accumulation steps)
261
+ * 3. Compute GRPO loss and gradients (on model thread)
262
+ * 4. Apply gradients (respecting accumulation steps, on model thread)
148
263
  *
149
264
  * # Arguments
150
265
  * * `prompts` - Array of chat conversations to use as prompts
@@ -171,13 +286,14 @@ export declare class GrpoTrainingEngine {
171
286
  /**
172
287
  * Run a training step with pre-generated completions
173
288
  *
174
- * This method performs training using pre-generated completions,
175
- * eliminating the double-generation issue.
289
+ * Uses the cached MxArrays from the most recent generate_batch_for_training
290
+ * call on the model thread. The generation_result parameter is used only for
291
+ * validation (the actual MxArrays are cached on the model thread).
176
292
  *
177
293
  * # Arguments
178
294
  * * `prompts` - Array of chat conversations to use as prompts
179
295
  * * `rewards` - Reward values for each completion (num_prompts * group_size)
180
- * * `generation_result` - Pre-generated completion data from generate_batch_for_training
296
+ * * `generation_result` - Pre-generated completion data (used for validation)
181
297
  *
182
298
  * # Returns
183
299
  * * Training step metrics
@@ -190,8 +306,8 @@ export declare class GrpoTrainingEngine {
190
306
  /**
191
307
  * Unified training step with JS reward callback and optional output recording
192
308
  *
193
- * Same as `train_step_auto` but optionally captures the full RewardOutput data
194
- * for persistence to an output store database.
309
+ * Generates completions via the model thread, calls the JS reward function
310
+ * with plain data, then dispatches the training step to the model thread.
195
311
  *
196
312
  * # Arguments
197
313
  * * `prompts` - Array of chat conversations to use as prompts
@@ -222,7 +338,16 @@ export declare class GrpoTrainingEngine {
222
338
  startEpoch(): void;
223
339
  /** End the current epoch and get metrics */
224
340
  endEpoch(epochTimeSecs: number): EngineEpochMetrics;
225
- /** Reset the engine for a fresh training run */
341
+ /**
342
+ * Reset the engine for a fresh training run.
343
+ *
344
+ * This is a TERMINAL operation on this handle. It drops the training
345
+ * state (optimizer, step counter) on the model thread so a fresh
346
+ * `GRPOTrainingEngine` can be constructed on the same model, and marks
347
+ * THIS handle as invalidated. Any subsequent dispatch-requiring method
348
+ * on this handle returns an error — callers must construct a new
349
+ * engine to continue training.
350
+ */
226
351
  reset(): void;
227
352
  /** Check if reward registry has any rewards registered */
228
353
  get hasBuiltinRewards(): boolean;
@@ -242,25 +367,199 @@ export declare class GrpoTrainingEngine {
242
367
  /**
243
368
  * Save optimizer state (moment tensors + step) to a SafeTensors file.
244
369
  *
245
- * The step counter is stored in the `__metadata__` field.
246
- * Each parameter's first moment (m) and second moment (v) are stored as
247
- * `{param_name}.m` and `{param_name}.v` tensors.
248
- *
249
- * No-op if the engine uses SGD (no optimizer state to save).
370
+ * Routes through the model thread so AdamW moments and step counter
371
+ * survive across checkpoint/resume. No-op if the engine uses SGD
372
+ * (no optimizer to save) or before the first optimizer update has
373
+ * populated any moment tensors.
250
374
  */
251
- saveOptimizerState(path: string): void;
375
+ saveOptimizerState(path: string): Promise<void>;
252
376
  /**
253
377
  * Load optimizer state (moment tensors + step) from a SafeTensors file.
254
378
  *
255
- * Restores the step counter from metadata and sets first/second moment
256
- * tensors for each parameter found in the file.
257
- *
258
- * No-op if the engine uses SGD (no optimizer to restore).
379
+ * Routes through the model thread. No-op if the engine uses SGD.
259
380
  */
260
- loadOptimizerState(path: string): void;
381
+ loadOptimizerState(path: string): Promise<void>;
261
382
  }
262
383
  export type GRPOTrainingEngine = GrpoTrainingEngine;
263
384
 
385
+ /**
386
+ * Harrier embedding model (Qwen3 backbone for text embeddings).
387
+ *
388
+ * Uses last-token pooling and L2 normalization to produce fixed-size
389
+ * embedding vectors from variable-length text inputs.
390
+ */
391
+ export declare class HarrierModel {
392
+ constructor(config: HarrierConfig);
393
+ /**
394
+ * Forward pass returning hidden states (no lm_head projection).
395
+ *
396
+ * # Arguments
397
+ * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
398
+ *
399
+ * # Returns
400
+ * * Hidden states, shape: [batch_size, seq_len, hidden_size]
401
+ */
402
+ forward(inputIds: MxArray): MxArray;
403
+ /**
404
+ * Encode a single text into a normalized embedding vector.
405
+ *
406
+ * Tokenizes the text, runs the forward pass, applies last-token pooling,
407
+ * and L2-normalizes the result. Truncates to `max_position_embeddings`.
408
+ *
409
+ * # Arguments
410
+ * * `text` - Input text to encode
411
+ * * `instruction` - Optional task instruction prefix or preset name
412
+ * (e.g. `"web_search_query"` resolves to the full Harrier prompt).
413
+ * Pass `null` for documents/passages that need no instruction.
414
+ *
415
+ * # Returns
416
+ * * Embedding vector, shape: [hidden_size]
417
+ */
418
+ encode(text: string, instruction?: string | undefined | null): Promise<MxArray>;
419
+ /**
420
+ * Encode a batch of texts into normalized embedding vectors.
421
+ *
422
+ * Each text is independently tokenized and encoded (no padding needed
423
+ * since each goes through its own forward pass). Truncates each text
424
+ * to `max_position_embeddings`.
425
+ *
426
+ * # Arguments
427
+ * * `texts` - Input texts to encode
428
+ * * `instruction` - Optional task instruction prefix or preset name
429
+ * (e.g. `"web_search_query"` resolves to the full Harrier prompt).
430
+ * Pass `null` for documents/passages that need no instruction.
431
+ *
432
+ * # Returns
433
+ * * Embedding matrix, shape: [batch_size, hidden_size]
434
+ */
435
+ encodeBatch(texts: Array<string>, instruction?: string | undefined | null): Promise<MxArray>;
436
+ /** Get the model configuration. */
437
+ getConfig(): HarrierConfig;
438
+ /**
439
+ * Get available prompt presets loaded from config_sentence_transformers.json.
440
+ *
441
+ * Returns a map of task name -> full instruction prefix.
442
+ * Pass a task name to `encode()`/`encodeBatch()` as the `instruction` parameter
443
+ * to use a preset instead of a raw prefix string.
444
+ */
445
+ getPrompts(): Record<string, string>;
446
+ /** Get the total number of model parameters. */
447
+ numParameters(): number;
448
+ /**
449
+ * Load a Harrier embedding model from a directory.
450
+ *
451
+ * Expects the standard HuggingFace layout:
452
+ * - config.json (model configuration)
453
+ * - model.safetensors or weights.safetensors (weights)
454
+ * - tokenizer.json (tokenizer)
455
+ * - config_sentence_transformers.json (optional, prompt presets)
456
+ */
457
+ static load(modelPath: string): Promise<HarrierModel>;
458
+ }
459
+
460
+ /**
461
+ * LFM2 language model (LFM2.5-1.2B-Thinking).
462
+ *
463
+ * Hybrid conv+attention architecture from Liquid AI. 16 layers total:
464
+ * 10 conv layers + 6 full_attention layers. Features gated short
465
+ * convolutions for local processing and standard attention for global context.
466
+ *
467
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
468
+ * commands via channels and await responses.
469
+ */
470
+ export declare class Lfm2Model {
471
+ /** Load an LFM2 model from a directory containing safetensors and config.json. */
472
+ static load(modelPath: string): Promise<Lfm2Model>;
473
+ /**
474
+ * Reset all caches and clear cached token history. Exposed so
475
+ * tests and session-management code can start from a known clean
476
+ * state between turns.
477
+ */
478
+ resetCaches(): void;
479
+ /**
480
+ * Start a new chat session.
481
+ *
482
+ * Runs the full jinja chat template once, decodes until
483
+ * `<|im_end|>`, and leaves the KV/conv caches on a clean ChatML
484
+ * boundary so subsequent `chatSessionContinue` /
485
+ * `chatSessionContinueTool` calls can append a raw delta on top
486
+ * without re-rendering the chat template.
487
+ *
488
+ * Requires `config.reuse_cache` to be enabled (the default).
489
+ */
490
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
491
+ /**
492
+ * Continue an existing chat session with a new user message.
493
+ *
494
+ * Appends a raw ChatML user/assistant delta to the session's
495
+ * cached KV/conv state, then decodes the assistant reply. Stops
496
+ * on `<|im_end|>` so the cache remains on a clean boundary for
497
+ * the next turn.
498
+ *
499
+ * Requires a live session started via `chatSessionStart`. Errors
500
+ * if the session is empty, carries image state, or if
501
+ * `config.reuse_cache` is explicitly set to `false`.
502
+ *
503
+ * LFM2 is text-only; `images` is an opt-in guard parameter: when
504
+ * non-empty the native side returns an error whose message begins
505
+ * with `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
506
+ * `ChatSession` layer can catch the prefix and route
507
+ * image-changes back through a fresh `chatSessionStart`
508
+ * uniformly across all model backends.
509
+ */
510
+ chatSessionContinue(
511
+ userMessage: string,
512
+ images: Uint8Array[] | null | undefined,
513
+ config: ChatConfig | null | undefined,
514
+ ): Promise<ChatResult>;
515
+ /**
516
+ * Continue an existing chat session with a tool-result turn.
517
+ *
518
+ * Builds an LFM2-format tool delta (`<|im_start|>tool
519
+ {content}
520
+ * <|im_end|>`) from `content` and prefills it on top of the live
521
+ * session caches, then decodes the assistant reply. Stops on
522
+ * `<|im_end|>` so the cache stays on a clean boundary for the
523
+ * next turn.
524
+ *
525
+ * The `tool_call_id` is currently dropped by the wire format —
526
+ * LFM2's chat template identifies tool responses positionally,
527
+ * not via an explicit id. Callers may still log it for their own
528
+ * bookkeeping.
529
+ *
530
+ * Requires a live session started via `chatSessionStart`.
531
+ */
532
+ chatSessionContinueTool(
533
+ toolCallId: string,
534
+ content: string,
535
+ config?: ChatConfig | undefined | null,
536
+ ): Promise<ChatResult>;
537
+ /** Streaming variant of `chatSessionStart`. */
538
+ chatStreamSessionStart(
539
+ messages: ChatMessage[],
540
+ config: ChatConfig | null,
541
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
542
+ ): Promise<ChatStreamHandle>;
543
+ /** Streaming variant of `chatSessionContinue`. */
544
+ chatStreamSessionContinue(
545
+ userMessage: string,
546
+ images: Uint8Array[] | null | undefined,
547
+ config: ChatConfig | null,
548
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
549
+ ): Promise<ChatStreamHandle>;
550
+ /** Streaming variant of `chatSessionContinueTool`. */
551
+ chatStreamSessionContinueTool(
552
+ toolCallId: string,
553
+ content: string,
554
+ config: ChatConfig | null,
555
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
556
+ ): Promise<ChatStreamHandle>;
557
+ /** Get the model configuration. */
558
+ getConfig(): Lfm2Config;
559
+ /** Estimated number of model parameters. */
560
+ numParameters(): number;
561
+ }
562
+
264
563
  export declare class MxArray {
265
564
  equal(other: MxArray): MxArray;
266
565
  notEqual(other: MxArray): MxArray;
@@ -399,6 +698,8 @@ export declare class MxArray {
399
698
  sinh(): MxArray;
400
699
  cosh(): MxArray;
401
700
  tanh(): MxArray;
701
+ /** Error function: erf(x) = (2/sqrt(pi)) * integral(0..x, exp(-t^2) dt) */
702
+ erf(): MxArray;
402
703
  floor(): MxArray;
403
704
  ceil(): MxArray;
404
705
  round(): MxArray;
@@ -640,13 +941,13 @@ export declare class OutputStore {
640
941
  }
641
942
 
642
943
  /**
643
- * Opaque handle to KV cache state from a previous chat() call.
944
+ * Opaque handle to KV cache state from a chat-session turn.
644
945
  *
645
- * Pass this back to the next chat() call via `model.setCache(cache)`
646
- * to enable incremental prefill — only new tokens since the last turn
647
- * are processed, avoiding redundant computation.
946
+ * Pass this back via `model.setCache(cache)` before the next
947
+ * chat-session call to enable incremental prefill — only new tokens
948
+ * since the last turn are processed, avoiding redundant computation.
648
949
  *
649
- * Created internally by the model when `reuseCache: true` (default).
950
+ * Created internally by the model during chat-session turns.
650
951
  * Extract via `model.takeCache()`, restore via `model.setCache(cache)`.
651
952
  */
652
953
  export declare class PromptCache {
@@ -658,16 +959,132 @@ export declare class PromptCache {
658
959
  dispose(): void;
659
960
  }
660
961
 
962
+ /**
963
+ * Qianfan-OCR Vision-Language Model (InternVL architecture).
964
+ *
965
+ * Combines InternViT vision encoder, MLP bridge with pixel shuffle,
966
+ * and Qwen3 language model for OCR and document understanding.
967
+ *
968
+ * All inference state lives on a dedicated OS thread. NAPI methods
969
+ * dispatch commands via channels and await responses.
970
+ */
971
+ export declare class QianfanOCRModel {
972
+ /**
973
+ * Create a new QianfanOCRModel from config (uninitialized, no weights).
974
+ *
975
+ * This constructor path does not spawn a model thread — the returned
976
+ * instance is only useful for config inspection. Call
977
+ * [`QianfanOCRModel::load`] to actually run inference.
978
+ */
979
+ constructor(config: QianfanOcrConfig);
980
+ /** Returns true if weights have been loaded via `load()`. */
981
+ get isInitialized(): boolean;
982
+ /**
983
+ * Load a QianfanOCRModel from a directory.
984
+ *
985
+ * Reads config.json, loads SafeTensors weights (single or sharded),
986
+ * builds vision encoder, bridge, and language model, and loads tokenizer.
987
+ * All heavy work runs on the dedicated model thread.
988
+ */
989
+ static load(modelPath: string): Promise<QianfanOCRModel>;
990
+ /**
991
+ * Generate text tokens given pre-tokenized input.
992
+ *
993
+ * Lower-level API — prefer the session chat methods
994
+ * (`chatSessionStart` / `chatSessionContinue` and their streaming
995
+ * variants) for typical usage.
996
+ */
997
+ generate(
998
+ inputIds: MxArray,
999
+ maxNewTokens?: number | undefined | null,
1000
+ temperature?: number | undefined | null,
1001
+ ): Promise<Array<number>>;
1002
+ /** Reset KV caches and token history. */
1003
+ resetCaches(): void;
1004
+ /**
1005
+ * Start a new chat session.
1006
+ *
1007
+ * Runs the full chat template once, decodes until `<|im_end|>`,
1008
+ * and leaves the KV caches on a clean turn boundary so subsequent
1009
+ * `chatSessionContinue` / `chatSessionContinueTool` calls can
1010
+ * append a raw ChatML delta on top without re-rendering the chat
1011
+ * template.
1012
+ *
1013
+ * Qianfan-OCR is always a VLM (InternViT + Qwen3 language model), so
1014
+ * this entry point accepts images in `messages` without the text-only
1015
+ * fast-fail used by plain language models.
1016
+ */
1017
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1018
+ /**
1019
+ * Continue an existing chat session with a new user message.
1020
+ *
1021
+ * Appends a raw ChatML user/assistant delta to the session's cached
1022
+ * KV state, then decodes the model reply. Stops on `<|im_end|>` so
1023
+ * the cache remains on a clean turn boundary for the next turn.
1024
+ *
1025
+ * Requires a live session started via `chatSessionStart`. Errors
1026
+ * if the session is empty or if `config.reuse_cache` is
1027
+ * explicitly set to `false`.
1028
+ *
1029
+ * `images` is an opt-in guard parameter: when non-empty the native
1030
+ * side returns an error whose message begins with
1031
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1032
+ * `ChatSession` layer can catch the prefix and route image-changes
1033
+ * back through a fresh `chatSessionStart` uniformly across all
1034
+ * model backends. Qianfan-OCR is a VLM but the continue path cannot
1035
+ * splice new vision features into a live KV cache — image changes
1036
+ * always require a fresh session start.
1037
+ */
1038
+ chatSessionContinue(
1039
+ userMessage: string,
1040
+ images: Uint8Array[] | null | undefined,
1041
+ config: ChatConfig | null | undefined,
1042
+ ): Promise<ChatResult>;
1043
+ /**
1044
+ * Continue an existing chat session with a tool-result turn.
1045
+ *
1046
+ * Builds a ChatML `<tool_response>` delta from `tool_call_id` and
1047
+ * `content` and prefills it on top of the live session caches, then
1048
+ * decodes the model reply. Stops on `<|im_end|>` so the cache stays
1049
+ * on a clean turn boundary for the next turn.
1050
+ *
1051
+ * Requires a live session started via `chatSessionStart`.
1052
+ */
1053
+ chatSessionContinueTool(
1054
+ toolCallId: string,
1055
+ content: string,
1056
+ config?: ChatConfig | undefined | null,
1057
+ ): Promise<ChatResult>;
1058
+ /** Streaming variant of `chatSessionStart`. */
1059
+ chatStreamSessionStart(
1060
+ messages: ChatMessage[],
1061
+ config: ChatConfig | null | undefined,
1062
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1063
+ ): Promise<ChatStreamHandle>;
1064
+ /** Streaming variant of `chatSessionContinue`. */
1065
+ chatStreamSessionContinue(
1066
+ userMessage: string,
1067
+ images: Uint8Array[] | null | undefined,
1068
+ config: ChatConfig | null | undefined,
1069
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1070
+ ): Promise<ChatStreamHandle>;
1071
+ /** Streaming variant of `chatSessionContinueTool`. */
1072
+ chatStreamSessionContinueTool(
1073
+ toolCallId: string,
1074
+ content: string,
1075
+ config: ChatConfig | null | undefined,
1076
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1077
+ ): Promise<ChatStreamHandle>;
1078
+ }
1079
+
661
1080
  /**
662
1081
  * Qwen3.5 Model -- hybrid linear/full attention with optional MoE.
663
1082
  *
664
- * Uses interior mutability (RwLock) for layers, final_norm, lm_head, and caches
665
- * to allow async generation via spawn_blocking without blocking the Node.js event loop.
666
- * This matches the pattern used by Qwen3Model.
1083
+ * All inference and training state lives on a dedicated OS thread. NAPI methods
1084
+ * dispatch commands via channels and await responses. Training commands are
1085
+ * routed through `TrainingDispatch` to the model thread.
667
1086
  */
668
1087
  export declare class Qwen35Model {
669
- /** Create a new Qwen3.5 model with the given configuration. */
670
- constructor(config: Qwen35Config);
671
1088
  /** Initialize caches for incremental generation. */
672
1089
  initCaches(): void;
673
1090
  /** Reset all caches. */
@@ -677,28 +1094,18 @@ export declare class Qwen35Model {
677
1094
  *
678
1095
  * The cache is moved out of the model — calling `takeCache()` twice
679
1096
  * returns `null` the second time. Pass the cache back via `setCache()`
680
- * before the next `chat()` call for incremental prefill.
1097
+ * before the next `chatSessionStart` / `chatSessionContinue` call for
1098
+ * incremental prefill.
681
1099
  */
682
1100
  takeCache(): PromptCache | null;
683
1101
  /**
684
1102
  * Restore a previously taken `PromptCache` into the model.
685
1103
  *
686
- * On the next `chat()` call with `reuseCache: true`, the model will
687
- * prefix-match the new tokens against the cache and only prefill the delta.
1104
+ * On the next `chatSessionStart` / `chatSessionContinue` call with
1105
+ * `reuseCache: true`, the model will prefix-match the new tokens against
1106
+ * the cache and only prefill the delta.
688
1107
  */
689
1108
  setCache(cache: PromptCache): void;
690
- /**
691
- * Forward pass through the model.
692
- *
693
- * # Arguments
694
- * * `input_ids` - Token IDs [B, T]
695
- *
696
- * # Returns
697
- * Logits [B, T, vocab_size]
698
- */
699
- forward(inputIds: MxArray): MxArray;
700
- /** Forward pass with cache for incremental generation. */
701
- forwardWithCache(inputIds: MxArray): MxArray;
702
1109
  /**
703
1110
  * Load a pretrained model from a directory.
704
1111
  *
@@ -708,44 +1115,131 @@ export declare class Qwen35Model {
708
1115
  * - tokenizer.json + tokenizer_config.json
709
1116
  */
710
1117
  static load(path: string): Promise<Qwen35Model>;
711
- /**
712
- * Generate text from a prompt token sequence.
713
- *
714
- * Runs generation on a worker thread via spawn_blocking to avoid
715
- * blocking the Node.js event loop.
716
- */
1118
+ /** Generate text from a prompt token sequence. */
717
1119
  generate(promptTokens: MxArray, config: Qwen35GenerationConfig): Promise<Qwen35GenerationResult>;
718
1120
  /**
719
- * Chat API with tool calling support.
1121
+ * Start a new chat session.
1122
+ *
1123
+ * Runs the full jinja chat template once and uses `<|im_end|>` as
1124
+ * its stop token so the cached KV state ends on a clean ChatML
1125
+ * boundary. Image support is conditional on the loaded
1126
+ * checkpoint: a Qwen3.5-VL dense model loaded with vision weights
1127
+ * accepts images in `messages` (the vision encoder handles
1128
+ * prefill), while a plain text Qwen3.5 checkpoint rejects them
1129
+ * with a runtime error. Subsequent turns in the same session MUST
1130
+ * go through `chatSessionContinue` so the caller appends raw
1131
+ * ChatML deltas on top of the live caches without rerunning the
1132
+ * jinja template; a mid-session image change requires a fresh
1133
+ * `chatSessionStart` call. The session is owned end-to-end by
1134
+ * the `chatSession*` surface.
1135
+ *
1136
+ * This method is the production entry point used by the TypeScript
1137
+ * `ChatSession` wrapper for turn 1 of a multi-round conversation.
1138
+ */
1139
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1140
+ /**
1141
+ * Continue an existing chat session with a new user message.
1142
+ *
1143
+ * Appends a raw ChatML user/assistant delta to the session's cached
1144
+ * KV state, then decodes the assistant reply. Stops on `<|im_end|>`
1145
+ * so the cache remains on a clean boundary for the next turn.
1146
+ *
1147
+ * Requires a live session started via `chatSessionStart`. Errors
1148
+ * if the session is empty, carries image state, or if
1149
+ * `config.reuse_cache` is explicitly set to `false`.
1150
+ *
1151
+ * `images` is an opt-in guard parameter: when non-empty, the native
1152
+ * side returns an error whose message begins with
1153
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1154
+ * `ChatSession` layer can catch the prefix and route image-changes
1155
+ * back through a fresh `chatSessionStart`.
1156
+ */
1157
+ chatSessionContinue(
1158
+ userMessage: string,
1159
+ images: Uint8Array[] | null | undefined,
1160
+ config: ChatConfig | null | undefined,
1161
+ ): Promise<ChatResult>;
1162
+ /**
1163
+ * Continue an existing chat session with a tool-result turn.
1164
+ *
1165
+ * Builds a ChatML `<tool_response>`-wrapped delta from `content` and
1166
+ * prefills it on top of the live session caches, then decodes the
1167
+ * assistant reply. Stops on `<|im_end|>` so the cache stays on a
1168
+ * clean boundary for the next turn.
1169
+ *
1170
+ * The `tool_call_id` is currently dropped by the wire format —
1171
+ * Qwen3.5's chat template identifies tool responses by position +
1172
+ * wrapper tags, not an explicit id. Callers may still log it for
1173
+ * their own bookkeeping.
1174
+ *
1175
+ * Requires a live session started via `chatSessionStart`.
1176
+ */
1177
+ chatSessionContinueTool(
1178
+ toolCallId: string,
1179
+ content: string,
1180
+ config?: ChatConfig | undefined | null,
1181
+ ): Promise<ChatResult>;
1182
+ /**
1183
+ * Streaming variant of `chatSessionStart`.
1184
+ *
1185
+ * Dispatches to the dedicated model thread. Behaviourally identical
1186
+ * to `chatSessionStart` (resets caches, uses `<|im_end|>` as
1187
+ * eos, inherits the same VLM-vs-text image-support contract) but
1188
+ * streams token deltas through the JS callback instead of
1189
+ * returning a `ChatResult`. Used by the TypeScript
1190
+ * `ChatSession.sendStream()` for turn 1 of a multi-round streaming
1191
+ * conversation.
1192
+ */
1193
+ chatStreamSessionStart(
1194
+ messages: ChatMessage[],
1195
+ config: ChatConfig | null,
1196
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1197
+ ): Promise<ChatStreamHandle>;
1198
+ /**
1199
+ * Streaming variant of `chatSessionContinue`.
1200
+ *
1201
+ * Appends a ChatML user/assistant delta on top of the live session
1202
+ * caches and streams the decoded reply. Requires a live session
1203
+ * started via `chatStreamSessionStart` (or the non-streaming
1204
+ * `chatSessionStart`). Used by the TypeScript
1205
+ * `ChatSession.sendStream()` for turns 2..N of a multi-round
1206
+ * streaming conversation.
720
1207
  *
721
- * Runs tokenization + generation on a worker thread via spawn_blocking
722
- * to avoid blocking the Node.js event loop.
1208
+ * `images` is an opt-in guard parameter: when non-empty, the
1209
+ * streaming path emits an error chunk whose message begins with
1210
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1211
+ * `ChatSession` layer can route image-changes through a fresh
1212
+ * session start.
723
1213
  */
724
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1214
+ chatStreamSessionContinue(
1215
+ userMessage: string,
1216
+ images: Uint8Array[] | null | undefined,
1217
+ config: ChatConfig | null,
1218
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1219
+ ): Promise<ChatStreamHandle>;
725
1220
  /**
726
- * Streaming chat API with tool calling support.
1221
+ * Streaming variant of `chatSessionContinueTool`.
727
1222
  *
728
- * Same as `chat()` but streams tokens one-by-one via the callback.
729
- * Returns a `ChatStreamHandle` immediately; generation runs in background.
730
- * Call `handle.cancel()` to abort generation early.
1223
+ * Builds a ChatML tool-response delta on top of the live session
1224
+ * caches and streams the decoded reply. Requires a live session
1225
+ * started via `chatSessionStart` / `chatStreamSessionStart`.
731
1226
  */
732
- chatStream(
733
- messages: ChatMessage[],
1227
+ chatStreamSessionContinueTool(
1228
+ toolCallId: string,
1229
+ content: string,
734
1230
  config: ChatConfig | null,
735
1231
  callback: (err: Error | null, chunk: ChatStreamChunk) => void,
736
1232
  ): Promise<ChatStreamHandle>;
737
- /** Get the number of parameters in the model. */
1233
+ /**
1234
+ * Get the number of parameters in the model.
1235
+ *
1236
+ * Pure config computation — no model-thread dispatch needed.
1237
+ */
738
1238
  numParameters(): number;
739
1239
  /**
740
1240
  * Save the model weights and configuration to a directory.
741
1241
  *
742
- * This saves:
743
- * - config.json: Model configuration (with model_type for detectModelType)
744
- * - weights.safetensors: Full model weights in SafeTensors format
745
- * - weights.mlx: Parameter metadata (for reference)
746
- *
747
- * # Arguments
748
- * * `save_path` - Directory to save the model
1242
+ * Dispatches to model thread.
749
1243
  */
750
1244
  saveModel(savePath: string): Promise<undefined>;
751
1245
  }
@@ -754,57 +1248,145 @@ export type Qwen3_5Model = Qwen35Model;
754
1248
  /**
755
1249
  * Qwen3.5 MoE Model -- hybrid linear/full attention with Mixture-of-Experts.
756
1250
  *
757
- * Supports C++ MoE forward path (non-compiled, builds fresh graph per step)
758
- * when weights are registered via `register_moe_weights_with_cpp`.
759
- * Falls back to Rust forward_inner path for test models without stored weights.
1251
+ * All inference and training state lives on a dedicated OS thread. NAPI methods
1252
+ * dispatch commands via channels and await responses. Training commands are
1253
+ * routed through `TrainingDispatch` to the model thread.
760
1254
  */
761
1255
  export declare class Qwen35MoeModel {
762
- constructor(config: Qwen35MoeConfig);
1256
+ /** Initialize caches for incremental generation. */
1257
+ initCaches(): void;
1258
+ /** Reset all caches. */
1259
+ resetCaches(): void;
1260
+ /** Take the KV cache from the model, returning a `PromptCache` handle. */
1261
+ takeCache(): PromptCache | null;
1262
+ /** Restore a previously taken `PromptCache` into the model. */
1263
+ setCache(cache: PromptCache): void;
1264
+ /** Load a pretrained model from a directory. */
1265
+ static load(path: string): Promise<Qwen35MoeModel>;
1266
+ /** Generate text from a prompt token sequence. */
1267
+ generate(promptTokens: MxArray, config: Qwen35MoeGenerationConfig): Promise<Qwen35MoeGenerationResult>;
763
1268
  /**
764
- * Take the KV cache from the model, returning a `PromptCache` handle.
1269
+ * Start a new chat session.
765
1270
  *
766
- * The cache is moved out of the model calling `takeCache()` twice
767
- * returns `null` the second time. Pass the cache back via `setCache()`
768
- * before the next `chat()` call for incremental prefill.
1271
+ * Runs the full jinja chat template once, decodes until `<|im_end|>`,
1272
+ * and leaves the KV caches on a clean ChatML boundary so subsequent
1273
+ * `chatSessionContinue` / `chatSessionContinueTool` calls can
1274
+ * append a raw delta on top without re-rendering the chat
1275
+ * template.
1276
+ *
1277
+ * Image support is conditional on the loaded checkpoint: a
1278
+ * Qwen3.5-VL MoE model loaded with vision weights accepts images
1279
+ * in `messages` (the vision encoder handles prefill), while a
1280
+ * plain text Qwen3.5 MoE checkpoint rejects them with a runtime
1281
+ * error. A mid-session image change requires a fresh
1282
+ * `chatSessionStart` call.
1283
+ *
1284
+ * Requires `config.reuse_cache` to be enabled (the default).
769
1285
  */
770
- takeCache(): PromptCache | null;
1286
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
771
1287
  /**
772
- * Restore a previously taken `PromptCache` into the model.
1288
+ * Continue an existing chat session with a new user message.
1289
+ *
1290
+ * Appends a raw ChatML user/assistant delta to the session's cached
1291
+ * KV state, then decodes the assistant reply. Stops on `<|im_end|>`
1292
+ * so the cache remains on a clean boundary for the next turn.
1293
+ *
1294
+ * Requires a live session started via `chatSessionStart`.
1295
+ * Errors if the session is empty, carries image state, or if
1296
+ * `config.reuse_cache` is explicitly set to `false`.
773
1297
  *
774
- * On the next `chat()` call with `reuseCache: true`, the model will
775
- * prefix-match the new tokens against the cache and only prefill the delta.
1298
+ * `images` is an opt-in guard parameter: when non-empty, the native
1299
+ * side returns an error whose message begins with
1300
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1301
+ * `ChatSession` layer can catch the prefix and route image-changes
1302
+ * back through a fresh `chatSessionStart`.
776
1303
  */
777
- setCache(cache: PromptCache): void;
778
- initCaches(): void;
779
- resetCaches(): void;
780
- forward(inputIds: MxArray): MxArray;
781
- forwardWithCache(inputIds: MxArray): MxArray;
782
- static load(path: string): Promise<Qwen35MoeModel>;
783
- generate(promptTokens: MxArray, config: Qwen35MoeGenerationConfig): Promise<Qwen35MoeGenerationResult>;
784
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1304
+ chatSessionContinue(
1305
+ userMessage: string,
1306
+ images: Uint8Array[] | null | undefined,
1307
+ config: ChatConfig | null | undefined,
1308
+ ): Promise<ChatResult>;
785
1309
  /**
786
- * Streaming chat API with tool calling support.
1310
+ * Continue an existing chat session with a tool-result turn.
1311
+ *
1312
+ * Builds a ChatML `<tool_response>`-wrapped delta from `content` and
1313
+ * prefills it on top of the live session caches, then decodes the
1314
+ * assistant reply. Stops on `<|im_end|>` so the cache stays on a
1315
+ * clean boundary for the next turn.
1316
+ *
1317
+ * The `tool_call_id` is currently dropped by the wire format —
1318
+ * Qwen3.5's chat template identifies tool responses by position +
1319
+ * wrapper tags, not an explicit id. Callers may still log it for
1320
+ * their own bookkeeping.
787
1321
  *
788
- * Same as `chat()` but streams tokens one-by-one via the callback.
789
- * Returns a `ChatStreamHandle` immediately; generation runs in background.
790
- * Call `handle.cancel()` to abort generation early.
1322
+ * Requires a live session started via `chatSessionStart`.
791
1323
  */
792
- chatStream(
1324
+ chatSessionContinueTool(
1325
+ toolCallId: string,
1326
+ content: string,
1327
+ config?: ChatConfig | undefined | null,
1328
+ ): Promise<ChatResult>;
1329
+ /**
1330
+ * Streaming variant of `chatSessionStart`.
1331
+ *
1332
+ * Dispatches to the dedicated model thread. Behaviourally identical
1333
+ * to `chatSessionStart` (resets caches, uses `<|im_end|>` as
1334
+ * eos, inherits the same VLM-vs-text image-support contract) but
1335
+ * streams token deltas through the JS callback instead of
1336
+ * returning a `ChatResult`. Used by the TypeScript
1337
+ * `ChatSession.sendStream()` for turn 1 of a multi-round streaming
1338
+ * conversation.
1339
+ */
1340
+ chatStreamSessionStart(
793
1341
  messages: ChatMessage[],
794
1342
  config: ChatConfig | null,
795
1343
  callback: (err: Error | null, chunk: ChatStreamChunk) => void,
796
1344
  ): Promise<ChatStreamHandle>;
1345
+ /**
1346
+ * Streaming variant of `chatSessionContinue`.
1347
+ *
1348
+ * Appends a ChatML user/assistant delta on top of the live session
1349
+ * caches and streams the decoded reply. Requires a live session
1350
+ * started via `chatStreamSessionStart` (or the non-streaming
1351
+ * `chatSessionStart`). Used by the TypeScript
1352
+ * `ChatSession.sendStream()` for turns 2..N of a multi-round
1353
+ * streaming conversation.
1354
+ *
1355
+ * `images` is an opt-in guard parameter: when non-empty, the
1356
+ * streaming path emits an error chunk whose message begins with
1357
+ * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the TypeScript
1358
+ * `ChatSession` layer can route image-changes through a fresh
1359
+ * session start.
1360
+ */
1361
+ chatStreamSessionContinue(
1362
+ userMessage: string,
1363
+ images: Uint8Array[] | null | undefined,
1364
+ config: ChatConfig | null,
1365
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1366
+ ): Promise<ChatStreamHandle>;
1367
+ /**
1368
+ * Streaming variant of `chatSessionContinueTool`.
1369
+ *
1370
+ * Builds a ChatML tool-response delta on top of the live session
1371
+ * caches and streams the decoded reply. Requires a live session
1372
+ * started via `chatSessionStart` / `chatStreamSessionStart`.
1373
+ */
1374
+ chatStreamSessionContinueTool(
1375
+ toolCallId: string,
1376
+ content: string,
1377
+ config: ChatConfig | null,
1378
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1379
+ ): Promise<ChatStreamHandle>;
1380
+ /**
1381
+ * Get the number of parameters in the model.
1382
+ *
1383
+ * Pure config computation -- no model-thread dispatch needed.
1384
+ */
797
1385
  numParameters(): number;
798
1386
  /**
799
1387
  * Save the model weights and configuration to a directory.
800
1388
  *
801
- * This saves:
802
- * - config.json: Model configuration (with model_type for detectModelType)
803
- * - weights.safetensors: Full model weights in SafeTensors format
804
- * - weights.mlx: Parameter metadata (for reference)
805
- *
806
- * # Arguments
807
- * * `save_path` - Directory to save the model
1389
+ * Dispatches to model thread.
808
1390
  */
809
1391
  saveModel(savePath: string): Promise<undefined>;
810
1392
  }
@@ -813,28 +1395,15 @@ export type Qwen3_5MoeModel = Qwen35MoeModel;
813
1395
  /**
814
1396
  * Qwen3 Model with automatic differentiation support
815
1397
  *
816
- * Uses interior mutability (RwLock) for layers, final_norm, and lm_head
817
- * to allow gradient application without deep cloning the model.
818
- * This eliminates the previous ~4GB memory overhead from clone_for_session().
1398
+ * Uses a dedicated model thread for inference and training commands.
1399
+ * Training commands are routed via `TrainingDispatch`.
819
1400
  */
820
1401
  export declare class Qwen3Model {
821
- /** Create a new Qwen3 model with the given configuration */
822
- constructor(config: Qwen3Config);
823
1402
  /**
824
- * Reset the KV cache used for cache reuse across chat() calls.
1403
+ * Reset the KV cache used for cache reuse across chat-session turns.
825
1404
  * Call this when starting a new conversation to ensure a full prefill.
826
1405
  */
827
1406
  resetCache(): void;
828
- /**
829
- * Forward pass through the model
830
- *
831
- * # Arguments
832
- * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
833
- *
834
- * # Returns
835
- * * Logits, shape: [batch_size, seq_len, vocab_size]
836
- */
837
- forward(inputIds: MxArray): MxArray;
838
1407
  /**
839
1408
  * Initialize KV caches for incremental generation
840
1409
  *
@@ -847,8 +1416,6 @@ export declare class Qwen3Model {
847
1416
  * Clears cached key-value states. Call this between different generation sequences.
848
1417
  */
849
1418
  resetKvCaches(): void;
850
- /** Check if paged attention is enabled for this model */
851
- hasPagedAttention(): boolean;
852
1419
  /**
853
1420
  * Get paged attention memory statistics (if enabled)
854
1421
  *
@@ -861,17 +1428,6 @@ export declare class Qwen3Model {
861
1428
  * Returns the number of waiting, running, and completed sequences.
862
1429
  */
863
1430
  schedulerStats(): SchedulerStatsNapi | null;
864
- /**
865
- * Forward pass with KV caching for incremental generation
866
- *
867
- * # Arguments
868
- * * `input_ids` - Token IDs, shape: [batch_size, seq_len]
869
- * * `use_cache` - Whether to use KV caching (must call init_kv_caches() first)
870
- *
871
- * # Returns
872
- * * Logits, shape: [batch_size, seq_len, vocab_size]
873
- */
874
- forwardWithCache(inputIds: MxArray, useCache: boolean): MxArray;
875
1431
  /**
876
1432
  * Forward pass with paged attention for memory-efficient inference.
877
1433
  *
@@ -957,205 +1513,6 @@ export declare class Qwen3Model {
957
1513
  hasPagedWork(): boolean;
958
1514
  /** Get model configuration */
959
1515
  getConfig(): Qwen3Config;
960
- /**
961
- * Generate tokens using speculative decoding with a draft model.
962
- *
963
- * Speculative decoding uses a smaller draft model to generate tokens speculatively,
964
- * then verifies them with the target model in a single forward pass. This can achieve
965
- * 2-3x speedup when the draft model has high acceptance rate.
966
- *
967
- * # Algorithm
968
- * 1. Draft model generates N tokens speculatively (cheap forward passes)
969
- * 2. Target model (self) verifies all N tokens in one forward pass
970
- * 3. Accept/reject using rejection sampling
971
- * 4. On rejection, resample from adjusted distribution
972
- * 5. Rewind caches and continue
973
- *
974
- * # Arguments
975
- * * `draft_model` - Smaller model for speculative generation (should share tokenizer)
976
- * * `input_ids` - Input token IDs [1, seq_len]
977
- * * `config` - Generation configuration (includes num_draft_tokens)
978
- *
979
- * # Returns
980
- * GenerationResult with tokens, logprobs, and speculative stats in finish_reason
981
- *
982
- * # Example (TypeScript)
983
- * ```typescript
984
- * const targetModel = await loadModel('qwen3-7b');
985
- * const draftModel = await loadModel('qwen3-0.5b');
986
- *
987
- * const result = targetModel.generateSpeculativeSync(draftModel, inputIds, {
988
- * numDraftTokens: 5,
989
- * maxNewTokens: 100,
990
- * temperature: 0.7,
991
- * });
992
- * ```
993
- */
994
- generateSpeculativeSync(
995
- draftModel: Qwen3Model,
996
- inputIds: MxArray,
997
- config?: GenerationConfig | undefined | null,
998
- ): GenerationResult;
999
- /** Count total number of parameters in the model */
1000
- numParameters(): number;
1001
- /**
1002
- * Get all model parameters as a dictionary mapping names to arrays
1003
- *
1004
- * This matches the TypeScript API for compatibility
1005
- */
1006
- getParameters(): Record<string, MxArray>;
1007
- /** Load parameters from a dictionary */
1008
- loadParameters(params: Record<string, MxArray>): void;
1009
- /**
1010
- * Compute forward pass and loss (for evaluation)
1011
- *
1012
- * # Arguments
1013
- * * `input_ids` - Input token IDs, shape: [batch_size, seq_len]
1014
- * * `labels` - Target token IDs, shape: [batch_size, seq_len]
1015
- *
1016
- * # Returns
1017
- * * Scalar loss value
1018
- */
1019
- computeLoss(inputIds: MxArray, labels: MxArray): MxArray;
1020
- /**
1021
- * Compute loss and gradients using a hybrid approach
1022
- *
1023
- * This implementation computes gradients for the output layers and uses
1024
- * numerical approximations for other parameters. This is sufficient to
1025
- * demonstrate that training works while we build out full MLX autograd integration.
1026
- *
1027
- * # Arguments
1028
- * * `input_ids` - Input token IDs, shape: [batch_size, seq_len]
1029
- * * `labels` - Target token IDs, shape: [batch_size, seq_len]
1030
- *
1031
- * # Returns
1032
- * * A tuple of (loss, gradients_dict) where gradients_dict maps parameter names to gradient arrays
1033
- *
1034
- * # Phase 6A Status
1035
- * Current implementation computes:
1036
- * - ✅ Exact gradients for LM head (output layer)
1037
- * - ⚠吅 Numerical approximations for other layers
1038
- *
1039
- * Future: Full MLX autograd will compute exact gradients for all 250+ parameters
1040
- */
1041
- computeLossAndGradients(inputIds: MxArray, labels: MxArray): [MxArray, Record<string, MxArray>];
1042
- /**
1043
- * Complete GRPO training step using MLX Autograd (RECOMMENDED)
1044
- *
1045
- * This method uses automatic differentiation to compute gradients, eliminating
1046
- * the need for manual backward pass implementation. This is the preferred approach.
1047
- *
1048
- * # Arguments
1049
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1050
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1051
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1052
- * * `rewards` - Reward scores for each completion [batch*G]
1053
- * * `group_size` - Number of completions per prompt (G)
1054
- * * `config` - GRPO loss configuration
1055
- * * `learning_rate` - Learning rate for parameter updates
1056
- *
1057
- * # Returns
1058
- * * Tuple of (loss_value, metrics_dict)
1059
- */
1060
- trainStepGrpoAutograd(
1061
- promptTokens: Array<MxArray>,
1062
- completionTokens: Array<MxArray>,
1063
- completionLogprobs: Array<MxArray>,
1064
- rewards: Float64Array,
1065
- groupSize: number,
1066
- config: GrpoLossConfig,
1067
- learningRate: number,
1068
- ): [number, Record<string, number>];
1069
- /**
1070
- * Compute gradients only without applying them (for gradient accumulation)
1071
- *
1072
- * This method computes GRPO loss and gradients but does NOT update parameters.
1073
- * Used for gradient accumulation where gradients are summed across multiple
1074
- * micro-batches before applying them.
1075
- *
1076
- * # Arguments
1077
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1078
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1079
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1080
- * * `rewards` - Reward scores for each completion [batch*G]
1081
- * * `group_size` - Number of completions per prompt (G)
1082
- * * `config` - GRPO loss configuration
1083
- *
1084
- * # Returns
1085
- * * Tuple of (loss_value, gradients_dict, metrics_dict)
1086
- */
1087
- computeGradientsOnlyGrpoAutograd(
1088
- promptTokens: Array<MxArray>,
1089
- completionTokens: Array<MxArray>,
1090
- completionLogprobs: Array<MxArray>,
1091
- rewards: Float64Array,
1092
- groupSize: number,
1093
- config: GrpoLossConfig,
1094
- ): [number, Record<string, MxArray>, Record<string, number>];
1095
- /**
1096
- * Accumulate gradients into existing gradient dictionary
1097
- *
1098
- * This is a helper method for gradient accumulation. It adds new_gradients
1099
- * to accumulated_gradients element-wise.
1100
- *
1101
- * # Arguments
1102
- * * `accumulated_gradients` - Existing accumulated gradients (will be modified in-place conceptually, but returns new dict)
1103
- * * `new_gradients` - New gradients to add
1104
- *
1105
- * # Returns
1106
- * * Updated gradient dictionary with accumulated values
1107
- */
1108
- static accumulateGradients(
1109
- accumulatedGradients: Record<string, MxArray>,
1110
- newGradients: Record<string, MxArray>,
1111
- ): Record<string, MxArray>;
1112
- /**
1113
- * Complete GRPO training step using manual gradients (Legacy)
1114
- *
1115
- * This method performs a full GRPO training iteration:
1116
- * 1. Takes completions (already generated) with their logprobs and rewards
1117
- * 2. Computes advantages
1118
- * 3. Computes GRPO loss and gradients
1119
- * 4. Updates model parameters
1120
- *
1121
- * NOTE: Use train_step_grpo_autograd instead for automatic differentiation.
1122
- *
1123
- * # Arguments
1124
- * * `prompt_tokens` - Prompt token sequences [batch_size, seq_len] (1D arrays)
1125
- * * `completion_tokens` - Completion sequences [batch*G, completion_len] (1D arrays)
1126
- * * `completion_logprobs` - Logprobs from generation [batch*G, completion_len] (1D arrays)
1127
- * * `rewards` - Reward scores for each completion [batch*G]
1128
- * * `group_size` - Number of completions per prompt (G)
1129
- * * `config` - GRPO loss configuration
1130
- * * `learning_rate` - Learning rate for parameter updates
1131
- *
1132
- * # Returns
1133
- * * Tuple of (loss_value, metrics_dict)
1134
- */
1135
- trainStepGrpo(
1136
- promptTokens: Array<MxArray>,
1137
- completionTokens: Array<MxArray>,
1138
- completionLogprobs: Array<MxArray>,
1139
- rewards: Float64Array,
1140
- groupSize: number,
1141
- config: GrpoLossConfig,
1142
- learningRate: number,
1143
- ): [number, Record<string, number>];
1144
- /**
1145
- * Apply gradients to model parameters
1146
- *
1147
- * # Arguments
1148
- * * `gradients` - Dictionary mapping parameter names to gradient arrays
1149
- * * `learning_rate` - Learning rate for gradient descent
1150
- *
1151
- * This performs a simple SGD update: param = param - lr * grad
1152
- * Only updates parameters that have gradients; others remain unchanged.
1153
- *
1154
- * IMPORTANT: This function preserves the original dtype of parameters.
1155
- * The learning rate scalar is cast to match param dtype to prevent
1156
- * promotion to float32 during arithmetic operations.
1157
- */
1158
- applyGradients(gradients: Record<string, MxArray>, learningRate: number): void;
1159
1516
  /**
1160
1517
  * Text-to-text generation with integrated tokenization
1161
1518
  *
@@ -1188,76 +1545,82 @@ export declare class Qwen3Model {
1188
1545
  */
1189
1546
  generate(messages: Array<ChatMessage>, config?: GenerationConfig | undefined | null): Promise<GenerationResult>;
1190
1547
  /**
1191
- * High-level chat API with structured response parsing
1192
- *
1193
- * The primary API for conversational AI. Handles:
1194
- * - Chat message formatting with Jinja2 templates
1195
- * - Tool/function calling with structured output
1196
- * - Thinking extraction from `<think>` tags
1197
- * - Clean response text with all special tags stripped
1198
- *
1199
- * ## `chat()` vs `generate()`
1200
- *
1201
- * | Feature | `chat()` | `generate()` |
1202
- * |---------|----------|--------------|
1203
- * | **Purpose** | Conversational AI with tools | Raw text generation |
1204
- * | **Input** | Chat messages | Token IDs (MxArray) |
1205
- * | **Tool Support** | Built-in parsing | None |
1206
- * | **Thinking** | Extracts `<think>` content | Raw text only |
1207
- * | **Output** | Structured `ChatResult` | Basic `GenerationResult` |
1208
- * | **Use Case** | Chat apps, agents, assistants | Training, low-level control |
1209
- *
1210
- * ## When to use `chat()`
1211
- * - Building conversational applications
1212
- * - Need tool/function calling
1213
- * - Want structured responses with thinking separated
1214
- * - Working with chat message format
1215
- *
1216
- * ## When to use `generate()`
1217
- * - Training and fine-tuning (need raw logprobs)
1218
- * - Custom tokenization pipeline
1219
- * - Low-level generation control
1220
- * - Non-chat use cases
1548
+ * Reset all caches and clear cached token history. Exposed so
1549
+ * tests and session-management code can start from a known clean
1550
+ * state between turns.
1551
+ */
1552
+ resetCaches(): void;
1553
+ /**
1554
+ * Start a new chat session.
1221
1555
  *
1222
- * # Arguments
1223
- * * `messages` - Array of chat messages (user/assistant/system roles)
1224
- * * `config` - Chat configuration including optional tools and generation params
1556
+ * Runs the full jinja chat template once, decodes until `<|im_end|>`,
1557
+ * and leaves the KV caches on a clean ChatML boundary so subsequent
1558
+ * `chatSessionContinue` / `chatSessionContinueTool` calls can
1559
+ * append a raw delta on top without re-rendering the chat
1560
+ * template.
1225
1561
  *
1226
- * # Returns
1227
- * * `ChatResult` containing:
1228
- * - `text`: Clean response (tool_call and think tags stripped)
1229
- * - `thinking`: Extracted chain-of-thought reasoning (or null)
1230
- * - `toolCalls`: Parsed tool calls with native JS object arguments
1231
- * - `finishReason`: "stop" | "length" | "tool_calls"
1232
- * - `rawText`: Original text before processing (for debugging)
1562
+ * Requires `config.reuse_cache` to be enabled (the default).
1563
+ */
1564
+ chatSessionStart(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1565
+ /**
1566
+ * Continue an existing chat session with a new user message.
1233
1567
  *
1234
- * # Example
1235
- * ```typescript
1236
- * // Simple chat
1237
- * const result = await model.chat(messages);
1238
- * console.log(result.text);
1568
+ * Appends a raw ChatML user/assistant delta to the session's
1569
+ * cached KV state, then decodes the assistant reply. Stops on
1570
+ * `<|im_end|>` so the cache remains on a clean boundary for the
1571
+ * next turn.
1239
1572
  *
1240
- * // With tools
1241
- * const result = await model.chat(messages, {
1242
- * tools: [{ type: 'function', function: { name: 'get_weather' } }],
1243
- * maxNewTokens: 2048,
1244
- * temperature: 0.7,
1245
- * });
1573
+ * Requires a live session started via `chatSessionStart`. Errors
1574
+ * if the session is empty, carries image state, or if
1575
+ * `config.reuse_cache` is explicitly set to `false`.
1246
1576
  *
1247
- * // Handle tool calls
1248
- * for (const call of result.toolCalls) {
1249
- * if (call.status === 'ok') {
1250
- * console.log(call.name, call.arguments); // Arguments is a JS object!
1251
- * }
1252
- * }
1253
- *
1254
- * // Access thinking (chain-of-thought)
1255
- * if (result.thinking) {
1256
- * console.log('Model reasoning:', result.thinking);
1257
- * }
1258
- * ```
1577
+ * Qwen3 legacy is text-only; `images` is an opt-in guard parameter:
1578
+ * when non-empty the native side returns an error whose message
1579
+ * begins with `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` so the
1580
+ * TypeScript `ChatSession` layer can catch the prefix and route
1581
+ * image-changes back through a fresh `chatSessionStart` uniformly
1582
+ * across all model backends.
1259
1583
  */
1260
- chat(messages: Array<ChatMessage>, config?: ChatConfig | undefined | null): Promise<ChatResult>;
1584
+ chatSessionContinue(
1585
+ userMessage: string,
1586
+ images: Uint8Array[] | null | undefined,
1587
+ config: ChatConfig | null | undefined,
1588
+ ): Promise<ChatResult>;
1589
+ /**
1590
+ * Continue an existing chat session with a tool-result turn.
1591
+ *
1592
+ * Builds a Qwen3.5-style `<tool_response>`-wrapped user-role delta
1593
+ * from `content` and prefills it on top of the live session
1594
+ * caches, then decodes the assistant reply. Stops on `<|im_end|>`
1595
+ * so the cache stays on a clean boundary for the next turn.
1596
+ *
1597
+ * Requires a live session started via `chatSessionStart`.
1598
+ */
1599
+ chatSessionContinueTool(
1600
+ toolCallId: string,
1601
+ content: string,
1602
+ config?: ChatConfig | undefined | null,
1603
+ ): Promise<ChatResult>;
1604
+ /** Streaming variant of `chatSessionStart`. */
1605
+ chatStreamSessionStart(
1606
+ messages: ChatMessage[],
1607
+ config: ChatConfig | null,
1608
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1609
+ ): Promise<ChatStreamHandle>;
1610
+ /** Streaming variant of `chatSessionContinue`. */
1611
+ chatStreamSessionContinue(
1612
+ userMessage: string,
1613
+ images: Uint8Array[] | null | undefined,
1614
+ config: ChatConfig | null,
1615
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1616
+ ): Promise<ChatStreamHandle>;
1617
+ /** Streaming variant of `chatSessionContinueTool`. */
1618
+ chatStreamSessionContinueTool(
1619
+ toolCallId: string,
1620
+ content: string,
1621
+ config: ChatConfig | null,
1622
+ callback: (err: Error | null, chunk: ChatStreamChunk) => void,
1623
+ ): Promise<ChatStreamHandle>;
1261
1624
  /**
1262
1625
  * Generate multiple completions for multiple prompts in batch
1263
1626
  *
@@ -1355,6 +1718,10 @@ export declare class Qwen3Model {
1355
1718
  * - weights.safetensors: Full model weights in SafeTensors format
1356
1719
  * - weights.mlx: Parameter metadata (for reference)
1357
1720
  *
1721
+ * Dispatches to the dedicated model thread — all MxArray reads must
1722
+ * happen on the thread that owns them to avoid the MLX cross-thread
1723
+ * `CommandEncoder` crash.
1724
+ *
1358
1725
  * # Arguments
1359
1726
  * * `save_path` - Directory to save the model
1360
1727
  */
@@ -1507,7 +1874,33 @@ export declare class Qwen3Tokenizer {
1507
1874
  getEndoftextToken(): string;
1508
1875
  }
1509
1876
 
1510
- /** SFT Training Engine */
1877
+ /**
1878
+ * Response store for OpenAI Responses API persistence.
1879
+ *
1880
+ * Stores responses in SQLite to support `previous_response_id`
1881
+ * for multi-turn conversation state.
1882
+ */
1883
+ export declare class ResponseStore {
1884
+ /** Open (or create) a response store at the given path. */
1885
+ static open(path: string): Promise<ResponseStore>;
1886
+ /** Store a response. */
1887
+ store(response: StoredResponseRecord): Promise<void>;
1888
+ /** Get a single response by ID. */
1889
+ get(id: string): Promise<StoredResponseRecord | null>;
1890
+ /** Get the full conversation chain for a response (oldest first). */
1891
+ getChain(id: string): Promise<Array<StoredResponseRecord>>;
1892
+ /** Delete a response by ID. Returns true if a row was deleted. */
1893
+ delete(id: string): Promise<boolean>;
1894
+ /** Delete expired responses. Returns the number of rows deleted. */
1895
+ cleanupExpired(): Promise<number>;
1896
+ }
1897
+
1898
+ /**
1899
+ * SFT Training Engine
1900
+ *
1901
+ * Thin coordinator that routes all MLX operations through the model thread.
1902
+ * No MxArrays or model state live here - only plain data crosses the boundary.
1903
+ */
1511
1904
  export declare class SftTrainingEngine {
1512
1905
  /** Create a new SFT training engine from a Qwen3 model */
1513
1906
  constructor(model: Qwen3Model, config: SftEngineConfig);
@@ -1524,9 +1917,10 @@ export declare class SftTrainingEngine {
1524
1917
  /**
1525
1918
  * Flush any accumulated gradients at epoch end
1526
1919
  *
1527
- * When stepsPerEpoch % gradient_accumulation_steps != 0, there may be
1528
- * leftover gradients from the final micro-batches. This method applies
1529
- * them with proper averaging, matching TRL behavior.
1920
+ * With the model thread architecture, gradient accumulation is handled
1921
+ * on the model thread. This method is kept for API compatibility but
1922
+ * currently logs a warning. Partial accumulation at epoch boundaries
1923
+ * will be handled in a future update.
1530
1924
  */
1531
1925
  flushGradients(): boolean;
1532
1926
  /**
@@ -1549,15 +1943,46 @@ export declare class SftTrainingEngine {
1549
1943
  startEpoch(epoch: number): void;
1550
1944
  /** End current epoch and return metrics */
1551
1945
  endEpoch(epochTimeSecs: number): SftEpochMetrics;
1552
- /** Reset training state (for new training run) */
1946
+ /**
1947
+ * Reset training state (for new training run)
1948
+ *
1949
+ * This is a TERMINAL operation on this handle. It drops the training
1950
+ * state (optimizer, step counter) on the model thread so a fresh
1951
+ * `SftTrainingEngine` can be constructed on the same model, and marks
1952
+ * THIS handle as invalidated. Any subsequent dispatch-requiring method
1953
+ * on this handle returns an error — callers must construct a new
1954
+ * engine to continue training.
1955
+ */
1553
1956
  reset(): void;
1554
- /** Restore training state (for resuming from checkpoint) */
1957
+ /**
1958
+ * Restore training state (for resuming from checkpoint)
1959
+ *
1960
+ * Updates both the engine's read-through cache and the model thread's
1961
+ * authoritative `ts.step`. Does NOT touch optimizer state — that is
1962
+ * loaded via `loadOptimizerState`, which restores the AdamW bias-
1963
+ * correction step separately.
1964
+ */
1555
1965
  restoreState(step: number, epoch: number): void;
1556
- /** Get the underlying Qwen3 model for checkpointing */
1966
+ /**
1967
+ * Get the underlying Qwen3 model for checkpointing
1968
+ *
1969
+ * NOTE: With the model thread architecture, direct model access is no longer
1970
+ * supported. Use save_checkpoint() on the model directly instead.
1971
+ */
1557
1972
  getModel(): Qwen3Model;
1558
- /** Get the underlying Qwen3.5 dense model for checkpointing */
1973
+ /**
1974
+ * Get the underlying Qwen3.5 dense model for checkpointing
1975
+ *
1976
+ * NOTE: With the model thread architecture, direct model access is no longer
1977
+ * supported. Use save_checkpoint() on the model directly instead.
1978
+ */
1559
1979
  getQwen35Model(): Qwen35Model;
1560
- /** Get the underlying Qwen3.5 MoE model for checkpointing */
1980
+ /**
1981
+ * Get the underlying Qwen3.5 MoE model for checkpointing
1982
+ *
1983
+ * NOTE: With the model thread architecture, direct model access is no longer
1984
+ * supported. Use save_checkpoint() on the model directly instead.
1985
+ */
1561
1986
  getQwen35MoeModel(): Qwen35MoeModel;
1562
1987
  }
1563
1988
 
@@ -1730,9 +2155,17 @@ export type VLMChatResult = VlmChatResult;
1730
2155
  *
1731
2156
  * A generic VLM for OCR and document understanding tasks.
1732
2157
  * Currently supports PaddleOCR-VL architecture (vision encoder + ERNIE language model).
2158
+ *
2159
+ * All model state lives on a dedicated OS thread. NAPI methods dispatch
2160
+ * commands via channels and await responses.
1733
2161
  */
1734
2162
  export declare class VLModel {
1735
- /** Create a new PaddleOCR-VL model */
2163
+ /**
2164
+ * Create a new PaddleOCR-VL model (empty, not loaded).
2165
+ *
2166
+ * Creates a model thread with an empty inner. Use `VLModel.load()` instead
2167
+ * for loading a model from disk.
2168
+ */
1736
2169
  constructor(config: ModelConfig);
1737
2170
  /** Set the tokenizer */
1738
2171
  setTokenizer(tokenizer: Qwen3Tokenizer): void;
@@ -1815,9 +2248,7 @@ export declare class VLModel {
1815
2248
  /**
1816
2249
  * Generate text tokens given input tokens and optional image
1817
2250
  *
1818
- * Uses KV caching for efficient generation - each step only processes the
1819
- * new token(s) while reusing cached key-value states from previous tokens.
1820
- * Vision features are computed once at the start and cached.
2251
+ * Uses KV caching for efficient generation.
1821
2252
  *
1822
2253
  * # Arguments
1823
2254
  * * `input_ids` - Input token IDs [1, seq_len]
@@ -1972,6 +2403,20 @@ export interface ChatConfig {
1972
2403
  repetitionPenalty?: number | undefined;
1973
2404
  /** Size of the context window for repetition penalty (default: 256) */
1974
2405
  repetitionContextSize?: number | undefined;
2406
+ /**
2407
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
2408
+ * token that appeared at least once in context. Matches OpenAI API semantics.
2409
+ */
2410
+ presencePenalty?: number | undefined;
2411
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
2412
+ presenceContextSize?: number | undefined;
2413
+ /**
2414
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
2415
+ * logits of each token in context. Matches OpenAI API semantics.
2416
+ */
2417
+ frequencyPenalty?: number | undefined;
2418
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
2419
+ frequencyContextSize?: number | undefined;
1975
2420
  /** Max consecutive identical tokens before stopping (default: 16, 0 = disabled) */
1976
2421
  maxConsecutiveTokens?: number | undefined;
1977
2422
  /** Max n-gram repetitions before stopping (default: 3, 0 = disabled) */
@@ -1980,18 +2425,33 @@ export interface ChatConfig {
1980
2425
  ngramSize?: number | undefined;
1981
2426
  tools?: Array<ToolDefinition>;
1982
2427
  /**
1983
- * Enable thinking mode (Qwen3's <think> tags). Default: true (model thinks naturally).
1984
- * Set to false to suppress thinking by injecting empty <think></think> tags.
2428
+ * Reasoning effort level. Controls whether the model thinks before answering.
2429
+ * - "none" / "low": thinking disabled (template injects closed think block).
2430
+ * "none" also sets includeReasoning to false by default.
2431
+ * - "medium" / "high": thinking enabled (default behavior).
2432
+ * - Not set: thinking enabled (model thinks naturally).
1985
2433
  */
1986
- enableThinking?: boolean | undefined;
2434
+ reasoningEffort?: string | undefined;
2435
+ /**
2436
+ * Maximum number of thinking tokens before forcing </think>.
2437
+ * When the model has generated this many tokens while in thinking mode,
2438
+ * the next token is forced to be the think_end token. None = unlimited.
2439
+ */
2440
+ thinkingTokenBudget?: number | undefined;
2441
+ /**
2442
+ * Whether to include reasoning/thinking content in the output.
2443
+ * When false, the `thinking` field of ChatResult/ChatStreamChunk will always be None.
2444
+ * Default: true (false when reasoningEffort is "none").
2445
+ */
2446
+ includeReasoning?: boolean | undefined;
1987
2447
  /** When true, include performance metrics (TTFT, prefill tok/s, decode tok/s) in the result */
1988
2448
  reportPerformance?: boolean | undefined;
1989
2449
  /**
1990
- * Reuse KV cache across chat() calls for incremental prefill. Default: true.
2450
+ * Reuse KV cache across chat-session turns for incremental prefill. Default: true.
1991
2451
  * When true, the model preserves its KV cache after generation. On the next
1992
- * chat() call, it prefix-matches the new token sequence against the cached
1993
- * tokens and only prefills the delta — avoiding redundant computation for
1994
- * multi-turn conversations.
2452
+ * `chatSessionStart` / `chatSessionContinue` call, it prefix-matches the new
2453
+ * token sequence against the cached tokens and only prefills the delta —
2454
+ * avoiding redundant computation for multi-turn conversations.
1995
2455
  */
1996
2456
  reuseCache?: boolean | undefined;
1997
2457
  }
@@ -2018,6 +2478,8 @@ export interface ChatResult {
2018
2478
  toolCalls: Array<ToolCallResult>;
2019
2479
  thinking?: string;
2020
2480
  numTokens: number;
2481
+ promptTokens: number;
2482
+ reasoningTokens: number;
2021
2483
  finishReason: string;
2022
2484
  rawText: string;
2023
2485
  /** Performance metrics (present when `reportPerformance: true` in config) */
@@ -2044,9 +2506,17 @@ export interface ChatStreamChunk {
2044
2506
  toolCalls?: Array<ToolCallResult>;
2045
2507
  thinking?: string;
2046
2508
  numTokens?: number;
2509
+ promptTokens?: number;
2510
+ reasoningTokens?: number;
2047
2511
  rawText?: string;
2048
2512
  /** Performance metrics (only present in the final chunk when `reportPerformance: true`) */
2049
2513
  performance?: PerformanceMetrics;
2514
+ /**
2515
+ * Whether this delta chunk contains reasoning/thinking content.
2516
+ * true = reasoning (inside <think>...</think>), false = content (after </think>).
2517
+ * Only present on intermediate (non-final) chunks.
2518
+ */
2519
+ isReasoning?: boolean | undefined;
2050
2520
  }
2051
2521
 
2052
2522
  /** Result from classify_and_rotate: orientation info + corrected image bytes. */
@@ -2174,6 +2644,45 @@ export declare function convertParquetToJsonl(inputPath: string, outputPath: str
2174
2644
  /** Create a default PaddleOCR-VL 1.5 configuration (JS factory function) */
2175
2645
  export declare function createPaddleocrVlConfig(): ModelConfig;
2176
2646
 
2647
+ /** Create a default Qianfan-OCR configuration (JS factory function) */
2648
+ export declare function createQianfanOcrConfig(): QianfanOcrConfig;
2649
+
2650
+ /**
2651
+ * Create a random-init Qwen3.5 model and save it to disk.
2652
+ *
2653
+ * Spawns a dedicated `ModelThread<Qwen35Cmd>` whose init builds a fresh
2654
+ * random-weight `Qwen35Inner` directly, then dispatches `Qwen35Cmd::SaveModel`
2655
+ * on that thread. The thread is dropped at the end of the promise, so the
2656
+ * in-memory model is released once the checkpoint has been written. Used by
2657
+ * TypeScript test fixtures that need an on-disk checkpoint without keeping a
2658
+ * NAPI model instance alive.
2659
+ */
2660
+ export declare function createRandomQwen35Checkpoint(config: Qwen35Config, savePath: string): Promise<undefined>;
2661
+
2662
+ /**
2663
+ * Create a random-init Qwen3.5 MoE model and save it to disk.
2664
+ *
2665
+ * Spawns a dedicated `ModelThread<Qwen35MoeCmd>` whose init builds a fresh
2666
+ * random-weight `Qwen35MoeInner` directly, then dispatches
2667
+ * `Qwen35MoeCmd::SaveModel` on that thread. The thread is dropped at the end
2668
+ * of the promise, so the in-memory model is released once the checkpoint has
2669
+ * been written. Used by TypeScript test fixtures that need an on-disk
2670
+ * checkpoint without keeping a NAPI model instance alive.
2671
+ */
2672
+ export declare function createRandomQwen35MoeCheckpoint(config: Qwen35MoeConfig, savePath: string): Promise<undefined>;
2673
+
2674
+ /**
2675
+ * Create a random-init Qwen3 model and save it to disk.
2676
+ *
2677
+ * Spawns a dedicated `ModelThread<Qwen3Cmd>` whose init builds a fresh
2678
+ * random-weight `Qwen3Inner` directly, then dispatches `Qwen3Cmd::SaveModel`
2679
+ * on that thread. The thread is dropped at the end of the promise, so the
2680
+ * in-memory model is released once the checkpoint has been written. Used by
2681
+ * TypeScript test fixtures that need an on-disk checkpoint without keeping a
2682
+ * NAPI model instance alive.
2683
+ */
2684
+ export declare function createRandomQwen3Checkpoint(config: Qwen3Config, savePath: string): Promise<undefined>;
2685
+
2177
2686
  /** Document element - either a table or paragraph */
2178
2687
  export interface DocumentElement {
2179
2688
  elementType: ElementType;
@@ -2300,6 +2809,86 @@ export interface FunctionParameters {
2300
2809
  required?: Array<string>;
2301
2810
  }
2302
2811
 
2812
+ /**
2813
+ * Gemma 4 model configuration (dense variant).
2814
+ *
2815
+ * Supports E2B (2.3B), E4B (4.5B), and 31B dense models.
2816
+ * For MoE models (26B-A4B), use `Gemma4MoeConfig` from `gemma4_moe`.
2817
+ */
2818
+ export interface Gemma4Config {
2819
+ vocabSize: number;
2820
+ hiddenSize: number;
2821
+ numHiddenLayers: number;
2822
+ numAttentionHeads: number;
2823
+ numKeyValueHeads: number;
2824
+ headDim: number;
2825
+ intermediateSize: number;
2826
+ rmsNormEps: number;
2827
+ tieWordEmbeddings: boolean;
2828
+ maxPositionEmbeddings: number;
2829
+ slidingWindow: number;
2830
+ /**
2831
+ * Explicit per-layer attention type: "sliding_attention" or "full_attention".
2832
+ * Parsed from `text_config.layer_types` in the HuggingFace config.
2833
+ */
2834
+ layerTypes: Array<string>;
2835
+ /** RoPE theta for global (full) attention layers. */
2836
+ ropeTheta: number;
2837
+ /** RoPE theta for sliding (local) attention layers. */
2838
+ ropeLocalBaseFreq: number;
2839
+ /** Fraction of head_dim to rotate for global attention (0.25 = 25%). */
2840
+ partialRotaryFactor: number;
2841
+ /** KV heads for global layers. If None, uses num_key_value_heads. */
2842
+ globalNumKeyValueHeads?: number;
2843
+ /** Head dimension for global layers. If None, uses head_dim. */
2844
+ globalHeadDim?: number;
2845
+ attentionKEqV: boolean;
2846
+ finalLogitSoftcapping?: number;
2847
+ perLayerInputEmbeds: boolean;
2848
+ hiddenSizePerLayerInput?: number;
2849
+ vocabSizePerLayerInput?: number;
2850
+ padTokenId: number;
2851
+ eosTokenIds: Array<number>;
2852
+ bosTokenId: number;
2853
+ attentionBias: boolean;
2854
+ useDoubleWideMlp: boolean;
2855
+ numKvSharedLayers?: number;
2856
+ defaultTemperature?: number;
2857
+ defaultTopK?: number;
2858
+ defaultTopP?: number;
2859
+ enableMoeBlock: boolean;
2860
+ numExperts?: number;
2861
+ topKExperts?: number;
2862
+ moeIntermediateSize?: number;
2863
+ visionConfig?: Gemma4VisionConfig;
2864
+ imageTokenId?: number;
2865
+ boiTokenId?: number;
2866
+ eoiTokenId?: number;
2867
+ visionSoftTokensPerImage?: number;
2868
+ }
2869
+
2870
+ /**
2871
+ * Vision encoder configuration for Gemma4 multimodal models.
2872
+ *
2873
+ * Parsed from the `vision_config` sub-dict in config.json.
2874
+ */
2875
+ export interface Gemma4VisionConfig {
2876
+ hiddenSize: number;
2877
+ intermediateSize: number;
2878
+ numHiddenLayers: number;
2879
+ numAttentionHeads: number;
2880
+ numKeyValueHeads: number;
2881
+ headDim: number;
2882
+ rmsNormEps: number;
2883
+ patchSize: number;
2884
+ positionEmbeddingSize: number;
2885
+ defaultOutputLength: number;
2886
+ poolingKernelSize: number;
2887
+ useClippedLinears: boolean;
2888
+ ropeTheta: number;
2889
+ standardize: boolean;
2890
+ }
2891
+
2303
2892
  /** Result from generate_batch_for_training with all data needed for training */
2304
2893
  export interface GenerateBatchResult {
2305
2894
  /** Generated completion texts */
@@ -2333,6 +2922,20 @@ export interface GenerationConfig {
2333
2922
  * Matches mlx-lm default. Larger values catch longer patterns but use more memory
2334
2923
  */
2335
2924
  repetitionContextSize?: number;
2925
+ /**
2926
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
2927
+ * token that appeared at least once in context. Matches OpenAI API semantics.
2928
+ */
2929
+ presencePenalty?: number;
2930
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
2931
+ presenceContextSize?: number;
2932
+ /**
2933
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
2934
+ * logits of each token in context. Matches OpenAI API semantics.
2935
+ */
2936
+ frequencyPenalty?: number;
2937
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
2938
+ frequencyContextSize?: number;
2336
2939
  /**
2337
2940
  * Stop if same token repeats this many times consecutively (default: 16)
2338
2941
  * Set to 0 to disable. Prevents OOM from degenerate repetitive generation.
@@ -2526,6 +3129,16 @@ export interface GrpoEngineConfig {
2526
3129
  topK?: number;
2527
3130
  /** Repetition penalty (default: 1.1) */
2528
3131
  repetitionPenalty?: number;
3132
+ /**
3133
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
3134
+ * token that appeared at least once in context.
3135
+ */
3136
+ presencePenalty?: number;
3137
+ /**
3138
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
3139
+ * logits of each token in context.
3140
+ */
3141
+ frequencyPenalty?: number;
2529
3142
  /**
2530
3143
  * Maximum allowed NaN gradient occurrences before stopping training (default: 100)
2531
3144
  * When exceeded, training will stop with an error to prevent model corruption.
@@ -2659,6 +3272,46 @@ export interface GrpoLossConfig {
2659
3272
  vocabChunkSize?: number;
2660
3273
  }
2661
3274
 
3275
+ /**
3276
+ * Configuration for Harrier embedding model (Qwen3 backbone).
3277
+ *
3278
+ * Only includes backbone dimensions needed for encoding.
3279
+ * No generation fields (token IDs, paged attention, etc.).
3280
+ */
3281
+ export interface HarrierConfig {
3282
+ hiddenSize: number;
3283
+ numLayers: number;
3284
+ numHeads: number;
3285
+ numKeyValueHeads: number;
3286
+ intermediateSize: number;
3287
+ rmsNormEps: number;
3288
+ ropeTheta: number;
3289
+ maxPositionEmbeddings: number;
3290
+ headDim: number;
3291
+ /**
3292
+ * Qwen3 always uses QK normalization. Omit to use the default (true).
3293
+ * Explicitly passing false is allowed but produces a model incompatible
3294
+ * with published Harrier weights.
3295
+ */
3296
+ useQkNorm?: boolean;
3297
+ vocabSize: number;
3298
+ }
3299
+
3300
+ /** InternViT vision encoder configuration */
3301
+ export interface InternVisionConfig {
3302
+ hiddenSize: number;
3303
+ intermediateSize: number;
3304
+ numHiddenLayers: number;
3305
+ numAttentionHeads: number;
3306
+ numChannels: number;
3307
+ imageSize: number;
3308
+ patchSize: number;
3309
+ layerNormEps: number;
3310
+ qkvBias: boolean;
3311
+ /** Drop path rate (inference only, always 0) */
3312
+ dropPathRate: number;
3313
+ }
3314
+
2662
3315
  /** Check whether profiling is currently enabled. */
2663
3316
  export declare function isProfilingEnabled(): boolean;
2664
3317
 
@@ -2676,6 +3329,35 @@ export interface LayoutElement {
2676
3329
  order: number;
2677
3330
  }
2678
3331
 
3332
+ /**
3333
+ * LFM2 model configuration.
3334
+ *
3335
+ * Supports LiquidAI's LFM2.5 hybrid conv+attention architecture.
3336
+ * 16 layers total: 10 conv + 6 full_attention, defined by `layer_types` array.
3337
+ */
3338
+ export interface Lfm2Config {
3339
+ vocabSize: number;
3340
+ hiddenSize: number;
3341
+ numHiddenLayers: number;
3342
+ numAttentionHeads: number;
3343
+ numKeyValueHeads: number;
3344
+ maxPositionEmbeddings: number;
3345
+ normEps: number;
3346
+ convBias: boolean;
3347
+ convLCache: number;
3348
+ blockDim: number;
3349
+ blockFfDim: number;
3350
+ blockMultipleOf: number;
3351
+ blockFfnDimMultiplier: number;
3352
+ blockAutoAdjustFfDim: boolean;
3353
+ ropeTheta: number;
3354
+ layerTypes: Array<string>;
3355
+ tieEmbedding: boolean;
3356
+ eosTokenId: number;
3357
+ bosTokenId: number;
3358
+ padTokenId: number;
3359
+ }
3360
+
2679
3361
  export interface MemorySnapshot {
2680
3362
  /** Active (non-cached) memory in bytes. */
2681
3363
  activeBytes: number;
@@ -2896,6 +3578,29 @@ export interface ProfilingSummary {
2896
3578
  avgPrefillMs: number;
2897
3579
  }
2898
3580
 
3581
+ /** Full Qianfan-OCR model configuration */
3582
+ export interface QianfanOcrConfig {
3583
+ visionConfig: InternVisionConfig;
3584
+ llmConfig: Qwen3LmConfig;
3585
+ modelType: string;
3586
+ imgContextTokenId: number;
3587
+ /** `<img>` token ID */
3588
+ imgStartTokenId: number;
3589
+ /** `</img>` token ID */
3590
+ imgEndTokenId: number;
3591
+ /** `<|im_end|>` token ID */
3592
+ eosTokenId: number;
3593
+ /** Which vision encoder layer to extract features from */
3594
+ selectLayer: number;
3595
+ /** Pixel shuffle version */
3596
+ psVersion: string;
3597
+ downsampleRatio: number;
3598
+ dynamicImageSize: boolean;
3599
+ useThumbnail: boolean;
3600
+ maxDynamicPatch: number;
3601
+ minDynamicPatch: number;
3602
+ }
3603
+
2899
3604
  /**
2900
3605
  * Qwen3.5 model configuration (dense variant).
2901
3606
  *
@@ -3039,6 +3744,22 @@ export interface Qwen3Config {
3039
3744
  useFp8Cache?: boolean | undefined;
3040
3745
  }
3041
3746
 
3747
+ /** Qwen3 language model configuration */
3748
+ export interface Qwen3LmConfig {
3749
+ hiddenSize: number;
3750
+ numHiddenLayers: number;
3751
+ intermediateSize: number;
3752
+ numAttentionHeads: number;
3753
+ numKeyValueHeads: number;
3754
+ headDim: number;
3755
+ rmsNormEps: number;
3756
+ vocabSize: number;
3757
+ maxPositionEmbeddings: number;
3758
+ ropeTheta: number;
3759
+ useQkNorm: boolean;
3760
+ tieWordEmbeddings: boolean;
3761
+ }
3762
+
3042
3763
  /** Result of text recognition. */
3043
3764
  export interface RecResult {
3044
3765
  /** Recognized text */
@@ -3272,6 +3993,22 @@ export interface StepSummary {
3272
3993
  lengthCount: number;
3273
3994
  }
3274
3995
 
3996
+ /** A stored response record exposed to JavaScript. */
3997
+ export interface StoredResponseRecord {
3998
+ id: string;
3999
+ createdAt: number;
4000
+ model: string;
4001
+ status: string;
4002
+ instructions?: string;
4003
+ inputJson: string;
4004
+ outputJson: string;
4005
+ outputText: string;
4006
+ usageJson: string;
4007
+ previousResponseId?: string;
4008
+ configJson?: string;
4009
+ expiresAt?: number;
4010
+ }
4011
+
3275
4012
  /** A table structure */
3276
4013
  export interface Table {
3277
4014
  rows: Array<TableRow>;
@@ -3462,6 +4199,20 @@ export interface VlmChatConfig {
3462
4199
  topP?: number;
3463
4200
  /** Repetition penalty (default: 1.5) */
3464
4201
  repetitionPenalty?: number;
4202
+ /**
4203
+ * Presence penalty (0.0 = disabled). Subtracts a flat penalty from logits of any
4204
+ * token that appeared at least once in context. Matches OpenAI API semantics.
4205
+ */
4206
+ presencePenalty?: number;
4207
+ /** Number of recent tokens to consider for presence penalty (default: 20) */
4208
+ presenceContextSize?: number;
4209
+ /**
4210
+ * Frequency penalty (0.0 = disabled). Subtracts penalty * occurrence_count from
4211
+ * logits of each token in context. Matches OpenAI API semantics.
4212
+ */
4213
+ frequencyPenalty?: number;
4214
+ /** Number of recent tokens to consider for frequency penalty (default: 20) */
4215
+ frequencyContextSize?: number;
3465
4216
  /** Whether to return log probabilities (default: false) */
3466
4217
  returnLogprobs?: boolean;
3467
4218
  }