@mlx-node/server 0.0.8 → 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.
Files changed (61) hide show
  1. package/dist/auth.d.ts +56 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +106 -0
  4. package/dist/chat-session-warm-reuse.d.ts +8 -8
  5. package/dist/chat-session-warm-reuse.d.ts.map +1 -1
  6. package/dist/chat-session-warm-reuse.js +12 -8
  7. package/dist/endpoints/responses.d.ts +3 -1
  8. package/dist/endpoints/responses.d.ts.map +1 -1
  9. package/dist/endpoints/responses.js +38 -5
  10. package/dist/handler.d.ts +27 -1
  11. package/dist/handler.d.ts.map +1 -1
  12. package/dist/handler.js +65 -16
  13. package/dist/health.d.ts +146 -0
  14. package/dist/health.d.ts.map +1 -0
  15. package/dist/health.js +107 -0
  16. package/dist/host/discover.d.ts +19 -0
  17. package/dist/host/discover.d.ts.map +1 -0
  18. package/dist/host/discover.js +50 -0
  19. package/dist/host/env-policy.d.ts +62 -0
  20. package/dist/host/env-policy.d.ts.map +1 -0
  21. package/dist/host/env-policy.js +69 -0
  22. package/dist/host/index.d.ts +202 -0
  23. package/dist/host/index.d.ts.map +1 -0
  24. package/dist/host/index.js +325 -0
  25. package/dist/host/logger.d.ts +36 -0
  26. package/dist/host/logger.d.ts.map +1 -0
  27. package/dist/host/logger.js +376 -0
  28. package/dist/host/net.d.ts +65 -0
  29. package/dist/host/net.d.ts.map +1 -0
  30. package/dist/host/net.js +97 -0
  31. package/dist/host/paths.d.ts +28 -0
  32. package/dist/host/paths.d.ts.map +1 -0
  33. package/dist/host/paths.js +71 -0
  34. package/dist/host/swap.d.ts +27 -0
  35. package/dist/host/swap.d.ts.map +1 -0
  36. package/dist/host/swap.js +178 -0
  37. package/dist/host/temp-root.d.ts +57 -0
  38. package/dist/host/temp-root.d.ts.map +1 -0
  39. package/dist/host/temp-root.js +99 -0
  40. package/dist/index.d.ts +11 -2
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7 -1
  43. package/dist/load-model.d.ts +69 -0
  44. package/dist/load-model.d.ts.map +1 -0
  45. package/dist/load-model.js +63 -0
  46. package/dist/model-work-coordinator.d.ts +29 -4
  47. package/dist/model-work-coordinator.d.ts.map +1 -1
  48. package/dist/model-work-coordinator.js +97 -16
  49. package/dist/router.d.ts +34 -1
  50. package/dist/router.d.ts.map +1 -1
  51. package/dist/router.js +47 -5
  52. package/dist/server.d.ts +117 -3
  53. package/dist/server.d.ts.map +1 -1
  54. package/dist/server.js +125 -9
  55. package/dist/session-registry.d.ts +7 -0
  56. package/dist/session-registry.d.ts.map +1 -1
  57. package/dist/session-registry.js +9 -0
  58. package/dist/streaming.d.ts +14 -0
  59. package/dist/streaming.d.ts.map +1 -1
  60. package/dist/streaming.js +45 -0
  61. package/package.json +15 -3
