@mlx-node/lm 0.0.8 → 0.0.10

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.
@@ -112,6 +112,7 @@ declare const MODEL_FAMILY_REGISTRY: readonly [{
112
112
  };
113
113
  readonly load: (modelPath: string) => Promise<import("../stream.js").StreamingInstance<typeof NativeLfm2Model, {
114
114
  readonly recordModelPath: true;
115
+ readonly replayAssistantRawText: true;
115
116
  }>>;
116
117
  readonly nativeModelClass: typeof NativeLfm2Model;
117
118
  }, {
@@ -122,6 +123,7 @@ declare const MODEL_FAMILY_REGISTRY: readonly [{
122
123
  };
123
124
  readonly load: (modelPath: string) => Promise<import("../stream.js").StreamingInstance<typeof NativeLfm2Model, {
124
125
  readonly recordModelPath: true;
126
+ readonly replayAssistantRawText: true;
125
127
  }>>;
126
128
  readonly nativeModelClass: typeof NativeLfm2Model;
127
129
  }, {
@@ -1 +1 @@
1
- {"version":3,"file":"model-loader.d.ts","sourceRoot":"","sources":["../../src/models/model-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EACL,WAAW,IAAI,iBAAiB,EAChC,YAAY,EACZ,SAAS,IAAI,eAAe,EAC5B,eAAe,EACf,UAAU,IAAI,gBAAgB,EAC9B,WAAW,IAAI,iBAAiB,EAChC,cAAc,IAAI,oBAAoB,EACvC,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG3E,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED,UAAU,uBAAwB,SAAQ,qBAAqB;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,qBAAqB;wBAEZ,QAAQ;mBACb,UAAU;;;;;+BAKE,MAAM,YAAY,gBAAgB;;;;;;wBASzC,SAAS;mBACd,WAAW;;;;;+BAMC,MAAM;;;wBAIb,OAAO;mBACZ,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;;;wBAKb,SAAS;mBACd,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;wBAIb,aAAa;mBAClB,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;wBAIb,MAAM;mBACX,UAAU;;iBACP,aAAa;;+BACJ,MAAM;;;;;wBAIb,UAAU;mBACf,UAAU;;iBACP,aAAa;;+BACJ,MAAM;;;;;wBAIb,eAAe;mBACpB,KAAK;;iBACF,aAAa;;+BACJ,MAAM;;;wBAIb,aAAa;mBAClB,KAAK;;iBACF,aAAa;;+BACJ,MAAM;;EAGyB,CAAC;AAEtD,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,CAAC;AAE5E,KAAK,qBAAqB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,KAAK,yBAAyB,GAAG,OAAO,CAAC,qBAAqB,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AAEhG;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,GACtD,YAAY,CAAC,yBAAyB,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAsIhE;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAGrG;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAa3C;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAkB3E"}
1
+ {"version":3,"file":"model-loader.d.ts","sourceRoot":"","sources":["../../src/models/model-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EACL,WAAW,IAAI,iBAAiB,EAChC,YAAY,EACZ,SAAS,IAAI,eAAe,EAC5B,eAAe,EACf,UAAU,IAAI,gBAAgB,EAC9B,WAAW,IAAI,iBAAiB,EAChC,cAAc,IAAI,oBAAoB,EACvC,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG3E,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID,UAAU,qBAAqB;IAC7B,QAAQ,CAAC,oBAAoB,EAAE,OAAO,CAAC;IACvC,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED,UAAU,uBAAwB,SAAQ,qBAAqB;IAC7D,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CACxC;AAgCD;;;;;;;;;;;;;;;;;;GAkBG;AACH,QAAA,MAAM,qBAAqB;wBAEZ,QAAQ;mBACb,UAAU;;;;;+BAKE,MAAM,YAAY,gBAAgB;;;;;;wBASzC,SAAS;mBACd,WAAW;;;;;+BAMC,MAAM;;;wBAIb,OAAO;mBACZ,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;;;wBAKb,SAAS;mBACd,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;wBAIb,aAAa;mBAClB,WAAW;;iBACR,aAAa;;+BACJ,MAAM;;;;;wBAIb,MAAM;mBACX,UAAU;;iBACP,aAAa;;+BACJ,MAAM;;;;;;wBAIb,UAAU;mBACf,UAAU;;iBACP,aAAa;;+BACJ,MAAM;;;;;;wBAIb,eAAe;mBACpB,KAAK;;iBACF,aAAa;;+BACJ,MAAM;;;wBAIb,aAAa;mBAClB,KAAK;;iBACF,aAAa;;+BACJ,MAAM;;EAGyB,CAAC;AAEtD,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,CAAC;AAE5E,KAAK,qBAAqB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,KAAK,yBAAyB,GAAG,OAAO,CAAC,qBAAqB,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAC;AAEhG;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,qBAAqB,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAEpF;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,GACtD,YAAY,CAAC,yBAAyB,CAAC,kBAAkB,CAAC,CAAC,CAAC;AAsIhE;;;;;;;;;;;GAWG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAGrG;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC,CAa3C;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAkB3E"}
@@ -48,8 +48,20 @@ export declare class PagedConfigOverrideManager {
48
48
  * A caller-supplied canonical family takes precedence over the raw config
49
49
  * type (for example, `gemma4` for a `gemma4_unified` checkpoint).
50
50
  * Unmanaged, unreadable, or malformed checkpoints pass through unchanged.
51
+ *
52
+ * `persistPagedCache` is a tri-state cold-tier directive. `undefined` leaves
53
+ * the field untouched (families with no cold-tier opt-in). A boolean is
54
+ * AUTHORITATIVE: it writes `persist_paged_cache: <value>` into the cloned
55
+ * config, overriding whatever the source config.json carries (either alias),
56
+ * and forces a clone whenever the source's value disagrees so the directive
57
+ * actually reaches the loader. Callers gate the boolean to the families whose
58
+ * paged cold restore is sound — the allowlist in
59
+ * `packages/agent/src/cold-tier.ts`, mirrored by `COLD_RESTORE_FAMILIES` in
60
+ * `crates/mlx-core/src/cold_tier.rs`. It is a SET, not one family, and it is
61
+ * deliberately not repeated here: `MlxModelHost` calls this for every
62
+ * allowlisted family, and a second copy of the list would drift.
51
63
  */
52
- resolve(modelPath: string, canonicalModelType?: string): Promise<string>;
64
+ resolve(modelPath: string, canonicalModelType?: string, persistPagedCache?: boolean): Promise<string>;
53
65
  private resolveInternal;
54
66
  /** Remove this manager's temporary root without affecting other managers. */
55
67
  cleanup(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"paged-config-override.d.ts","sourceRoot":"","sources":["../../src/models/paged-config-override.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,YAAI,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAElH,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,YAAI,SAAS,EAAE,aAAa,CAAU,CAAC;AAO5E,MAAM,WAAW,iCAAiC;IAChD,qFAAqF;IACrF,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAU;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAChE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA8B;IAC7D,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,OAAO,GAAE,iCAAsC,EAI1D;IAED;;;;;OAKG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY7E;YAEa,eAAe;IAgD7B,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAKvB;YAEa,cAAc;YAad,cAAc;IA8C5B,OAAO,CAAC,OAAO;CAIhB"}
1
+ {"version":3,"file":"paged-config-override.d.ts","sourceRoot":"","sources":["../../src/models/paged-config-override.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,2EAA2E;AAC3E,eAAO,MAAM,uBAAuB,YAAI,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AAElH,iEAAiE;AACjE,eAAO,MAAM,wBAAwB,YAAI,SAAS,EAAE,aAAa,CAAU,CAAC;AAO5E,MAAM,WAAW,iCAAiC;IAChD,qFAAqF;IACrF,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,qBAAa,0BAA0B;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAsB;IACjD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAAU;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsC;IAChE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA8B;IAC7D,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,cAAc,CAA4B;IAClD,OAAO,CAAC,QAAQ,CAAS;IAEzB,YAAY,OAAO,GAAE,iCAAsC,EAI1D;IAED;;;;;;;;;;;;;;;;;OAiBG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAY1G;YAEa,eAAe;IAuE7B,6EAA6E;IAC7E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAKvB;YAEa,cAAc;YAad,cAAc;IAsD5B,OAAO,CAAC,OAAO;CAIhB"}
@@ -47,12 +47,24 @@ export class PagedConfigOverrideManager {
47
47
  * A caller-supplied canonical family takes precedence over the raw config
48
48
  * type (for example, `gemma4` for a `gemma4_unified` checkpoint).
49
49
  * Unmanaged, unreadable, or malformed checkpoints pass through unchanged.
50
+ *
51
+ * `persistPagedCache` is a tri-state cold-tier directive. `undefined` leaves
52
+ * the field untouched (families with no cold-tier opt-in). A boolean is
53
+ * AUTHORITATIVE: it writes `persist_paged_cache: <value>` into the cloned
54
+ * config, overriding whatever the source config.json carries (either alias),
55
+ * and forces a clone whenever the source's value disagrees so the directive
56
+ * actually reaches the loader. Callers gate the boolean to the families whose
57
+ * paged cold restore is sound — the allowlist in
58
+ * `packages/agent/src/cold-tier.ts`, mirrored by `COLD_RESTORE_FAMILIES` in
59
+ * `crates/mlx-core/src/cold_tier.rs`. It is a SET, not one family, and it is
60
+ * deliberately not repeated here: `MlxModelHost` calls this for every
61
+ * allowlisted family, and a second copy of the list would drift.
50
62
  */
51
- async resolve(modelPath, canonicalModelType) {
63
+ async resolve(modelPath, canonicalModelType, persistPagedCache) {
52
64
  if (this.disposed) {
53
65
  throw new Error('PagedConfigOverrideManager: resolve() called after cleanup()');
54
66
  }
55
- const operation = this.resolveInternal(modelPath, canonicalModelType);
67
+ const operation = this.resolveInternal(modelPath, canonicalModelType, persistPagedCache);
56
68
  this.activeResolves.add(operation);
57
69
  try {
58
70
  return await operation;
@@ -61,7 +73,7 @@ export class PagedConfigOverrideManager {
61
73
  this.activeResolves.delete(operation);
62
74
  }
63
75
  }
64
- async resolveInternal(modelPath, canonicalModelType) {
76
+ async resolveInternal(modelPath, canonicalModelType, persistPagedCache) {
65
77
  const sourcePath = isAbsolute(modelPath) ? modelPath : resolve(modelPath);
66
78
  let config;
67
79
  try {
@@ -75,6 +87,15 @@ export class PagedConfigOverrideManager {
75
87
  if (modelType === null || !this.modelTypes.has(modelType)) {
76
88
  return modelPath;
77
89
  }
90
+ // A boolean persist directive is authoritative: force a clone whenever the
91
+ // source config's EFFECTIVE value disagrees, so the directive actually reaches
92
+ // the loader (both `--no-persist-cache` off AND default-on). Mirror the native
93
+ // parser's snake-first precedence — `persist_paged_cache` wins when present,
94
+ // else the camelCase alias — so a config with snake=false AND camel=true does
95
+ // not read as `true` here (an OR) while native reads snake=false, silently
96
+ // dropping an authoritative `true` request.
97
+ const sourcePersist = typeof config.persist_paged_cache === 'boolean' ? config.persist_paged_cache : config.persistPagedCache === true;
98
+ const persistOverrideNeeded = persistPagedCache !== undefined && persistPagedCache !== sourcePersist;
78
99
  // Gemma4's DSpark / assistant speculative executor currently owns flat KV
79
100
  // caches. Preserve native draft discovery only for explicit callers; the
80
101
  // default paged clone intentionally omits subdirectories, hiding `draft/`
@@ -89,20 +110,28 @@ export class PagedConfigOverrideManager {
89
110
  const memorySatisfied = cacheFloorMb === undefined || (configuredMemoryMb ?? 0) >= cacheFloorMb;
90
111
  // Even an already-paged Gemma config must be cloned when `draft/` exists:
91
112
  // returning the source would expose the draft to native auto-discovery and
92
- // trigger the flat-speculation/paged-cache conflict.
93
- if (pagedEnabled && memorySatisfied && !hasEmbeddedGemmaDraft) {
113
+ // trigger the flat-speculation/paged-cache conflict. An authoritative
114
+ // persist directive that disagrees with the source likewise blocks the
115
+ // pass-through so the resolved value reaches the loader.
116
+ if (pagedEnabled && memorySatisfied && !hasEmbeddedGemmaDraft && !persistOverrideNeeded) {
94
117
  return modelPath;
95
118
  }
96
- const existing = this.overrides.get(sourcePath);
119
+ // Memoize per (source, resolved family, persist directive): the same
120
+ // checkpoint resolved with a different persist tri-state must yield a distinct
121
+ // clone, not the first one cached under the bare path. `cleanup()` iterates
122
+ // every value, so multiple entries per path are all still disposed.
123
+ const persistKey = persistPagedCache === undefined ? 'u' : persistPagedCache ? 't' : 'f';
124
+ const cacheKey = `${sourcePath}\0${modelType}\0${persistKey}`;
125
+ const existing = this.overrides.get(cacheKey);
97
126
  if (existing !== undefined)
98
127
  return existing;
99
- const pending = this.createOverride(sourcePath, config, cacheFloorMb, modelType);
100
- this.overrides.set(sourcePath, pending);
128
+ const pending = this.createOverride(sourcePath, config, cacheFloorMb, modelType, persistPagedCache);
129
+ this.overrides.set(cacheKey, pending);
101
130
  try {
102
131
  return await pending;
103
132
  }
104
133
  catch (error) {
105
- this.overrides.delete(sourcePath);
134
+ this.overrides.delete(cacheKey);
106
135
  throw error;
107
136
  }
108
137
  }
@@ -126,13 +155,21 @@ export class PagedConfigOverrideManager {
126
155
  await rm(root, { recursive: true, force: true }).catch(() => undefined);
127
156
  }
128
157
  }
129
- async createOverride(sourcePath, sourceConfig, cacheFloorMb, modelType) {
158
+ async createOverride(sourcePath, sourceConfig, cacheFloorMb, modelType, persistPagedCache) {
130
159
  const root = await this.getRoot();
131
160
  const overrideDir = await mkdtemp(join(root, 'model-'));
132
161
  const config = {
133
162
  ...sourceConfig,
134
163
  use_block_paged_cache: true,
135
164
  };
165
+ if (persistPagedCache !== undefined) {
166
+ // Authoritative: the loader reads snake_case, so that spelling is the one
167
+ // that decides persistence. Reconcile a stray camelCase alias spread from
168
+ // the source config so it can never contradict the authoritative value.
169
+ config.persist_paged_cache = persistPagedCache;
170
+ if ('persistPagedCache' in config)
171
+ config.persistPagedCache = persistPagedCache;
172
+ }
136
173
  if (cacheFloorMb !== undefined) {
137
174
  config.paged_cache_memory_mb = Math.max(positiveNumber(sourceConfig.paged_cache_memory_mb) ?? 0, cacheFloorMb);
138
175
  }
package/dist/stream.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from '@mlx-node/core';
2
- import type { ChatStreamChunk, ChatStreamHandle, PerformanceMetrics, ToolCallResult } from '@mlx-node/core';
3
- import type { SessionCapableModel } from './chat-session.js';
1
+ import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from "@mlx-node/core";
2
+ import type { ChatStreamChunk, ChatStreamHandle, PerformanceMetrics, ToolCallResult } from "@mlx-node/core";
3
+ import type { SessionCapableModel } from "./chat-session.js";
4
4
  export interface ChatStreamDelta {
5
5
  text: string;
6
6
  done: false;
@@ -12,10 +12,23 @@ export interface ChatStreamFinal {
12
12
  finishReason: string;
13
13
  toolCalls: ToolCallResult[];
14
14
  thinking: string | null;
15
+ /** Effective `enable_thinking` value passed to the model chat template. */
16
+ thinkingEnabled: boolean;
15
17
  numTokens: number;
16
18
  promptTokens: number;
17
19
  reasoningTokens: number;
18
20
  rawText: string;
21
+ /**
22
+ * Native token-aware reasoning-redacted raw output. ChatSession uses this
23
+ * when it captures full reasoning internally for deterministic replay while
24
+ * keeping `includeReasoning: false` private to the caller.
25
+ */
26
+ publicRawText?: string;
27
+ /**
28
+ * Whether terminal `text` is the complete parsed assistant content.
29
+ * Gemma emits visible content exclusively as deltas and sets false.
30
+ */
31
+ textAuthoritative?: boolean;
19
32
  /**
20
33
  * Number of prompt tokens served from the reused KV-cache prefix on
21
34
  * this turn. Mirrors the `cachedTokens` field on the non-streaming
@@ -42,6 +55,15 @@ export interface ChatStreamFinal {
42
55
  performance?: PerformanceMetrics;
43
56
  }
44
57
  export type ChatStreamEvent = ChatStreamDelta | ChatStreamFinal;
58
+ type TemplateContentOrder = "textThenMedia" | "imagesThenText";
59
+ interface TemplateContentPolicy {
60
+ order: TemplateContentOrder;
61
+ /**
62
+ * When sanitized text already contains this model-owned placeholder, keep
63
+ * the message structured but do not synthesize additional image parts.
64
+ */
65
+ existingImagePlaceholder?: string;
66
+ }
45
67
  /**
46
68
  * Shared AsyncGenerator adapter for callback-based native streaming methods.
47
69
  *
@@ -130,6 +152,18 @@ interface StreamingModelOptions {
130
152
  * only the factory's path-backed replacement.
131
153
  */
132
154
  applyTemplate?: boolean;
155
+ /**
156
+ * Model-specific ordering for structured multimodal content parts. The
157
+ * tokenizer applies this policy after sanitization while the checkpoint
158
+ * Jinja template continues to own all role and wire-format tokens.
159
+ */
160
+ templateContentPolicy?: TemplateContentPolicy;
161
+ /**
162
+ * Preserve the native raw assistant bytes in session history. LFM2's
163
+ * checkpoint template consumes reasoning inside `message.content` and does
164
+ * not read the structured `reasoning_content` field used by Qwen/Gemma.
165
+ */
166
+ replayAssistantRawText?: boolean;
133
167
  }
134
168
  /**
135
169
  * Shared base type produced by the factory: a `SessionCapableModel`
@@ -147,10 +181,10 @@ export type StreamingModel = SessionCapableModel;
147
181
  */
148
182
  type ResolvedApplyTemplate<O extends StreamingModelOptions> = O extends {
149
183
  applyTemplate: boolean;
150
- } ? O['applyTemplate'] : O['recordModelPath'];
184
+ } ? O["applyTemplate"] : O["recordModelPath"];
151
185
  /** @internal Method names whose callback ABI is replaced by the generator wrapper. */
152
186
  export type NativeStreamingMethod = keyof NativeStreamingInstance;
153
- type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreamingMethod | (ResolvedApplyTemplate<O> extends true ? 'applyChatTemplate' : never);
187
+ type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreamingMethod | (ResolvedApplyTemplate<O> extends true ? "applyChatTemplate" : never);
154
188
  /**
155
189
  * Instance surface of a generated streaming wrapper. Only methods replaced at
156
190
  * runtime are removed from the native instance: the three callback streaming
@@ -165,7 +199,7 @@ type StreamingReplacementMethod<O extends StreamingModelOptions> = NativeStreami
165
199
  *
166
200
  * @internal Concrete instance type returned by {@link makeStreamingModel}.
167
201
  */
168
- export type StreamingInstance<C extends NativeStreamingCtor, O extends StreamingModelOptions> = Omit<InstanceType<C>, StreamingReplacementMethod<O>> & SessionCapableModel & (ResolvedApplyTemplate<O> extends true ? Required<Pick<SessionCapableModel, 'applyChatTemplate'>> : object);
202
+ export type StreamingInstance<C extends NativeStreamingCtor, O extends StreamingModelOptions> = Omit<InstanceType<C>, StreamingReplacementMethod<O>> & SessionCapableModel & (ResolvedApplyTemplate<O> extends true ? Required<Pick<SessionCapableModel, "applyChatTemplate">> : object);
169
203
  /**
170
204
  * Build the streaming-model subclass for a native chat model class.
171
205
  *
@@ -185,7 +219,7 @@ export type StreamingInstance<C extends NativeStreamingCtor, O extends Streaming
185
219
  */
186
220
  export declare function makeStreamingModel<C extends NativeStreamingCtor, const O extends StreamingModelOptions>(NativeClass: C, opts: O): {
187
221
  new (...args: ConstructorParameters<C>): StreamingInstance<C, O>;
188
- load(...args: Parameters<C['load']>): Promise<StreamingInstance<C, O>>;
222
+ load(...args: Parameters<C["load"]>): Promise<StreamingInstance<C, O>>;
189
223
  };
190
224
  declare const Qwen35Model_base: {
191
225
  new (): StreamingInstance<typeof Qwen35ModelNative, {
@@ -219,9 +253,11 @@ export declare class Qwen35MoeModel extends Qwen35MoeModel_base {
219
253
  declare const Lfm2Model_base: {
220
254
  new (): StreamingInstance<typeof Lfm2ModelNative, {
221
255
  readonly recordModelPath: true;
256
+ readonly replayAssistantRawText: true;
222
257
  }>;
223
258
  load(modelPath: string): Promise<StreamingInstance<typeof Lfm2ModelNative, {
224
259
  readonly recordModelPath: true;
260
+ readonly replayAssistantRawText: true;
225
261
  }>>;
226
262
  };
227
263
  /** LFM2 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"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"}
package/dist/stream.js CHANGED
@@ -1,25 +1,28 @@
1
- import { join } from 'node:path';
2
- import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from '@mlx-node/core';
1
+ import { join } from "node:path";
2
+ import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Tokenizer, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from "@mlx-node/core";
3
3
  const modelPathsForTokenizers = new WeakMap();
4
4
  const tokenizerPromises = new WeakMap();
5
5
  function getNativeIsReasoning(chunk) {
6
- return typeof chunk.isReasoning === 'boolean' ? chunk.isReasoning : undefined;
6
+ return typeof chunk.isReasoning === "boolean" ? chunk.isReasoning : undefined;
7
7
  }
8
8
  function rememberModelPath(model, modelPath) {
9
9
  modelPathsForTokenizers.set(model, modelPath);
10
10
  }
11
- async function applyChatTemplateFromModelPath(model, messages, addGenerationPrompt, tools, enableThinking) {
11
+ async function applyChatTemplateFromModelPath(model, messages, addGenerationPrompt, tools, enableThinking, contentPolicy) {
12
12
  const modelPath = modelPathsForTokenizers.get(model);
13
13
  if (modelPath == null) {
14
- throw new Error('applyChatTemplate unavailable: model path was not recorded when this model was loaded');
14
+ throw new Error("applyChatTemplate unavailable: model path was not recorded when this model was loaded");
15
15
  }
16
16
  let tokenizerPromise = tokenizerPromises.get(model);
17
17
  if (tokenizerPromise == null) {
18
- tokenizerPromise = Qwen3Tokenizer.fromPretrained(join(modelPath, 'tokenizer.json'));
18
+ tokenizerPromise = Qwen3Tokenizer.fromPretrained(join(modelPath, "tokenizer.json"));
19
19
  tokenizerPromises.set(model, tokenizerPromise);
20
20
  }
21
21
  const tokenizer = await tokenizerPromise;
22
- return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking);
22
+ if (contentPolicy == null) {
23
+ return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking);
24
+ }
25
+ return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking, contentPolicy.order, contentPolicy.existingImagePlaceholder);
23
26
  }
24
27
  /**
25
28
  * Shared AsyncGenerator adapter for callback-based native streaming methods.
@@ -127,7 +130,7 @@ export async function* _runChatStream(startCall, signal) {
127
130
  }
128
131
  else {
129
132
  onAbort = triggerAbort;
130
- signal.addEventListener('abort', onAbort, { once: true });
133
+ signal.addEventListener("abort", onAbort, { once: true });
131
134
  }
132
135
  }
133
136
  try {
@@ -149,6 +152,9 @@ export async function* _runChatStream(startCall, signal) {
149
152
  throw item.error;
150
153
  const chunk = item.chunk;
151
154
  if (chunk.done) {
155
+ if (typeof chunk.thinkingEnabled !== "boolean") {
156
+ throw new Error("Native terminal chat stream chunk is missing thinkingEnabled");
157
+ }
152
158
  // The native `ChatStreamChunk` carries `cachedTokens` on the
153
159
  // terminal (`done == true`) chunk for every streaming entry
154
160
  // point. Emit it on the final event verbatim — undefined means
@@ -164,15 +170,22 @@ export async function* _runChatStream(startCall, signal) {
164
170
  finishReason: chunk.finishReason,
165
171
  toolCalls: chunk.toolCalls ?? [],
166
172
  thinking: chunk.thinking ?? null,
173
+ thinkingEnabled: chunk.thinkingEnabled,
167
174
  numTokens: chunk.numTokens,
168
175
  promptTokens: chunk.promptTokens ?? 0,
169
176
  reasoningTokens: chunk.reasoningTokens ?? 0,
170
177
  rawText: chunk.rawText,
171
178
  performance: chunk.performance ?? undefined,
172
179
  };
173
- if (typeof chunkWithCached.cachedTokens === 'number') {
180
+ if (typeof chunkWithCached.cachedTokens === "number") {
174
181
  finalEvent.cachedTokens = chunkWithCached.cachedTokens;
175
182
  }
183
+ if (typeof chunkWithCached.publicRawText === "string") {
184
+ finalEvent.publicRawText = chunkWithCached.publicRawText;
185
+ }
186
+ if (typeof chunkWithCached.textAuthoritative === "boolean") {
187
+ finalEvent.textAuthoritative = chunkWithCached.textAuthoritative;
188
+ }
176
189
  yield finalEvent;
177
190
  return;
178
191
  }
@@ -188,7 +201,7 @@ export async function* _runChatStream(startCall, signal) {
188
201
  finally {
189
202
  if (signal != null && onAbort != null) {
190
203
  try {
191
- signal.removeEventListener('abort', onAbort);
204
+ signal.removeEventListener("abort", onAbort);
192
205
  }
193
206
  catch {
194
207
  // removeEventListener shouldn't throw, but stay defensive —
@@ -218,6 +231,8 @@ export async function* _runChatStream(startCall, signal) {
218
231
  export function makeStreamingModel(NativeClass, opts) {
219
232
  const recordPath = opts.recordModelPath;
220
233
  const applyTemplate = opts.applyTemplate ?? recordPath;
234
+ const templateContentPolicy = opts.templateContentPolicy;
235
+ const replayAssistantRawText = opts.replayAssistantRawText ?? false;
221
236
  // Capture the native callback-based methods before the subclass
222
237
  // overrides below shadow them on the prototype.
223
238
  const nativeStart = NativeClass.prototype.chatStreamSessionStart;
@@ -228,6 +243,12 @@ export function makeStreamingModel(NativeClass, opts) {
228
243
  // unchanged — we extend the real native class.
229
244
  const Base = NativeClass;
230
245
  class StreamingModelImpl extends Base {
246
+ supportsReplayReasoningCapture() {
247
+ return true;
248
+ }
249
+ replaysAssistantRawText() {
250
+ return replayAssistantRawText;
251
+ }
231
252
  static async load(modelPath, ...rest) {
232
253
  // Forward any trailing family-specific load options verbatim (e.g.
233
254
  // Gemma4's `Gemma4LoadOptions` with `draftModelPath`); families whose
@@ -251,19 +272,19 @@ export function makeStreamingModel(NativeClass, opts) {
251
272
  async *chatStreamSessionStart(messages, config, signal) {
252
273
  yield* _runChatStream((callback) => nativeStart.call(this, messages, (config ?? null), callback), signal);
253
274
  }
254
- async *chatStreamSessionContinue(userMessage, images, audio, config, signal) {
255
- yield* _runChatStream((callback) => nativeContinue.call(this, userMessage, images, audio, (config ?? null), callback), signal);
275
+ async *chatStreamSessionContinue(messages, config, signal) {
276
+ yield* _runChatStream((callback) => nativeContinue.call(this, messages, (config ?? null), callback), signal);
256
277
  }
257
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal, isError) {
258
- yield* _runChatStream((callback) => nativeContinueTool.call(this, toolCallId, content, (config ?? null), callback, (isError ?? null)), signal);
278
+ async *chatStreamSessionContinueTool(messages, config, signal) {
279
+ yield* _runChatStream((callback) => nativeContinueTool.call(this, messages, (config ?? null), callback), signal);
259
280
  }
260
281
  }
261
282
  if (applyTemplate) {
262
- Object.defineProperty(StreamingModelImpl.prototype, 'applyChatTemplate', {
283
+ Object.defineProperty(StreamingModelImpl.prototype, "applyChatTemplate", {
263
284
  configurable: true,
264
285
  writable: true,
265
286
  value(messages, addGenerationPrompt, tools, enableThinking) {
266
- return applyChatTemplateFromModelPath(this, messages, addGenerationPrompt, tools, enableThinking);
287
+ return applyChatTemplateFromModelPath(this, messages, addGenerationPrompt, tools, enableThinking, templateContentPolicy);
267
288
  },
268
289
  });
269
290
  }
@@ -277,16 +298,25 @@ export function makeStreamingModel(NativeClass, opts) {
277
298
  * `.name === 'Qwen35Model'` and a working `instanceof`. Records its
278
299
  * model path so `applyChatTemplate` can serve a lazily built tokenizer.
279
300
  */
280
- export class Qwen35Model extends makeStreamingModel(Qwen35ModelNative, { recordModelPath: true }) {
301
+ export class Qwen35Model extends makeStreamingModel(Qwen35ModelNative, {
302
+ recordModelPath: true,
303
+ }) {
281
304
  }
282
305
  /** Qwen3.5 MoE model — see {@link Qwen35Model} for the wrapper shape. */
283
- export class Qwen35MoeModel extends makeStreamingModel(Qwen35MoeModelNative, { recordModelPath: true }) {
306
+ export class Qwen35MoeModel extends makeStreamingModel(Qwen35MoeModelNative, {
307
+ recordModelPath: true,
308
+ }) {
284
309
  }
285
310
  /** LFM2 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
286
- export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, { recordModelPath: true }) {
311
+ export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, {
312
+ recordModelPath: true,
313
+ replayAssistantRawText: true,
314
+ }) {
287
315
  }
288
316
  /** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
289
- export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, { recordModelPath: true }) {
317
+ export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, {
318
+ recordModelPath: true,
319
+ }) {
290
320
  }
291
321
  /**
292
322
  * Qwen3 (first-gen, text-only) model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlx-node/lm",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "homepage": "https://github.com/mlx-node/mlx-node",
5
5
  "bugs": {
6
6
  "url": "https://github.com/mlx-node/mlx-node/issues"
@@ -28,7 +28,7 @@
28
28
  "test": "vite test run"
29
29
  },
30
30
  "dependencies": {
31
- "@mlx-node/core": "0.0.8"
31
+ "@mlx-node/core": "0.0.10"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^26.0.0"