@mlx-node/server 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.
- package/dist/auth.d.ts +56 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +106 -0
- package/dist/chat-session-warm-reuse.d.ts +8 -8
- package/dist/chat-session-warm-reuse.d.ts.map +1 -1
- package/dist/chat-session-warm-reuse.js +12 -8
- package/dist/endpoints/responses.d.ts +3 -1
- package/dist/endpoints/responses.d.ts.map +1 -1
- package/dist/endpoints/responses.js +38 -5
- package/dist/handler.d.ts +27 -1
- package/dist/handler.d.ts.map +1 -1
- package/dist/handler.js +65 -16
- package/dist/health.d.ts +146 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +107 -0
- package/dist/host/discover.d.ts +19 -0
- package/dist/host/discover.d.ts.map +1 -0
- package/dist/host/discover.js +50 -0
- package/dist/host/env-policy.d.ts +62 -0
- package/dist/host/env-policy.d.ts.map +1 -0
- package/dist/host/env-policy.js +69 -0
- package/dist/host/index.d.ts +202 -0
- package/dist/host/index.d.ts.map +1 -0
- package/dist/host/index.js +325 -0
- package/dist/host/logger.d.ts +36 -0
- package/dist/host/logger.d.ts.map +1 -0
- package/dist/host/logger.js +376 -0
- package/dist/host/net.d.ts +65 -0
- package/dist/host/net.d.ts.map +1 -0
- package/dist/host/net.js +97 -0
- package/dist/host/paths.d.ts +28 -0
- package/dist/host/paths.d.ts.map +1 -0
- package/dist/host/paths.js +71 -0
- package/dist/host/swap.d.ts +27 -0
- package/dist/host/swap.d.ts.map +1 -0
- package/dist/host/swap.js +178 -0
- package/dist/host/temp-root.d.ts +57 -0
- package/dist/host/temp-root.d.ts.map +1 -0
- package/dist/host/temp-root.js +99 -0
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/load-model.d.ts +69 -0
- package/dist/load-model.d.ts.map +1 -0
- package/dist/load-model.js +63 -0
- package/dist/model-work-coordinator.d.ts +29 -4
- package/dist/model-work-coordinator.d.ts.map +1 -1
- package/dist/model-work-coordinator.js +97 -16
- package/dist/router.d.ts +34 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -5
- package/dist/server.d.ts +117 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +125 -9
- package/dist/session-registry.d.ts +7 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/session-registry.js +9 -0
- package/dist/streaming.d.ts +14 -0
- package/dist/streaming.d.ts.map +1 -1
- package/dist/streaming.js +45 -0
- package/package.json +15 -3
package/dist/server.js
CHANGED
|
@@ -5,11 +5,23 @@ import { homedir } from 'node:os';
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { ResponseStore } from '@mlx-node/core';
|
|
7
7
|
import { createHandler } from './handler.js';
|
|
8
|
+
import { createHealthReporter } from './health.js';
|
|
8
9
|
import { createIdleSweeper, DEFAULT_IDLE_CLEAR_CACHE_MS, parseIdleClearCacheEnv } from './idle-sweeper.js';
|
|
10
|
+
import { runGuardedModelLoad } from './load-model.js';
|
|
9
11
|
import { ModelWorkCoordinator } from './model-work-coordinator.js';
|
|
10
12
|
import { ModelRegistry } from './registry.js';
|
|
13
|
+
import { activeSSEStreamCountForResponses } from './streaming.js';
|
|
11
14
|
/** Cleanup interval for expired responses (ms). */
|
|
12
15
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
|
|
16
|
+
/**
|
|
17
|
+
* Default grace period before {@link ServerInstance.close} destroys
|
|
18
|
+
* still-open connections.
|
|
19
|
+
*
|
|
20
|
+
* 5 s is deliberately a little longer than the ~5 s GPU watchdog window: a
|
|
21
|
+
* decode loop that is about to yield its next token should get the chance to
|
|
22
|
+
* unwind cleanly rather than being cut off a hair early.
|
|
23
|
+
*/
|
|
24
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 5000;
|
|
13
25
|
/**
|
|
14
26
|
* Default retention for persisted response rows in SQLite, in seconds.
|
|
15
27
|
*
|
|
@@ -84,6 +96,42 @@ function normalizePositiveIntConfig(value, name) {
|
|
|
84
96
|
}
|
|
85
97
|
return value;
|
|
86
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve the effective auth token from an explicit value plus the env
|
|
101
|
+
* fallback.
|
|
102
|
+
*
|
|
103
|
+
* Exported because `createInferenceHost` has to answer "is this server going
|
|
104
|
+
* to be protected?" BEFORE it binds, in order to refuse a non-loopback bind
|
|
105
|
+
* that would serve anonymously. Two independent copies of this rule would
|
|
106
|
+
* drift, and the direction it would drift is a host that refuses to start
|
|
107
|
+
* while `MLX_SERVER_AUTH_TOKEN` is sitting right there in the environment.
|
|
108
|
+
*
|
|
109
|
+
* An empty env var means "not set". An accidental `MLX_SERVER_AUTH_TOKEN=` in
|
|
110
|
+
* a launcher script must not enable auth with an empty secret that every
|
|
111
|
+
* credential-less request would then fail against.
|
|
112
|
+
*
|
|
113
|
+
* An empty EXPLICIT token is a different case and is rejected outright, for the
|
|
114
|
+
* same reason {@link normalizePositiveIntConfig} rejects a bogus explicit knob:
|
|
115
|
+
* somebody asked for a token and supplied nothing, which is
|
|
116
|
+
* `--auth-token "$TOKEN"` with `TOKEN` unset. Returning `''` made the bind
|
|
117
|
+
* guard read "auth is configured" and allow `0.0.0.0`, while the comparator
|
|
118
|
+
* accepted an empty `x-api-key` because both strings were empty — a wildcard
|
|
119
|
+
* bind published under a credential anyone can guess. Quietly downgrading to
|
|
120
|
+
* "no auth" instead would be fail-open in the other direction: on loopback it
|
|
121
|
+
* hands back the unauthenticated, wildcard-CORS server the operator was
|
|
122
|
+
* explicitly trying not to start. Throwing is the only answer that is wrong in
|
|
123
|
+
* neither bind mode, and it happens before anything binds or loads.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveAuthToken(explicit) {
|
|
126
|
+
if (explicit === '') {
|
|
127
|
+
throw new Error('authToken was set to an empty string; pass a real secret or omit it entirely ' +
|
|
128
|
+
'(an unset shell variable in `--auth-token "$VAR"` is the usual cause).');
|
|
129
|
+
}
|
|
130
|
+
if (explicit !== undefined)
|
|
131
|
+
return explicit;
|
|
132
|
+
const fromEnv = process.env.MLX_SERVER_AUTH_TOKEN;
|
|
133
|
+
return fromEnv != null && fromEnv !== '' ? fromEnv : undefined;
|
|
134
|
+
}
|
|
87
135
|
/**
|
|
88
136
|
* Start an MLX-Node HTTP server exposing `POST /v1/responses`,
|
|
89
137
|
* `POST /v1/messages`, and `GET /v1/models`.
|
|
@@ -97,7 +145,10 @@ function normalizePositiveIntConfig(value, name) {
|
|
|
97
145
|
export async function createServer(config) {
|
|
98
146
|
const port = config?.port ?? 8080;
|
|
99
147
|
const host = config?.host ?? '127.0.0.1';
|
|
100
|
-
const
|
|
148
|
+
const authToken = resolveAuthToken(config?.authToken);
|
|
149
|
+
// Forwarded unresolved so `createHandler` applies the auth-aware default
|
|
150
|
+
// (`true` without a token, `false` with one) in exactly one place.
|
|
151
|
+
const cors = config?.cors;
|
|
101
152
|
const disableStore = config?.disableStore ?? false;
|
|
102
153
|
// Validate caller-supplied numeric knobs BEFORE consulting env fallbacks
|
|
103
154
|
// so a bogus explicit value surfaces as a descriptive error instead of
|
|
@@ -139,6 +190,9 @@ export async function createServer(config) {
|
|
|
139
190
|
}
|
|
140
191
|
}, CLEANUP_INTERVAL_MS);
|
|
141
192
|
cleanupTimer.unref();
|
|
193
|
+
// One reporter shared by the HTTP endpoint and `ServerInstance.health()`
|
|
194
|
+
// so both report the same uptime origin.
|
|
195
|
+
const health = createHealthReporter({ registry, idleSweeper, modelWorkCoordinator });
|
|
142
196
|
const handler = createHandler(registry, {
|
|
143
197
|
cors,
|
|
144
198
|
store,
|
|
@@ -147,8 +201,22 @@ export async function createServer(config) {
|
|
|
147
201
|
resolveModel: config?.resolveModel,
|
|
148
202
|
modelWorkCoordinator,
|
|
149
203
|
listModels: config?.listModels,
|
|
204
|
+
authToken,
|
|
205
|
+
health,
|
|
206
|
+
});
|
|
207
|
+
/**
|
|
208
|
+
* Responses owned by THIS HTTP server. The SSE registry is deliberately
|
|
209
|
+
* process-wide because `beginSSE` is also used by standalone handlers;
|
|
210
|
+
* shutdown accounting intersects it with this weak ownership set so one
|
|
211
|
+
* server cannot claim streams that another server leaves live. A WeakSet
|
|
212
|
+
* needs no second response cleanup lifecycle.
|
|
213
|
+
*/
|
|
214
|
+
const ownedResponses = new WeakSet();
|
|
215
|
+
const server = httpCreateServer((req, res) => {
|
|
216
|
+
// Synchronous, before `handler` can reach either endpoint's `beginSSE`.
|
|
217
|
+
ownedResponses.add(res);
|
|
218
|
+
void handler(req, res);
|
|
150
219
|
});
|
|
151
|
-
const server = httpCreateServer(handler);
|
|
152
220
|
await new Promise((resolve, reject) => {
|
|
153
221
|
const onError = (err) => {
|
|
154
222
|
server.removeListener('error', onError);
|
|
@@ -160,21 +228,69 @@ export async function createServer(config) {
|
|
|
160
228
|
resolve();
|
|
161
229
|
});
|
|
162
230
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
231
|
+
// Memoized so a second `close()` returns the first call's promise instead
|
|
232
|
+
// of re-entering `server.close()` (which invokes its callback with
|
|
233
|
+
// ERR_SERVER_NOT_RUNNING once the server is already down).
|
|
234
|
+
let closePromise = null;
|
|
235
|
+
const close = (opts) => {
|
|
236
|
+
if (closePromise !== null)
|
|
237
|
+
return closePromise;
|
|
238
|
+
const startedAt = Date.now();
|
|
239
|
+
const requested = opts?.timeoutMs;
|
|
240
|
+
const timeoutMs = typeof requested === 'number' && Number.isFinite(requested) && requested >= 0
|
|
241
|
+
? requested
|
|
242
|
+
: DEFAULT_CLOSE_TIMEOUT_MS;
|
|
243
|
+
closePromise = (async () => {
|
|
168
244
|
clearInterval(cleanupTimer);
|
|
169
245
|
idleSweeper.close();
|
|
170
|
-
|
|
246
|
+
let forced = false;
|
|
247
|
+
let streamsAborted = 0;
|
|
248
|
+
// Arm the wait BEFORE dropping idle sockets so nothing can complete in
|
|
249
|
+
// the gap and settle `server.close()` before we are listening.
|
|
250
|
+
const serverClosed = new Promise((resolve, reject) => {
|
|
171
251
|
server.close((err) => {
|
|
172
|
-
|
|
252
|
+
// Tolerated defensively: memoization should make this unreachable,
|
|
253
|
+
// but a caller who also closed `instance.server` directly would
|
|
254
|
+
// otherwise turn a successful shutdown into a rejection.
|
|
255
|
+
if (err && err.code !== 'ERR_SERVER_NOT_RUNNING')
|
|
173
256
|
reject(err);
|
|
174
257
|
else
|
|
175
258
|
resolve();
|
|
176
259
|
});
|
|
177
260
|
});
|
|
261
|
+
// Keep-alive sockets parked in a client pool hold no request; without
|
|
262
|
+
// this the shutdown would sit on them until the client's own timeout.
|
|
263
|
+
server.closeIdleConnections();
|
|
264
|
+
const forceTimer = setTimeout(() => {
|
|
265
|
+
forced = true;
|
|
266
|
+
// Snapshot BEFORE destroying: `closeAllConnections()` fires each
|
|
267
|
+
// response's `'close'` event, which unregisters it from the global SSE
|
|
268
|
+
// registry used by the intersection.
|
|
269
|
+
streamsAborted = activeSSEStreamCountForResponses(ownedResponses);
|
|
270
|
+
// Destroying the socket fires exactly the `res.on('close')` path a
|
|
271
|
+
// client disconnect fires, so the endpoint's AbortController cancels
|
|
272
|
+
// the native stream handle rather than leaking a running decode.
|
|
273
|
+
server.closeAllConnections();
|
|
274
|
+
}, timeoutMs);
|
|
275
|
+
try {
|
|
276
|
+
await serverClosed;
|
|
277
|
+
}
|
|
278
|
+
finally {
|
|
279
|
+
clearTimeout(forceTimer);
|
|
280
|
+
}
|
|
281
|
+
return { forced, streamsAborted, durationMs: Date.now() - startedAt };
|
|
282
|
+
})();
|
|
283
|
+
return closePromise;
|
|
284
|
+
};
|
|
285
|
+
return {
|
|
286
|
+
server,
|
|
287
|
+
registry,
|
|
288
|
+
store,
|
|
289
|
+
modelWork: modelWorkCoordinator,
|
|
290
|
+
health,
|
|
291
|
+
close,
|
|
292
|
+
loadModel(opts) {
|
|
293
|
+
return runGuardedModelLoad({ idleSweeper, modelWorkCoordinator, registry }, opts);
|
|
178
294
|
},
|
|
179
295
|
withSuspendedDrains(fn) {
|
|
180
296
|
return idleSweeper.withSuspendedDrains(fn);
|
|
@@ -281,6 +281,13 @@ export declare class SessionRegistry {
|
|
|
281
281
|
* `fn`. Primarily for tests and diagnostics.
|
|
282
282
|
*/
|
|
283
283
|
get queueDepth(): number;
|
|
284
|
+
/**
|
|
285
|
+
* Configured waiter cap for this model's execution mutex, or `undefined`
|
|
286
|
+
* when unbounded. Paired with {@link queueDepth} so a readiness probe can
|
|
287
|
+
* tell "3 waiters, unbounded" (fine) from "3 waiters, cap of 3" (the next
|
|
288
|
+
* request gets a 429) without reaching into private state.
|
|
289
|
+
*/
|
|
290
|
+
get queueDepthLimit(): number | undefined;
|
|
284
291
|
/**
|
|
285
292
|
* Current sampling defaults applied to every new `ChatSession` this
|
|
286
293
|
* registry allocates. Exposed primarily for tests and diagnostics.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"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"}
|
package/dist/session-registry.js
CHANGED
|
@@ -406,6 +406,15 @@ export class SessionRegistry {
|
|
|
406
406
|
get queueDepth() {
|
|
407
407
|
return this.queuedCount;
|
|
408
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Configured waiter cap for this model's execution mutex, or `undefined`
|
|
411
|
+
* when unbounded. Paired with {@link queueDepth} so a readiness probe can
|
|
412
|
+
* tell "3 waiters, unbounded" (fine) from "3 waiters, cap of 3" (the next
|
|
413
|
+
* request gets a 429) without reaching into private state.
|
|
414
|
+
*/
|
|
415
|
+
get queueDepthLimit() {
|
|
416
|
+
return this.maxQueueDepth;
|
|
417
|
+
}
|
|
409
418
|
/**
|
|
410
419
|
* Current sampling defaults applied to every new `ChatSession` this
|
|
411
420
|
* registry allocates. Exposed primarily for tests and diagnostics.
|
package/dist/streaming.d.ts
CHANGED
|
@@ -4,4 +4,18 @@ export declare function beginSSE(res: ServerResponse): void;
|
|
|
4
4
|
/** Write one SSE event. Injects `type: eventType` into the payload (data's own `type` wins) for OpenAI SDK compatibility. */
|
|
5
5
|
export declare function writeSSEEvent(res: ServerResponse, eventType: string, data: object): void;
|
|
6
6
|
export declare function endSSE(res: ServerResponse): void;
|
|
7
|
+
/**
|
|
8
|
+
* Number of SSE streams currently open process-wide. Diagnostics and
|
|
9
|
+
* shutdown accounting only.
|
|
10
|
+
*/
|
|
11
|
+
export declare function activeSSEStreamCount(): number;
|
|
12
|
+
/**
|
|
13
|
+
* Number of active SSE streams among a caller-owned collection of responses.
|
|
14
|
+
*
|
|
15
|
+
* `createServer()` uses this to intersect the process-wide SSE registry with
|
|
16
|
+
* the responses accepted by one `node:http` Server. Keep the no-argument
|
|
17
|
+
* {@link activeSSEStreamCount} above for process-wide diagnostics and
|
|
18
|
+
* standalone `createHandler()` consumers.
|
|
19
|
+
*/
|
|
20
|
+
export declare function activeSSEStreamCountForResponses(responses: WeakSet<ServerResponse>): number;
|
|
7
21
|
//# sourceMappingURL=streaming.d.ts.map
|
package/dist/streaming.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAE5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAgBhD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAclD;AAED,6HAA6H;AAC7H,wBAAgB,aAAa,CAAC,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAGxF;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,CAGhD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM,CAM3F"}
|
package/dist/streaming.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
/** SSE writer utilities. */
|
|
2
|
+
/**
|
|
3
|
+
* Responses that have committed to SSE (`beginSSE`) but have not yet been
|
|
4
|
+
* ended (`endSSE`) or had their connection torn down.
|
|
5
|
+
*
|
|
6
|
+
* Purely an accounting aid for {@link activeSSEStreamCount}, which graceful
|
|
7
|
+
* shutdown reads to report how many streams a forced close cut short. Nothing
|
|
8
|
+
* in the request path branches on membership, so a miscount can only skew a
|
|
9
|
+
* diagnostic number — never the behaviour of a live stream.
|
|
10
|
+
*
|
|
11
|
+
* Module-scoped because `@mlx-node/server` is loaded once per process and
|
|
12
|
+
* `beginSSE`/`endSSE` are free functions called from both endpoints.
|
|
13
|
+
*/
|
|
14
|
+
const activeSSEResponses = new Set();
|
|
2
15
|
export function beginSSE(res) {
|
|
16
|
+
activeSSEResponses.add(res);
|
|
17
|
+
// Belt-and-braces cleanup: a stream torn down by a client disconnect (or by
|
|
18
|
+
// `server.closeAllConnections()`) may unwind through an error path that
|
|
19
|
+
// never reaches `endSSE`. Without this the entry would leak for the life of
|
|
20
|
+
// the process and inflate the count forever.
|
|
21
|
+
res.once('close', () => {
|
|
22
|
+
activeSSEResponses.delete(res);
|
|
23
|
+
});
|
|
3
24
|
res.writeHead(200, {
|
|
4
25
|
'Content-Type': 'text/event-stream',
|
|
5
26
|
'Cache-Control': 'no-cache',
|
|
@@ -12,5 +33,29 @@ export function writeSSEEvent(res, eventType, data) {
|
|
|
12
33
|
res.write(`event: ${eventType}\ndata: ${JSON.stringify(payload)}\n\n`);
|
|
13
34
|
}
|
|
14
35
|
export function endSSE(res) {
|
|
36
|
+
activeSSEResponses.delete(res);
|
|
15
37
|
res.end();
|
|
16
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Number of SSE streams currently open process-wide. Diagnostics and
|
|
41
|
+
* shutdown accounting only.
|
|
42
|
+
*/
|
|
43
|
+
export function activeSSEStreamCount() {
|
|
44
|
+
return activeSSEResponses.size;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Number of active SSE streams among a caller-owned collection of responses.
|
|
48
|
+
*
|
|
49
|
+
* `createServer()` uses this to intersect the process-wide SSE registry with
|
|
50
|
+
* the responses accepted by one `node:http` Server. Keep the no-argument
|
|
51
|
+
* {@link activeSSEStreamCount} above for process-wide diagnostics and
|
|
52
|
+
* standalone `createHandler()` consumers.
|
|
53
|
+
*/
|
|
54
|
+
export function activeSSEStreamCountForResponses(responses) {
|
|
55
|
+
let count = 0;
|
|
56
|
+
for (const response of activeSSEResponses) {
|
|
57
|
+
if (responses.has(response))
|
|
58
|
+
count += 1;
|
|
59
|
+
}
|
|
60
|
+
return count;
|
|
61
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlx-node/server",
|
|
3
|
-
"version": "0.0.
|
|
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"
|
|
@@ -21,14 +21,26 @@
|
|
|
21
21
|
".": {
|
|
22
22
|
"types": "./dist/index.d.ts",
|
|
23
23
|
"import": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./host": {
|
|
26
|
+
"types": "./dist/host/index.d.ts",
|
|
27
|
+
"import": "./dist/host/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./host/env-policy": {
|
|
30
|
+
"types": "./dist/host/env-policy.d.ts",
|
|
31
|
+
"import": "./dist/host/env-policy.js"
|
|
32
|
+
},
|
|
33
|
+
"./host/paths": {
|
|
34
|
+
"types": "./dist/host/paths.d.ts",
|
|
35
|
+
"import": "./dist/host/paths.js"
|
|
24
36
|
}
|
|
25
37
|
},
|
|
26
38
|
"scripts": {
|
|
27
39
|
"build": "tsc -b"
|
|
28
40
|
},
|
|
29
41
|
"dependencies": {
|
|
30
|
-
"@mlx-node/core": "0.0.
|
|
31
|
-
"@mlx-node/lm": "0.0.
|
|
42
|
+
"@mlx-node/core": "0.0.10",
|
|
43
|
+
"@mlx-node/lm": "0.0.10"
|
|
32
44
|
},
|
|
33
45
|
"devDependencies": {
|
|
34
46
|
"@types/node": "@types/node@24.12.2"
|