@mlx-node/lm 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -38
- package/dist/chat-session.d.ts +372 -104
- package/dist/chat-session.d.ts.map +1 -1
- package/dist/chat-session.js +891 -187
- package/dist/index.d.ts +12 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -7
- package/dist/interfaces.d.ts +2 -17
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/models/lfm2-configs.d.ts.map +1 -1
- package/dist/models/lfm2-configs.js +59 -0
- package/dist/models/model-loader.d.ts +175 -4
- package/dist/models/model-loader.d.ts.map +1 -1
- package/dist/models/model-loader.js +226 -49
- package/dist/models/paged-config-override.d.ts +72 -0
- package/dist/models/paged-config-override.d.ts.map +1 -0
- package/dist/models/paged-config-override.js +291 -0
- package/dist/models/qwen3_5-configs.d.ts.map +1 -1
- package/dist/models/qwen3_5-configs.js +5 -0
- package/dist/stream.d.ts +216 -103
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +192 -229
- package/package.json +3 -3
package/dist/stream.js
CHANGED
|
@@ -1,48 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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, contentPolicy) {
|
|
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
|
+
if (contentPolicy == null) {
|
|
23
|
+
return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking);
|
|
24
|
+
}
|
|
25
|
+
return tokenizer.applyChatTemplate(messages, addGenerationPrompt, tools, enableThinking, contentPolicy.order, contentPolicy.existingImagePlaceholder);
|
|
26
|
+
}
|
|
46
27
|
/**
|
|
47
28
|
* Shared AsyncGenerator adapter for callback-based native streaming methods.
|
|
48
29
|
*
|
|
@@ -149,7 +130,7 @@ export async function* _runChatStream(startCall, signal) {
|
|
|
149
130
|
}
|
|
150
131
|
else {
|
|
151
132
|
onAbort = triggerAbort;
|
|
152
|
-
signal.addEventListener(
|
|
133
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
153
134
|
}
|
|
154
135
|
}
|
|
155
136
|
try {
|
|
@@ -171,28 +152,56 @@ export async function* _runChatStream(startCall, signal) {
|
|
|
171
152
|
throw item.error;
|
|
172
153
|
const chunk = item.chunk;
|
|
173
154
|
if (chunk.done) {
|
|
174
|
-
|
|
155
|
+
if (typeof chunk.thinkingEnabled !== "boolean") {
|
|
156
|
+
throw new Error("Native terminal chat stream chunk is missing thinkingEnabled");
|
|
157
|
+
}
|
|
158
|
+
// The native `ChatStreamChunk` carries `cachedTokens` on the
|
|
159
|
+
// terminal (`done == true`) chunk for every streaming entry
|
|
160
|
+
// point. Emit it on the final event verbatim — undefined means
|
|
161
|
+
// the native dispatch did not populate it (e.g. a bridge-level
|
|
162
|
+
// mock or an in-process driver), in which case downstream
|
|
163
|
+
// consumers treat the absence as "unknown / not plumbed" and
|
|
164
|
+
// skip emitting e.g. `X-Cached-Tokens` rather than reporting a
|
|
165
|
+
// fabricated `0`.
|
|
166
|
+
const chunkWithCached = chunk;
|
|
167
|
+
const finalEvent = {
|
|
175
168
|
text: chunk.text,
|
|
176
169
|
done: true,
|
|
177
170
|
finishReason: chunk.finishReason,
|
|
178
171
|
toolCalls: chunk.toolCalls ?? [],
|
|
179
172
|
thinking: chunk.thinking ?? null,
|
|
173
|
+
thinkingEnabled: chunk.thinkingEnabled,
|
|
180
174
|
numTokens: chunk.numTokens,
|
|
181
175
|
promptTokens: chunk.promptTokens ?? 0,
|
|
182
176
|
reasoningTokens: chunk.reasoningTokens ?? 0,
|
|
183
177
|
rawText: chunk.rawText,
|
|
184
178
|
performance: chunk.performance ?? undefined,
|
|
185
179
|
};
|
|
180
|
+
if (typeof chunkWithCached.cachedTokens === "number") {
|
|
181
|
+
finalEvent.cachedTokens = chunkWithCached.cachedTokens;
|
|
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
|
+
}
|
|
189
|
+
yield finalEvent;
|
|
186
190
|
return;
|
|
187
191
|
}
|
|
188
|
-
|
|
192
|
+
const delta = { text: chunk.text, done: false };
|
|
193
|
+
const isReasoning = getNativeIsReasoning(chunk);
|
|
194
|
+
if (isReasoning !== undefined) {
|
|
195
|
+
delta.isReasoning = isReasoning;
|
|
196
|
+
}
|
|
197
|
+
yield delta;
|
|
189
198
|
}
|
|
190
199
|
}
|
|
191
200
|
}
|
|
192
201
|
finally {
|
|
193
202
|
if (signal != null && onAbort != null) {
|
|
194
203
|
try {
|
|
195
|
-
signal.removeEventListener(
|
|
204
|
+
signal.removeEventListener("abort", onAbort);
|
|
196
205
|
}
|
|
197
206
|
catch {
|
|
198
207
|
// removeEventListener shouldn't throw, but stay defensive —
|
|
@@ -203,205 +212,133 @@ export async function* _runChatStream(startCall, signal) {
|
|
|
203
212
|
}
|
|
204
213
|
}
|
|
205
214
|
/**
|
|
206
|
-
*
|
|
215
|
+
* Build the streaming-model subclass for a native chat model class.
|
|
207
216
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* `
|
|
212
|
-
*
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
217
|
+
* The returned class:
|
|
218
|
+
* - captures the three native callback-based session-streaming methods
|
|
219
|
+
* from `NativeClass.prototype`,
|
|
220
|
+
* - overrides them as `async *` generators delegating to
|
|
221
|
+
* {@link _runChatStream} with identical argument plumbing (including
|
|
222
|
+
* `config ?? null`, `images`, `isError ?? null`, and the `signal`),
|
|
223
|
+
* - overrides `static load` to re-prototype the native instance onto
|
|
224
|
+
* the concrete subclass (`this`) and optionally record the path,
|
|
225
|
+
* - installs a path-backed `applyChatTemplate` when `opts.applyTemplate`
|
|
226
|
+
* (defaulting to `opts.recordModelPath`).
|
|
271
227
|
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
* `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
|
|
275
|
-
* satisfies `SessionCapableModel`.
|
|
228
|
+
* @internal Exported so the VLM wrapper (`@mlx-node/vlm`) builds its
|
|
229
|
+
* `QianfanOCRModel` from the same factory. Not part of the public API.
|
|
276
230
|
*/
|
|
277
|
-
export
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
231
|
+
export function makeStreamingModel(NativeClass, opts) {
|
|
232
|
+
const recordPath = opts.recordModelPath;
|
|
233
|
+
const applyTemplate = opts.applyTemplate ?? recordPath;
|
|
234
|
+
const templateContentPolicy = opts.templateContentPolicy;
|
|
235
|
+
const replayAssistantRawText = opts.replayAssistantRawText ?? false;
|
|
236
|
+
// Capture the native callback-based methods before the subclass
|
|
237
|
+
// overrides below shadow them on the prototype.
|
|
238
|
+
const nativeStart = NativeClass.prototype.chatStreamSessionStart;
|
|
239
|
+
const nativeContinue = NativeClass.prototype.chatStreamSessionContinue;
|
|
240
|
+
const nativeContinueTool = NativeClass.prototype.chatStreamSessionContinueTool;
|
|
241
|
+
// `NativeClass` is structurally a constructor; cast to a concrete
|
|
242
|
+
// constructor type so `class extends` accepts it. Runtime behavior is
|
|
243
|
+
// unchanged — we extend the real native class.
|
|
244
|
+
const Base = NativeClass;
|
|
245
|
+
class StreamingModelImpl extends Base {
|
|
246
|
+
supportsReplayReasoningCapture() {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
replaysAssistantRawText() {
|
|
250
|
+
return replayAssistantRawText;
|
|
251
|
+
}
|
|
252
|
+
static async load(modelPath, ...rest) {
|
|
253
|
+
// Forward any trailing family-specific load options verbatim (e.g.
|
|
254
|
+
// Gemma4's `Gemma4LoadOptions` with `draftModelPath`); families whose
|
|
255
|
+
// native `load` takes only the path receive no extras. The public
|
|
256
|
+
// signature is re-narrowed per family via `Parameters<C['load']>` in
|
|
257
|
+
// the factory return type below.
|
|
258
|
+
const instance = await NativeClass.load(modelPath, ...rest);
|
|
259
|
+
// Use `this.prototype` (not `StreamingModelImpl.prototype`) so the
|
|
260
|
+
// concrete subclass declared per family supplies the prototype and
|
|
261
|
+
// `instanceof ConcreteSubclass` holds.
|
|
262
|
+
Object.setPrototypeOf(instance, this.prototype);
|
|
263
|
+
if (recordPath)
|
|
264
|
+
rememberModelPath(instance, modelPath);
|
|
265
|
+
return instance;
|
|
266
|
+
}
|
|
267
|
+
// The native methods are callback-based, but `Base` is typed as a
|
|
268
|
+
// `SessionCapableModel` constructor (whose streaming methods already
|
|
269
|
+
// return `AsyncGenerator<ChatStreamEvent>`), so these overrides are
|
|
270
|
+
// type-compatible and need no `@ts-expect-error` suppression. The
|
|
271
|
+
// callback bridging happens at runtime via the captured natives.
|
|
272
|
+
async *chatStreamSessionStart(messages, config, signal) {
|
|
273
|
+
yield* _runChatStream((callback) => nativeStart.call(this, messages, (config ?? null), callback), signal);
|
|
274
|
+
}
|
|
275
|
+
async *chatStreamSessionContinue(messages, config, signal) {
|
|
276
|
+
yield* _runChatStream((callback) => nativeContinue.call(this, messages, (config ?? null), callback), signal);
|
|
277
|
+
}
|
|
278
|
+
async *chatStreamSessionContinueTool(messages, config, signal) {
|
|
279
|
+
yield* _runChatStream((callback) => nativeContinueTool.call(this, messages, (config ?? null), callback), signal);
|
|
280
|
+
}
|
|
292
281
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
282
|
+
if (applyTemplate) {
|
|
283
|
+
Object.defineProperty(StreamingModelImpl.prototype, "applyChatTemplate", {
|
|
284
|
+
configurable: true,
|
|
285
|
+
writable: true,
|
|
286
|
+
value(messages, addGenerationPrompt, tools, enableThinking) {
|
|
287
|
+
return applyChatTemplateFromModelPath(this, messages, addGenerationPrompt, tools, enableThinking, templateContentPolicy);
|
|
288
|
+
},
|
|
289
|
+
});
|
|
297
290
|
}
|
|
291
|
+
return StreamingModelImpl;
|
|
298
292
|
}
|
|
299
293
|
/**
|
|
300
|
-
*
|
|
294
|
+
* Qwen3.5 dense model with AsyncGenerator-based session streaming.
|
|
301
295
|
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* `
|
|
305
|
-
*
|
|
306
|
-
* `images` guard rejects non-empty image sets with an
|
|
307
|
-
* `IMAGE_CHANGE_REQUIRES_SESSION_RESTART:` prefix.
|
|
296
|
+
* The empty `extends` inherits the factory's streaming overrides,
|
|
297
|
+
* `static load`, and `applyChatTemplate`, and supplies the concrete
|
|
298
|
+
* `.name === 'Qwen35Model'` and a working `instanceof`. Records its
|
|
299
|
+
* model path so `applyChatTemplate` can serve a lazily built tokenizer.
|
|
308
300
|
*/
|
|
309
|
-
export class
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
}
|
|
301
|
+
export class Qwen35Model extends makeStreamingModel(Qwen35ModelNative, {
|
|
302
|
+
recordModelPath: true,
|
|
303
|
+
}) {
|
|
330
304
|
}
|
|
331
|
-
/**
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
-
}
|
|
305
|
+
/** Qwen3.5 MoE model — see {@link Qwen35Model} for the wrapper shape. */
|
|
306
|
+
export class Qwen35MoeModel extends makeStreamingModel(Qwen35MoeModelNative, {
|
|
307
|
+
recordModelPath: true,
|
|
308
|
+
}) {
|
|
309
|
+
}
|
|
310
|
+
/** LFM2 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
311
|
+
export class Lfm2Model extends makeStreamingModel(Lfm2ModelNative, {
|
|
312
|
+
recordModelPath: true,
|
|
313
|
+
replayAssistantRawText: true,
|
|
314
|
+
}) {
|
|
315
|
+
}
|
|
316
|
+
/** Gemma4 model (text-only) — see {@link Qwen35Model} for the wrapper shape. */
|
|
317
|
+
export class Gemma4Model extends makeStreamingModel(Gemma4ModelNative, {
|
|
318
|
+
recordModelPath: true,
|
|
319
|
+
}) {
|
|
362
320
|
}
|
|
363
321
|
/**
|
|
364
|
-
* Qwen3 (
|
|
322
|
+
* Qwen3 (first-gen, text-only) model.
|
|
365
323
|
*
|
|
366
|
-
*
|
|
367
|
-
*
|
|
368
|
-
* `
|
|
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.
|
|
324
|
+
* Records its model path (so prototype-set + path-recording match the
|
|
325
|
+
* other families) but does not install the factory's path-backed
|
|
326
|
+
* `applyChatTemplate`; it retains the native tokenizer-backed method.
|
|
372
327
|
*/
|
|
373
|
-
export class Qwen3Model extends Qwen3ModelNative {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
}
|
|
328
|
+
export class Qwen3Model extends makeStreamingModel(Qwen3ModelNative, {
|
|
329
|
+
recordModelPath: true,
|
|
330
|
+
applyTemplate: false,
|
|
331
|
+
}) {
|
|
394
332
|
}
|
|
395
333
|
// -------------------------------------------------------------------
|
|
396
334
|
// Compile-time conformance check
|
|
397
335
|
// -------------------------------------------------------------------
|
|
398
336
|
//
|
|
399
|
-
// Ensures each
|
|
400
|
-
// `SessionCapableModel` so `ChatSession<XxxModel>`
|
|
401
|
-
// downstream code.
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
// fail to compile this block, surfacing the regression at build time.
|
|
337
|
+
// Ensures each family class structurally satisfies
|
|
338
|
+
// `SessionCapableModel` so `ChatSession<XxxModel>` type-checks in
|
|
339
|
+
// downstream code. Compile-only — the `null as unknown as T`
|
|
340
|
+
// placeholder never runs. If a factory override signature drifts away
|
|
341
|
+
// from the interface, this block fails to compile.
|
|
405
342
|
function _assertSessionCapable() {
|
|
406
343
|
const _qwen35 = null;
|
|
407
344
|
const _moe = null;
|
|
@@ -415,3 +352,29 @@ function _assertSessionCapable() {
|
|
|
415
352
|
void _qwen3;
|
|
416
353
|
}
|
|
417
354
|
void _assertSessionCapable;
|
|
355
|
+
/** Compile-time guard that both Qwen3.5 native classes and wrappers retain the exact media planner. */
|
|
356
|
+
function _assertExpandedPromptPlannerSurfaces() {
|
|
357
|
+
const _nativeDense = null;
|
|
358
|
+
const _nativeMoe = null;
|
|
359
|
+
const _wrappedDense = null;
|
|
360
|
+
const _wrappedMoe = null;
|
|
361
|
+
void _nativeDense;
|
|
362
|
+
void _nativeMoe;
|
|
363
|
+
void _wrappedDense;
|
|
364
|
+
void _wrappedMoe;
|
|
365
|
+
}
|
|
366
|
+
void _assertExpandedPromptPlannerSurfaces;
|
|
367
|
+
/** Compile-time guard that the factory preserves every non-streaming native member. */
|
|
368
|
+
function _assertPreservedNativeSurfaces() {
|
|
369
|
+
const _qwen3 = null;
|
|
370
|
+
const _qwen35 = null;
|
|
371
|
+
const _moe = null;
|
|
372
|
+
const _lfm2 = null;
|
|
373
|
+
const _gemma4 = null;
|
|
374
|
+
void _qwen3;
|
|
375
|
+
void _qwen35;
|
|
376
|
+
void _moe;
|
|
377
|
+
void _lfm2;
|
|
378
|
+
void _gemma4;
|
|
379
|
+
}
|
|
380
|
+
void _assertPreservedNativeSurfaces;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlx-node/lm",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
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.
|
|
31
|
+
"@mlx-node/core": "0.0.9"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@types/node": "^
|
|
34
|
+
"@types/node": "^26.0.0"
|
|
35
35
|
}
|
|
36
36
|
}
|