package/dist/health.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Server readiness reporting for supervisors (e.g. an Electron app running
3
+ * the inference server as a child process).
4
+ *
5
+ * The old `/health` returned a constant `{ status: 'ok' }`, which cannot
6
+ * distinguish four states a supervisor genuinely needs to tell apart:
7
+ *
8
+ * - up, nothing loaded → keep waiting, this is normal
9
+ * - up, model resident → route traffic
10
+ * - wedged mid-load → keep waiting, do NOT restart
11
+ * - wedged behind a full queue → shed load / warn the user
12
+ *
13
+ * Every input already exists in-process; this module only exposes it. The
14
+ * status ladder itself is a PURE function ({@link deriveHealthStatus}) over a
15
+ * plain record so it can be unit-tested without a server, a registry, or the
16
+ * native addon.
17
+ *
18
+ * IMPORTANT: nothing here may call into `@mlx-node/core`. `/health` is
19
+ * deliberately excluded from the idle sweeper's `beginRequest`/`endRequest`
20
+ * bracket (see `HandlerOptions.idleSweeper`), so a native call from this path
21
+ * would run outside the in-flight accounting the drain timer relies on.
22
+ * Every field below is read from plain JavaScript state.
23
+ */
24
+ /**
25
+ * Pure status ladder. No I/O, no clock, no native calls — just the four
26
+ * documented rungs, in precedence order:
27
+ *
28
+ * 1. `writerActive` → 'loading'
29
+ * 2. last load failed AND nothing resident → 'error'
30
+ * 3. queue saturated, OR a load is parked
31
+ * behind live inference → 'degraded'
32
+ * 4. otherwise → 'ok'
33
+ *
34
+ * `'loading'` outranks `'error'` on purpose: a retry that already holds the
35
+ * writer slot means the supervisor should wait, not restart the process.
36
+ */
37
+ export function deriveHealthStatus(input) {
38
+ if (input.writerActive)
39
+ return 'loading';
40
+ if (input.lastLoad?.ok === false && input.residentModelCount === 0)
41
+ return 'error';
42
+ if (input.queueSaturated)
43
+ return 'degraded';
44
+ // A writer parked while readers are still running means the swap cannot
45
+ // proceed until they drain, and every request for the incoming model
46
+ // stalls behind it. Either condition alone is an ordinary transient.
47
+ if (input.waitingWriters > 0 && input.inFlight > 0)
48
+ return 'degraded';
49
+ return 'ok';
50
+ }
51
+ /** Project the full body down to the three fields safe to serve without a token. */
52
+ export function toMinimalHealth(health) {
53
+ return { status: health.status, uptimeMs: health.uptimeMs, pid: health.pid };
54
+ }
55
+ /**
56
+ * Build a zero-argument reporter closing over the live server objects. Each
57
+ * call re-reads current state; nothing is cached, so a supervisor polling on
58
+ * an interval always sees the present moment.
59
+ */
60
+ export function createHealthReporter(deps) {
61
+ const now = deps.now ?? (() => Date.now());
62
+ const startedAt = deps.startedAt ?? now();
63
+ return () => {
64
+ const resident = deps.registry.list().map((entry) => entry.id);
65
+ // Queue saturation is inherently per-model: one wedged model should
66
+ // surface even while others are idle. We report the WORST case.
67
+ let depth = 0;
68
+ let limit = null;
69
+ let saturated = false;
70
+ for (const sessionRegistry of deps.registry.listSessionRegistries()) {
71
+ const registryDepth = sessionRegistry.queueDepth;
72
+ if (registryDepth > depth)
73
+ depth = registryDepth;
74
+ const registryLimit = sessionRegistry.queueDepthLimit;
75
+ if (registryLimit !== undefined) {
76
+ if (limit === null || registryLimit < limit)
77
+ limit = registryLimit;
78
+ if (registryDepth >= registryLimit)
79
+ saturated = true;
80
+ }
81
+ }
82
+ const writerActive = deps.modelWorkCoordinator?.writerActive ?? false;
83
+ const waitingWriters = deps.modelWorkCoordinator?.waitingWriters ?? 0;
84
+ const lastLoad = deps.modelWorkCoordinator?.lastLoad ?? null;
85
+ const inFlight = deps.idleSweeper?.inFlight ?? 0;
86
+ const drainPending = deps.idleSweeper?.isPending ?? false;
87
+ const status = deriveHealthStatus({
88
+ writerActive,
89
+ waitingWriters,
90
+ inFlight,
91
+ residentModelCount: resident.length,
92
+ queueSaturated: saturated,
93
+ lastLoad,
94
+ });
95
+ return {
96
+ status,
97
+ // Clamped: a backwards clock step (NTP, fake timers in a sibling test)
98
+ // must not surface a negative uptime a supervisor might read as a wrap.
99
+ uptimeMs: Math.max(0, now() - startedAt),
100
+ pid: process.pid,
101
+ models: { resident, count: resident.length },
102
+ work: { inFlight, drainPending, writerActive, waitingWriters },
103
+ queue: { depth, limit, saturated },
104
+ lastLoad,
105
+ };
106
+ };
107
+ }
@@ -0,0 +1,19 @@
1
+ /** Discover locally-downloaded generative models under a given directory. */
2
+ import { type ModelType } from '@mlx-node/lm';
3
+ import { type LaunchPreset } from '../presets.js';
4
+ /** A locally-downloaded model paired with its sampling preset. */
5
+ export interface DiscoveredModel {
6
+ name: string;
7
+ path: string;
8
+ modelType: ModelType;
9
+ preset: LaunchPreset;
10
+ }
11
+ /**
12
+ * Scan `dir` for model subdirectories. Each subdirectory with a recognized
13
+ * `config.json` is returned with its inferred `ModelType` and `LaunchPreset`.
14
+ * Non-generative types are silently skipped. Entries with no preset or an
15
+ * undetectable config are skipped (warnings only emitted when `MLX_DEBUG`
16
+ * is set). Must stay cheap — do not load weights here.
17
+ */
18
+ export declare function discoverModels(dir: string): Promise<DiscoveredModel[]>;
19
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../../src/host/discover.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAM7E,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE/D,OAAO,EAAkB,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAElE,kEAAkE;AAClE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;CACtB;AAKD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAoC5E"}
@@ -0,0 +1,50 @@
1
+ /** Discover locally-downloaded generative models under a given directory. */
2
+ import { readdir } from 'node:fs/promises';
3
+ import { basename, join } from 'node:path';
4
+ import { detectModelType } from '@mlx-node/lm';
5
+ import { LAUNCH_PRESETS } from '../presets.js';
6
+ // Non-generative detection results that cannot back a chat endpoint.
7
+ const NON_GENERATIVE = new Set(['harrier', 'qianfan-ocr', 'internvl_chat']);
8
+ /**
9
+ * Scan `dir` for model subdirectories. Each subdirectory with a recognized
10
+ * `config.json` is returned with its inferred `ModelType` and `LaunchPreset`.
11
+ * Non-generative types are silently skipped. Entries with no preset or an
12
+ * undetectable config are skipped (warnings only emitted when `MLX_DEBUG`
13
+ * is set). Must stay cheap — do not load weights here.
14
+ */
15
+ export async function discoverModels(dir) {
16
+ const debug = Boolean(process.env.MLX_DEBUG);
17
+ let entries;
18
+ try {
19
+ entries = await readdir(dir, { withFileTypes: true });
20
+ }
21
+ catch {
22
+ return [];
23
+ }
24
+ const out = [];
25
+ for (const entry of entries) {
26
+ if (!entry.isDirectory())
27
+ continue;
28
+ const full = join(dir, entry.name);
29
+ let modelType;
30
+ try {
31
+ modelType = await detectModelType(full);
32
+ }
33
+ catch (err) {
34
+ if (debug)
35
+ console.warn(`[mlx] skip ${full}: ${err.message}`);
36
+ continue;
37
+ }
38
+ if (NON_GENERATIVE.has(modelType))
39
+ continue;
40
+ const preset = LAUNCH_PRESETS[modelType];
41
+ if (!preset) {
42
+ if (debug)
43
+ console.warn(`[mlx] skip ${full}: no LAUNCH_PRESETS entry for ${modelType}`);
44
+ continue;
45
+ }
46
+ out.push({ name: basename(full), path: full, modelType, preset });
47
+ }
48
+ out.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
49
+ return out;
50
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Engine tuning that is LAUNCHER POLICY, not an engine default.
3
+ *
4
+ * The native engine reads these knobs from the process environment through a
5
+ * `OnceLock`: the FIRST read latches the value for the life of the process, so
6
+ * every variable here must be in place before any model is loaded and must
7
+ * never be mutated afterwards. That one-shot latch is why the policy is a
8
+ * plain data object rather than something a caller can toggle at runtime.
9
+ *
10
+ * Two application shapes, deliberately different:
11
+ *
12
+ * - In-process host (`mlx serve`, `mlx launch claude`): call
13
+ * {@link applyEnginePolicy} to write `process.env` BEFORE the first load.
14
+ * A value the user already set in their shell always wins — this is a
15
+ * default, not an override.
16
+ * - Out-of-process host (Electron `utilityProcess.fork`): pass
17
+ * {@link engineEnvFor} into `fork({ env })` and never touch
18
+ * `process.env` in the child. The returned map is unconditional: it
19
+ * describes the child's starting environment, and the parent is expected
20
+ * to spread the user's own env under it if it wants shell values to win.
21
+ *
22
+ * Keeping the two apart matters. `MLX_PAGED_PREFILL_CHUNK_SIZE` reads as
23
+ * `0` ("no chunking") in Rust when unset; 2048 is the value
24
+ * `mlx launch claude` has historically applied to bound the cold-prefill
25
+ * memory peak, and baking it into the engine would silently change every
26
+ * other embedder's behaviour.
27
+ */
28
+ /** Env var names this module owns. Exported so tests can assert the exact set. */
29
+ export declare const ENGINE_POLICY_ENV_VARS: readonly ['MLX_PAGED_PREFILL_CHUNK_SIZE'];
30
+ export interface EnginePolicy {
31
+ /**
32
+ * `MLX_PAGED_PREFILL_CHUNK_SIZE` — tokens per paged-prefill chunk.
33
+ * `0` disables chunking (the Rust default when the var is unset).
34
+ */
35
+ pagedPrefillChunkSize?: number;
36
+ }
37
+ /**
38
+ * The policy `mlx launch claude` has applied since the flag existed, and the
39
+ * one `mlx serve` and the desktop sidecar inherit.
40
+ *
41
+ * 2048 matches the mlx-lm / mlx-vlm default and reduces per-chunk overhead
42
+ * versus the older 1024 for long Qwen dense contexts.
43
+ */
44
+ export declare const LAUNCHER_ENGINE_POLICY: EnginePolicy;
45
+ /**
46
+ * Render a policy as the environment map a forked child should start with.
47
+ *
48
+ * Unconditional by construction: there is no "already set" to respect in a
49
+ * child that does not exist yet.
50
+ */
51
+ export declare function engineEnvFor(policy: EnginePolicy): Record<string, string>;
52
+ /**
53
+ * Apply a policy to an in-process environment as a DEFAULT.
54
+ *
55
+ * Only writes vars that are currently unset — matching the historical
56
+ * `if (process.env.X == null)` guard, so an explicit `X=` (empty string) in
57
+ * the user's shell is treated as "the user has an opinion" and left alone.
58
+ *
59
+ * Returns the names actually written, so a caller can log or assert on them.
60
+ */
61
+ export declare function applyEnginePolicy(policy: EnginePolicy, env?: NodeJS.ProcessEnv): string[];
62
+ //# sourceMappingURL=env-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-policy.d.ts","sourceRoot":"","sources":["../../src/host/env-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,kFAAkF;AAClF,eAAO,MAAM,sBAAsB,YAAI,8BAA8B,CAAU,CAAC;AAEhF,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,EAAE,YAA6D,CAAC;AAEnG;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMzE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,EAAE,CAStG"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Engine tuning that is LAUNCHER POLICY, not an engine default.
3
+ *
4
+ * The native engine reads these knobs from the process environment through a
5
+ * `OnceLock`: the FIRST read latches the value for the life of the process, so
6
+ * every variable here must be in place before any model is loaded and must
7
+ * never be mutated afterwards. That one-shot latch is why the policy is a
8
+ * plain data object rather than something a caller can toggle at runtime.
9
+ *
10
+ * Two application shapes, deliberately different:
11
+ *
12
+ * - In-process host (`mlx serve`, `mlx launch claude`): call
13
+ * {@link applyEnginePolicy} to write `process.env` BEFORE the first load.
14
+ * A value the user already set in their shell always wins — this is a
15
+ * default, not an override.
16
+ * - Out-of-process host (Electron `utilityProcess.fork`): pass
17
+ * {@link engineEnvFor} into `fork({ env })` and never touch
18
+ * `process.env` in the child. The returned map is unconditional: it
19
+ * describes the child's starting environment, and the parent is expected
20
+ * to spread the user's own env under it if it wants shell values to win.
21
+ *
22
+ * Keeping the two apart matters. `MLX_PAGED_PREFILL_CHUNK_SIZE` reads as
23
+ * `0` ("no chunking") in Rust when unset; 2048 is the value
24
+ * `mlx launch claude` has historically applied to bound the cold-prefill
25
+ * memory peak, and baking it into the engine would silently change every
26
+ * other embedder's behaviour.
27
+ */
28
+ /** Env var names this module owns. Exported so tests can assert the exact set. */
29
+ export const ENGINE_POLICY_ENV_VARS = ['MLX_PAGED_PREFILL_CHUNK_SIZE'];
30
+ /**
31
+ * The policy `mlx launch claude` has applied since the flag existed, and the
32
+ * one `mlx serve` and the desktop sidecar inherit.
33
+ *
34
+ * 2048 matches the mlx-lm / mlx-vlm default and reduces per-chunk overhead
35
+ * versus the older 1024 for long Qwen dense contexts.
36
+ */
37
+ export const LAUNCHER_ENGINE_POLICY = Object.freeze({ pagedPrefillChunkSize: 2048 });
38
+ /**
39
+ * Render a policy as the environment map a forked child should start with.
40
+ *
41
+ * Unconditional by construction: there is no "already set" to respect in a
42
+ * child that does not exist yet.
43
+ */
44
+ export function engineEnvFor(policy) {
45
+ const env = {};
46
+ if (policy.pagedPrefillChunkSize !== undefined) {
47
+ env.MLX_PAGED_PREFILL_CHUNK_SIZE = String(policy.pagedPrefillChunkSize);
48
+ }
49
+ return env;
50
+ }
51
+ /**
52
+ * Apply a policy to an in-process environment as a DEFAULT.
53
+ *
54
+ * Only writes vars that are currently unset — matching the historical
55
+ * `if (process.env.X == null)` guard, so an explicit `X=` (empty string) in
56
+ * the user's shell is treated as "the user has an opinion" and left alone.
57
+ *
58
+ * Returns the names actually written, so a caller can log or assert on them.
59
+ */
60
+ export function applyEnginePolicy(policy, env = process.env) {
61
+ const applied = [];
62
+ for (const [name, value] of Object.entries(engineEnvFor(policy))) {
63
+ if (env[name] == null) {
64
+ env[name] = value;
65
+ applied.push(name);
66
+ }
67
+ }
68
+ return applied;
69
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * `@mlx-node/server/host` — the reusable inference-host bootstrap.
3
+ *
4
+ * Everything a process needs to go from "a directory of downloaded models" to
5
+ * "a listening Anthropic/OpenAI-compatible endpoint with one resident model
6
+ * and a working `/model` swap", with none of the opinions about WHO supervises
7
+ * the process.
8
+ *
9
+ * That last part is why this is a module rather than a CLI flag. The two known
10
+ * front-ends invert the supervision relationship:
11
+ *
12
+ * - `mlx launch claude` starts the host, then spawns and supervises a child
13
+ * (`claude`), and exits when the child does.
14
+ * - The desktop app's Electron `utilityProcess` IS the child; the host is
15
+ * supervised, gets told when to shut down, and may be SIGKILLed.
16
+ *
17
+ * `mlx serve` is the third, degenerate case — nothing above, nothing below —
18
+ * and doubles as the terminal-visible reproduction of the sidecar when the
19
+ * utilityProcess wedges.
20
+ *
21
+ * A subpath export rather than part of `@mlx-node/server`'s main entry, so
22
+ * importing the plain HTTP handler does not drag in `@mlx-node/lm` and model
23
+ * loading.
24
+ *
25
+ * ## Ownership
26
+ *
27
+ * The returned host owns, and disposes on {@link InferenceHost.close}:
28
+ * 1. the HTTP server,
29
+ * 2. the verbose request logger (when `logDir` is set),
30
+ * 3. the `PagedConfigOverrideManager`'s temp root.
31
+ *
32
+ * The server goes first because the logger records a request from that
33
+ * request's own `finish`/`close` handler. Ending the log streams while a
34
+ * request is still draining loses exactly the completion lines a verbose
35
+ * shutdown exists to capture. The temp root goes last because a
36
+ * still-draining request may still be reading a cloned config. Each step is
37
+ * independently guarded — one failing disposer must not strand the ones
38
+ * after it.
39
+ */
40
+ import type { Server } from 'node:http';
41
+ import { type LoadableModel } from '@mlx-node/lm';
42
+ import type { ServerHealth } from '../health.js';
43
+ import { type CloseOptions, type ServerInstance } from '../server.js';
44
+ import { type DiscoveredModel } from './discover.js';
45
+ import { type EnginePolicy } from './env-policy.js';
46
+ import { type Logger } from './logger.js';
47
+ /** Families `mlx launch claude` has historically forced onto the paged path. */
48
+ export declare const DEFAULT_PAGED_MODEL_TYPES: readonly ['qwen3_5', 'qwen3_5_moe'];
49
+ /** Thrown when `modelsDir` holds nothing servable. Carries the dir for the caller's message. */
50
+ export declare class NoModelsDiscoveredError extends Error {
51
+ readonly modelsDir: string;
52
+ constructor(modelsDir: string);
53
+ }
54
+ /**
55
+ * Thrown when a bind reachable from the network was asked for with no shared
56
+ * secret to gate it.
57
+ *
58
+ * There is no safe way to serve that: every route but the `/health` liveness
59
+ * carve-out runs inference, so an unauthenticated LAN-reachable bind hands
60
+ * anyone who can route to this machine the GPU, the RAM, and the list of
61
+ * models on disk. Failing at startup is the only outcome the operator can act
62
+ * on — serving-with-a-warning is a warning nobody reads scrolling past a
63
+ * model load.
64
+ */
65
+ export declare class InsecureBindError extends Error {
66
+ readonly host: string;
67
+ constructor(host: string);
68
+ }
69
+ /** Thrown when a requested model name is not among the discovered ones. */
70
+ export declare class ModelNotFoundError extends Error {
71
+ readonly requested: string;
72
+ readonly modelsDir: string;
73
+ readonly available: string[];
74
+ constructor(requested: string, modelsDir: string, available: string[]);
75
+ }
76
+ /** Thrown when a model load is requested after host shutdown reaches its admission boundary. */
77
+ export declare class InferenceHostClosedError extends Error {
78
+ constructor();
79
+ }
80
+ export interface InferenceHostOptions {
81
+ /**
82
+ * Port to bind. Omitted ⇒ a free port is picked up-front (so the URL is
83
+ * known before the server starts, which `mlx launch claude` needs in order
84
+ * to bake `ANTHROPIC_BASE_URL` into the child's env). `0` binds an
85
+ * ephemeral port and the real one is read back off the socket.
86
+ */
87
+ port?: number;
88
+ /**
89
+ * Host to bind. Default `127.0.0.1`.
90
+ *
91
+ * A non-loopback value (including the wildcards `0.0.0.0` / `::`) requires
92
+ * an auth token — from {@link InferenceHostOptions.authToken} or
93
+ * `MLX_SERVER_AUTH_TOKEN` — or the call throws {@link InsecureBindError}
94
+ * instead of listening.
95
+ */
96
+ host?: string;
97
+ /** Model discovery root. Default: {@link resolveModelsDir}'s resolution order. */
98
+ modelsDir?: string;
99
+ /**
100
+ * Which discovered model is the bound/default one. Precedence, highest
101
+ * first: this option, `ANTHROPIC_MODEL`, `discovered[0]` (alphabetical).
102
+ * A name that matches nothing discovered throws {@link ModelNotFoundError}
103
+ * rather than silently falling back.
104
+ */
105
+ model?: string;
106
+ /** Shared secret for every route except `/health`. See `ServerConfig.authToken`. */
107
+ authToken?: string;
108
+ /** When set, every HTTP turn is captured under this directory. */
109
+ logDir?: string;
110
+ /**
111
+ * Engine env policy to apply to `process.env` BEFORE anything can load a
112
+ * model. Omitted ⇒ nothing is written and the engine's own defaults stand.
113
+ * Out-of-process hosts must pass `engineEnvFor(policy)` to `fork({ env })`
114
+ * instead and leave this unset — the vars latch via `OnceLock` on first
115
+ * read, so mutating them in the child is too late.
116
+ */
117
+ enginePolicy?: EnginePolicy;
118
+ /** Model types forced onto the block-paged KV cache. Default {@link DEFAULT_PAGED_MODEL_TYPES}. */
119
+ pagedModelTypes?: readonly string[];
120
+ /** Path to the SQLite response store. See `ServerConfig.storePath`. */
121
+ storePath?: string;
122
+ /** Disable response persistence entirely. See `ServerConfig.disableStore`. */
123
+ disableStore?: boolean;
124
+ /**
125
+ * Remove temp roots left behind by hosts that were killed without running
126
+ * `close()`. Default `true`; a supervisor that runs several hosts under one
127
+ * pid namespace and wants to control the timing can turn it off and call
128
+ * {@link sweepOrphanHostTempRoots} itself.
129
+ */
130
+ sweepOrphanTempRoots?: boolean;
131
+ /** Test seam: replaces the native model loader. */
132
+ loadModel?: (path: string) => Promise<LoadableModel>;
133
+ /** Test seam: replaces the verbose request logger. */
134
+ attachLogger?: (server: Server, logDir: string) => Logger;
135
+ }
136
+ export interface InferenceHost {
137
+ /** Connectable base URL, e.g. `http://127.0.0.1:51234`. */
138
+ url: string;
139
+ /** The port actually bound (resolved, never `0`). */
140
+ port: number;
141
+ /** The host actually bound — the requested value, not the advertised one. */
142
+ host: string;
143
+ /** The resolved model discovery root. */
144
+ modelsDir: string;
145
+ /** Everything discovered under `modelsDir`, alphabetical. */
146
+ models: DiscoveredModel[];
147
+ /** Name of the default/bound model. Always a member of {@link models}. */
148
+ boundModel: string;
149
+ /** Verbose log directory, or `null` when logging is off. */
150
+ logDir: string | null;
151
+ /** Escape hatch for callers that need the registry, store, or raw `http.Server`. */
152
+ server: ServerInstance;
153
+ /** Current readiness snapshot — the same body `GET /health` returns. */
154
+ health(): ServerHealth;
155
+ /**
156
+ * Make `name` the resident model, out of band (no HTTP request needed).
157
+ *
158
+ * Runs under the SAME brackets `ServerInstance.loadModel` uses — drains
159
+ * suspended OUTSIDE the coordinator's exclusive writer slot — so it cannot
160
+ * race the process-wide Metal allocator against in-flight inference, and
161
+ * `/health` reports `loading` plus a `lastLoad` record labelled with `name`.
162
+ *
163
+ * It deliberately does NOT call `ServerInstance.loadModel` itself. That
164
+ * helper only ever registers, never unregisters, so calling it per swap
165
+ * would accumulate every model the user has ever picked in memory. The
166
+ * single-resident swap controller is the thing that makes a swap a swap;
167
+ * this method just wraps it in the right brackets.
168
+ *
169
+ * Rejects with {@link ModelNotFoundError} for an unknown name — the swap
170
+ * controller would otherwise treat it as an alias for the current resident,
171
+ * which is right for Claude Code's hardcoded `claude-haiku-*` but wrong for
172
+ * a supervisor that asked for a specific model.
173
+ *
174
+ * Calls admitted before {@link close} begins are allowed to finish. Calls
175
+ * made after shutdown begins reject with {@link InferenceHostClosedError}.
176
+ */
177
+ loadModel(name: string): Promise<void>;
178
+ /**
179
+ * Stop accepting new HTTP and out-of-band load work, wait for every
180
+ * out-of-band load already admitted, then dispose the logger and temp root.
181
+ * Idempotent and memoized; disposal steps are individually guarded so one
182
+ * failure cannot strand the remaining steps.
183
+ */
184
+ close(opts?: CloseOptions): Promise<void>;
185
+ }
186
+ /**
187
+ * Start a local inference host.
188
+ *
189
+ * Throws {@link NoModelsDiscoveredError} / {@link ModelNotFoundError} /
190
+ * {@link InsecureBindError} rather than exiting — a library cannot know
191
+ * whether its caller is a CLI, a test, or an Electron main process. Front-ends
192
+ * render those into their own messages.
193
+ */
194
+ export declare function createInferenceHost(opts?: InferenceHostOptions): Promise<InferenceHost>;
195
+ export { discoverModels, type DiscoveredModel } from './discover.js';
196
+ export { applyEnginePolicy, engineEnvFor, ENGINE_POLICY_ENV_VARS, LAUNCHER_ENGINE_POLICY, type EnginePolicy, } from './env-policy.js';
197
+ export { attachLogger, resolveLogDir, type Logger } from './logger.js';
198
+ export { bracketHost, hostUrl, isLoopbackBindHost, pickFreePort } from './net.js';
199
+ export { resolveMlxNodeHome, resolveModelsDir } from './paths.js';
200
+ export { makeSwapController, type SwapController } from './swap.js';
201
+ export { hostTempDirPrefix, isProcessAlive, sweepOrphanHostTempRoots, HOST_TEMP_DIR_STEM, type SweepOrphanTempRootsOptions, } from './temp-root.js';
202
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"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"}