@mlx-node/lm 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/stream.js CHANGED
@@ -1,48 +1,26 @@
1
- import { Gemma4Model as Gemma4ModelNative, Lfm2Model as Lfm2ModelNative, Qwen3Model as Qwen3ModelNative, Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative, } from '@mlx-node/core';
2
- // Save references to the native callback-based session streaming methods
3
- // before we override them. The legacy `chatStream` surface was removed in
4
- // the chat-session refactor; the remaining session entry points below
5
- // drive all streaming via the `ChatSession` API.
6
- //
7
- // Each wrapper class re-declares these three methods as
8
- // `AsyncGenerator<ChatStreamEvent>` overrides that delegate through the
9
- // shared `_runChatStream` bridge, so the wrapper structurally satisfies
10
- // `SessionCapableModel` and can be passed to `ChatSession<M>`.
11
- // Dense
12
- // oxlint-disable-next-line @typescript-eslint/unbound-method
13
- const _nativeDenseChatStreamSessionStart = Qwen35ModelNative.prototype.chatStreamSessionStart;
14
- // oxlint-disable-next-line @typescript-eslint/unbound-method
15
- const _nativeDenseChatStreamSessionContinue = Qwen35ModelNative.prototype.chatStreamSessionContinue;
16
- // oxlint-disable-next-line @typescript-eslint/unbound-method
17
- const _nativeDenseChatStreamSessionContinueTool = Qwen35ModelNative.prototype.chatStreamSessionContinueTool;
18
- // MoE
19
- // oxlint-disable-next-line @typescript-eslint/unbound-method
20
- const _nativeMoeChatStreamSessionStart = Qwen35MoeModelNative.prototype.chatStreamSessionStart;
21
- // oxlint-disable-next-line @typescript-eslint/unbound-method
22
- const _nativeMoeChatStreamSessionContinue = Qwen35MoeModelNative.prototype.chatStreamSessionContinue;
23
- // oxlint-disable-next-line @typescript-eslint/unbound-method
24
- const _nativeMoeChatStreamSessionContinueTool = Qwen35MoeModelNative.prototype.chatStreamSessionContinueTool;
25
- // LFM2
26
- // oxlint-disable-next-line @typescript-eslint/unbound-method
27
- const _nativeLfm2ChatStreamSessionStart = Lfm2ModelNative.prototype.chatStreamSessionStart;
28
- // oxlint-disable-next-line @typescript-eslint/unbound-method
29
- const _nativeLfm2ChatStreamSessionContinue = Lfm2ModelNative.prototype.chatStreamSessionContinue;
30
- // oxlint-disable-next-line @typescript-eslint/unbound-method
31
- const _nativeLfm2ChatStreamSessionContinueTool = Lfm2ModelNative.prototype.chatStreamSessionContinueTool;
32
- // Gemma4
33
- // oxlint-disable-next-line @typescript-eslint/unbound-method
34
- const _nativeGemma4ChatStreamSessionStart = Gemma4ModelNative.prototype.chatStreamSessionStart;
35
- // oxlint-disable-next-line @typescript-eslint/unbound-method
36
- const _nativeGemma4ChatStreamSessionContinue = Gemma4ModelNative.prototype.chatStreamSessionContinue;
37
- // oxlint-disable-next-line @typescript-eslint/unbound-method
38
- const _nativeGemma4ChatStreamSessionContinueTool = Gemma4ModelNative.prototype.chatStreamSessionContinueTool;
39
- // Qwen3 (legacy, text-only)
40
- // oxlint-disable-next-line @typescript-eslint/unbound-method
41
- const _nativeQwen3ChatStreamSessionStart = Qwen3ModelNative.prototype.chatStreamSessionStart;
42
- // oxlint-disable-next-line @typescript-eslint/unbound-method
43
- const _nativeQwen3ChatStreamSessionContinue = Qwen3ModelNative.prototype.chatStreamSessionContinue;
44
- // oxlint-disable-next-line @typescript-eslint/unbound-method
45
- const _nativeQwen3ChatStreamSessionContinueTool = Qwen3ModelNative.prototype.chatStreamSessionContinueTool;
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
+ const modelPathsForTokenizers = new WeakMap();
4
+ const tokenizerPromises = new WeakMap();
5
+ function getNativeIsReasoning(chunk) {
6
+ return typeof chunk.isReasoning === 'boolean' ? chunk.isReasoning : undefined;
7
+ }
8
+ function rememberModelPath(model, modelPath) {
9
+ modelPathsForTokenizers.set(model, modelPath);
10
+ }
11
+ async function applyChatTemplateFromModelPath(model, messages, addGenerationPrompt, tools, enableThinking) {
12
+ const modelPath = modelPathsForTokenizers.get(model);
13
+ if (modelPath == null) {
14
+ throw new Error('applyChatTemplate unavailable: model path was not recorded when this model was loaded');
15
+ }
16
+ let tokenizerPromise = tokenizerPromises.get(model);
17
+ if (tokenizerPromise == null) {
18
+ tokenizerPromise = Qwen3Tokenizer.fromPretrained(join(modelPath, 'tokenizer.json'));
19
+ tokenizerPromises.set(model, tokenizerPromise);
20
+ }
21
+ const tokenizer = await tokenizerPromise;
22
+ return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking);
23
+ }
46
24
  /**
47
25
  * Shared AsyncGenerator adapter for callback-based native streaming methods.
48
26
  *
@@ -171,7 +149,16 @@ export async function* _runChatStream(startCall, signal) {
171
149
  throw item.error;
172
150
  const chunk = item.chunk;
173
151
  if (chunk.done) {
174
- yield {
152
+ // The native `ChatStreamChunk` carries `cachedTokens` on the
153
+ // terminal (`done == true`) chunk for every streaming entry
154
+ // point. Emit it on the final event verbatim — undefined means
155
+ // the native dispatch did not populate it (e.g. a bridge-level
156
+ // mock or an in-process driver), in which case downstream
157
+ // consumers treat the absence as "unknown / not plumbed" and
158
+ // skip emitting e.g. `X-Cached-Tokens` rather than reporting a
159
+ // fabricated `0`.
160
+ const chunkWithCached = chunk;
161
+ const finalEvent = {
175
162
  text: chunk.text,
176
163
  done: true,
177
164
  finishReason: chunk.finishReason,
@@ -183,9 +170,18 @@ export async function* _runChatStream(startCall, signal) {
183
170
  rawText: chunk.rawText,
184
171
  performance: chunk.performance ?? undefined,
185
172
  };
173
+ if (typeof chunkWithCached.cachedTokens === 'number') {
174
+ finalEvent.cachedTokens = chunkWithCached.cachedTokens;
175
+ }
176
+ yield finalEvent;
186
177
  return;
187
178
  }
188
- yield { text: chunk.text, done: false, isReasoning: chunk.isReasoning ?? undefined };
179
+ const delta = { text: chunk.text, done: false };
180
+ const isReasoning = getNativeIsReasoning(chunk);
181
+ if (isReasoning !== undefined) {
182
+ delta.isReasoning = isReasoning;
183
+ }
184
+ yield delta;
189
185
  }
190
186
  }
191
187
  }
@@ -203,205 +199,116 @@ export async function* _runChatStream(startCall, signal) {
203
199
  }
204
200
  }
205
201
  /**
206
- * Qwen3.5 dense model with AsyncGenerator-based session streaming.
202
+ * Build the streaming-model subclass for a native chat model class.
207
203
  *
208
- * Streaming is driven through the session API — `chatStreamSessionStart`,
209
- * `chatStreamSessionContinue`, and `chatStreamSessionContinueTool` below
210
- * which adapt the callback-based native methods to
211
- * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally satisfies
212
- * `SessionCapableModel` and can be passed to `ChatSession<Qwen35Model>`.
213
- */
214
- export class Qwen35Model extends Qwen35ModelNative {
215
- static async load(modelPath) {
216
- const instance = await Qwen35ModelNative.load(modelPath);
217
- Object.setPrototypeOf(instance, Qwen35Model.prototype);
218
- return instance;
219
- }
220
- /**
221
- * Streaming variant of {@link Qwen35Model#chatSessionStart}.
222
- *
223
- * Resets the KV caches, runs the jinja chat template, prefills on
224
- * top of the fresh caches, and streams the decoded reply token-by-
225
- * token. Stops on `<|im_end|>` so the cached history ends on a
226
- * clean ChatML boundary that subsequent `chatStreamSessionContinue`
227
- * deltas can append to. Text-only.
228
- *
229
- * The optional `signal` parameter wires an AbortSignal into the
230
- * `_runChatStream` adapter's fast-abort path. Callers that need
231
- * client-disconnect-aware cancellation (e.g. HTTP endpoints) pass
232
- * one here and the native decode winds down at the next safepoint.
233
- */
234
- // @ts-expect-error — override callback-based native method with AsyncGenerator
235
- async *chatStreamSessionStart(messages, config, signal) {
236
- yield* _runChatStream((callback) => _nativeDenseChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
237
- }
238
- /**
239
- * Streaming variant of {@link Qwen35Model#chatSessionContinue}.
240
- *
241
- * Builds a raw ChatML delta on top of the live session caches,
242
- * tokenizes it, prefills the delta, and streams the decoded reply.
243
- * Requires a live session started via `chatSessionStart` or
244
- * `chatStreamSessionStart`. Stops on `<|im_end|>`.
245
- *
246
- * `images` is the native opt-in guard parameter — callers that
247
- * attach a new image set must restart the session via
248
- * `chatStreamSessionStart` with the full history. The high-level
249
- * `ChatSession` wrapper handles that routing; callers that drive
250
- * the wrapper directly should pass `null` for text-only continues.
251
- */
252
- // @ts-expect-error — override callback-based native method with AsyncGenerator
253
- async *chatStreamSessionContinue(userMessage, images, config, signal) {
254
- yield* _runChatStream((callback) => _nativeDenseChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
255
- }
256
- /**
257
- * Streaming variant of {@link Qwen35Model#chatSessionContinueTool}.
258
- *
259
- * Builds a ChatML `<tool_response>` delta on top of the live
260
- * session caches and streams the decoded assistant reply. Requires
261
- * a live session started via `chatSessionStart` /
262
- * `chatStreamSessionStart`.
263
- */
264
- // @ts-expect-error — override callback-based native method with AsyncGenerator
265
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
266
- yield* _runChatStream((callback) => _nativeDenseChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
267
- }
268
- }
269
- /**
270
- * Qwen3.5 MoE model wrapper.
204
+ * The returned class:
205
+ * - captures the three native callback-based session-streaming methods
206
+ * from `NativeClass.prototype`,
207
+ * - overrides them as `async *` generators delegating to
208
+ * {@link _runChatStream} with identical argument plumbing (including
209
+ * `config ?? null`, `images`, `isError ?? null`, and the `signal`),
210
+ * - overrides `static load` to re-prototype the native instance onto
211
+ * the concrete subclass (`this`) and optionally record the path,
212
+ * - installs a path-backed `applyChatTemplate` when `opts.applyTemplate`
213
+ * (defaulting to `opts.recordModelPath`).
271
214
  *
272
- * Streaming is driven through the `ChatSession` API overrides below
273
- * adapt the callback-based native methods to
274
- * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
275
- * satisfies `SessionCapableModel`.
215
+ * @internal Exported so the VLM wrapper (`@mlx-node/vlm`) builds its
216
+ * `QianfanOCRModel` from the same factory. Not part of the public API.
276
217
  */
277
- export class Qwen35MoeModel extends Qwen35MoeModelNative {
278
- static async load(modelPath) {
279
- const instance = await Qwen35MoeModelNative.load(modelPath);
280
- Object.setPrototypeOf(instance, Qwen35MoeModel.prototype);
281
- return instance;
282
- }
283
- /** Streaming variant of {@link Qwen35MoeModel#chatSessionStart}. */
284
- // @ts-expect-error override callback-based native method with AsyncGenerator
285
- async *chatStreamSessionStart(messages, config, signal) {
286
- yield* _runChatStream((callback) => _nativeMoeChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
287
- }
288
- /** Streaming variant of {@link Qwen35MoeModel#chatSessionContinue}. */
289
- // @ts-expect-error override callback-based native method with AsyncGenerator
290
- async *chatStreamSessionContinue(userMessage, images, config, signal) {
291
- yield* _runChatStream((callback) => _nativeMoeChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
218
+ export function makeStreamingModel(NativeClass, opts) {
219
+ const recordPath = opts.recordModelPath;
220
+ const applyTemplate = opts.applyTemplate ?? recordPath;
221
+ // Capture the native callback-based methods before the subclass
222
+ // overrides below shadow them on the prototype.
223
+ const nativeStart = NativeClass.prototype.chatStreamSessionStart;
224
+ const nativeContinue = NativeClass.prototype.chatStreamSessionContinue;
225
+ const nativeContinueTool = NativeClass.prototype.chatStreamSessionContinueTool;
226
+ // `NativeClass` is structurally a constructor; cast to a concrete
227
+ // constructor type so `class extends` accepts it. Runtime behavior is
228
+ // unchanged — we extend the real native class.
229
+ const Base = NativeClass;
230
+ class StreamingModelImpl extends Base {
231
+ static async load(modelPath, ...rest) {
232
+ // Forward any trailing family-specific load options verbatim (e.g.
233
+ // Gemma4's `Gemma4LoadOptions` with `draftModelPath`); families whose
234
+ // native `load` takes only the path receive no extras. The public
235
+ // signature is re-narrowed per family via `Parameters<C['load']>` in
236
+ // the factory return type below.
237
+ const instance = await NativeClass.load(modelPath, ...rest);
238
+ // Use `this.prototype` (not `StreamingModelImpl.prototype`) so the
239
+ // concrete subclass declared per family supplies the prototype and
240
+ // `instanceof ConcreteSubclass` holds.
241
+ Object.setPrototypeOf(instance, this.prototype);
242
+ if (recordPath)
243
+ rememberModelPath(instance, modelPath);
244
+ return instance;
245
+ }
246
+ // The native methods are callback-based, but `Base` is typed as a
247
+ // `SessionCapableModel` constructor (whose streaming methods already
248
+ // return `AsyncGenerator<ChatStreamEvent>`), so these overrides are
249
+ // type-compatible and need no `@ts-expect-error` suppression. The
250
+ // callback bridging happens at runtime via the captured natives.
251
+ async *chatStreamSessionStart(messages, config, signal) {
252
+ yield* _runChatStream((callback) => nativeStart.call(this, messages, (config ?? null), callback), signal);
253
+ }
254
+ async *chatStreamSessionContinue(userMessage, images, audio, config, signal) {
255
+ yield* _runChatStream((callback) => nativeContinue.call(this, userMessage, images, audio, (config ?? null), callback), signal);
256
+ }
257
+ async *chatStreamSessionContinueTool(toolCallId, content, config, signal, isError) {
258
+ yield* _runChatStream((callback) => nativeContinueTool.call(this, toolCallId, content, (config ?? null), callback, (isError ?? null)), signal);
259
+ }
292
260
  }
293
- /** Streaming variant of {@link Qwen35MoeModel#chatSessionContinueTool}. */
294
- // @ts-expect-error — override callback-based native method with AsyncGenerator
295
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
296
- yield* _runChatStream((callback) => _nativeMoeChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
261
+ if (applyTemplate) {
262
+ Object.defineProperty(StreamingModelImpl.prototype, 'applyChatTemplate', {
263
+ configurable: true,
264
+ writable: true,
265
+ value(messages, addGenerationPrompt, tools, enableThinking) {
266
+ return applyChatTemplateFromModelPath(this, messages, addGenerationPrompt, tools, enableThinking);
267
+ },
268
+ });
297
269
  }
270
+ return StreamingModelImpl;
298
271
  }
299
272
  /**
300
- * LFM2 model wrapper.
273
+ * Qwen3.5 dense model with AsyncGenerator-based session streaming.
301
274
  *
302
- * Streaming is driven through the `ChatSession` API overrides below
303
- * adapt the callback-based native methods to
304
- * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
305
- * satisfies `SessionCapableModel`. LFM2 is text-only; the native
306
- * `images` guard rejects non-empty image sets with an
307
- * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` prefix.
275
+ * The empty `extends` inherits the factory's streaming overrides,
276
+ * `static load`, and `applyChatTemplate`, and supplies the concrete
277
+ * `.name === 'Qwen35Model'` and a working `instanceof`. Records its
278
+ * model path so `applyChatTemplate` can serve a lazily built tokenizer.
308
279
  */
309
- export class Lfm2Model extends Lfm2ModelNative {
310
- static async load(modelPath) {
311
- const instance = await Lfm2ModelNative.load(modelPath);
312
- Object.setPrototypeOf(instance, Lfm2Model.prototype);
313
- return instance;
314
- }
315
- /** Streaming variant of {@link Lfm2Model#chatSessionStart}. */
316
- // @ts-expect-error — override callback-based native method with AsyncGenerator
317
- async *chatStreamSessionStart(messages, config, signal) {
318
- yield* _runChatStream((callback) => _nativeLfm2ChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
319
- }
320
- /** Streaming variant of {@link Lfm2Model#chatSessionContinue}. */
321
- // @ts-expect-error — override callback-based native method with AsyncGenerator
322
- async *chatStreamSessionContinue(userMessage, images, config, signal) {
323
- yield* _runChatStream((callback) => _nativeLfm2ChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
324
- }
325
- /** Streaming variant of {@link Lfm2Model#chatSessionContinueTool}. */
326
- // @ts-expect-error — override callback-based native method with AsyncGenerator
327
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
328
- yield* _runChatStream((callback) => _nativeLfm2ChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
329
- }
280
+ export class Qwen35Model extends makeStreamingModel(Qwen35ModelNative, { recordModelPath: true }) {
330
281
  }
331
- /**
332
- * Gemma4 model wrapper.
333
- *
334
- * Streaming is driven through the `ChatSession` API overrides below
335
- * adapt the callback-based native methods to
336
- * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
337
- * satisfies `SessionCapableModel`. Gemma4 is text-only in the
338
- * current refactor scope; the native `images` guard rejects non-empty
339
- * image sets with an `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` prefix.
340
- */
341
- export class Gemma4Model extends Gemma4ModelNative {
342
- static async load(modelPath) {
343
- const instance = await Gemma4ModelNative.load(modelPath);
344
- Object.setPrototypeOf(instance, Gemma4Model.prototype);
345
- return instance;
346
- }
347
- /** Streaming variant of {@link Gemma4Model#chatSessionStart}. */
348
- // @ts-expect-error — override callback-based native method with AsyncGenerator
349
- async *chatStreamSessionStart(messages, config, signal) {
350
- yield* _runChatStream((callback) => _nativeGemma4ChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
351
- }
352
- /** Streaming variant of {@link Gemma4Model#chatSessionContinue}. */
353
- // @ts-expect-error — override callback-based native method with AsyncGenerator
354
- async *chatStreamSessionContinue(userMessage, images, config, signal) {
355
- yield* _runChatStream((callback) => _nativeGemma4ChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
356
- }
357
- /** Streaming variant of {@link Gemma4Model#chatSessionContinueTool}. */
358
- // @ts-expect-error — override callback-based native method with AsyncGenerator
359
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
360
- yield* _runChatStream((callback) => _nativeGemma4ChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
361
- }
282
+ /** Qwen3.5 MoE model — see {@link Qwen35Model} for the wrapper shape. */
283
+ export class Qwen35MoeModel extends makeStreamingModel(Qwen35MoeModelNative, { recordModelPath: true }) {
284
+ }
285
+ /** LFM2 model (text-only) see {@link Qwen35Model} for the wrapper shape. */
286
+ export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, { recordModelPath: true }) {
287
+ }
288
+ /** Gemma4 model (text-only) see {@link Qwen35Model} for the wrapper shape. */
289
+ export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, { recordModelPath: true }) {
362
290
  }
363
291
  /**
364
- * Qwen3 (legacy) model wrapper.
292
+ * Qwen3 (first-gen, text-only) model.
365
293
  *
366
- * Streaming is driven through the `ChatSession` API overrides below
367
- * adapt the callback-based native methods to
368
- * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
369
- * satisfies `SessionCapableModel`. Qwen3 legacy is text-only; the
370
- * native `images` guard rejects non-empty image sets with an
371
- * `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` prefix.
294
+ * Records its model path (so prototype-set + path-recording match the
295
+ * other families) but does not install the factory's path-backed
296
+ * `applyChatTemplate`; it retains the native tokenizer-backed method.
372
297
  */
373
- export class Qwen3Model extends Qwen3ModelNative {
374
- static async load(modelPath) {
375
- const instance = await Qwen3ModelNative.load(modelPath);
376
- Object.setPrototypeOf(instance, Qwen3Model.prototype);
377
- return instance;
378
- }
379
- /** Streaming variant of {@link Qwen3Model#chatSessionStart}. */
380
- // @ts-expect-error — override callback-based native method with AsyncGenerator
381
- async *chatStreamSessionStart(messages, config, signal) {
382
- yield* _runChatStream((callback) => _nativeQwen3ChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
383
- }
384
- /** Streaming variant of {@link Qwen3Model#chatSessionContinue}. */
385
- // @ts-expect-error — override callback-based native method with AsyncGenerator
386
- async *chatStreamSessionContinue(userMessage, images, config, signal) {
387
- yield* _runChatStream((callback) => _nativeQwen3ChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
388
- }
389
- /** Streaming variant of {@link Qwen3Model#chatSessionContinueTool}. */
390
- // @ts-expect-error — override callback-based native method with AsyncGenerator
391
- async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
392
- yield* _runChatStream((callback) => _nativeQwen3ChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
393
- }
298
+ export class Qwen3Model extends makeStreamingModel(Qwen3ModelNative, {
299
+ recordModelPath: true,
300
+ applyTemplate: false,
301
+ }) {
394
302
  }
395
303
  // -------------------------------------------------------------------
396
304
  // Compile-time conformance check
397
305
  // -------------------------------------------------------------------
398
306
  //
399
- // Ensures each wrapper class structurally satisfies
400
- // `SessionCapableModel` so `ChatSession<XxxModel>` will type-check in
401
- // downstream code. The assignments are compile-only — the
402
- // `null as unknown as T` placeholder never runs. If a wrapper's
403
- // override signature drifts away from the interface, TypeScript will
404
- // fail to compile this block, surfacing the regression at build time.
307
+ // Ensures each family class structurally satisfies
308
+ // `SessionCapableModel` so `ChatSession<XxxModel>` type-checks in
309
+ // downstream code. Compile-only — the `null as unknown as T`
310
+ // placeholder never runs. If a factory override signature drifts away
311
+ // from the interface, this block fails to compile.
405
312
  function _assertSessionCapable() {
406
313
  const _qwen35 = null;
407
314
  const _moe = null;
@@ -415,3 +322,29 @@ function _assertSessionCapable() {
415
322
  void _qwen3;
416
323
  }
417
324
  void _assertSessionCapable;
325
+ /** Compile-time guard that both Qwen3.5 native classes and wrappers retain the exact media planner. */
326
+ function _assertExpandedPromptPlannerSurfaces() {
327
+ const _nativeDense = null;
328
+ const _nativeMoe = null;
329
+ const _wrappedDense = null;
330
+ const _wrappedMoe = null;
331
+ void _nativeDense;
332
+ void _nativeMoe;
333
+ void _wrappedDense;
334
+ void _wrappedMoe;
335
+ }
336
+ void _assertExpandedPromptPlannerSurfaces;
337
+ /** Compile-time guard that the factory preserves every non-streaming native member. */
338
+ function _assertPreservedNativeSurfaces() {
339
+ const _qwen3 = null;
340
+ const _qwen35 = null;
341
+ const _moe = null;
342
+ const _lfm2 = null;
343
+ const _gemma4 = null;
344
+ void _qwen3;
345
+ void _qwen35;
346
+ void _moe;
347
+ void _lfm2;
348
+ void _gemma4;
349
+ }
350
+ void _assertPreservedNativeSurfaces;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlx-node/lm",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
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,9 +28,9 @@
28
28
  "test": "vite test run"
29
29
  },
30
30
  "dependencies": {
31
- "@mlx-node/core": "0.0.7"
31
+ "@mlx-node/core": "0.0.8"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^25.6.0"
34
+ "@types/node": "^26.0.0"
35
35
  }
36
36
  }