@mlx-node/agent 0.0.12 → 0.0.15

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/catalog.d.ts +10 -1
  2. package/dist/catalog.d.ts.map +1 -1
  3. package/dist/catalog.js +11 -2
  4. package/dist/delegate.d.ts +29 -0
  5. package/dist/delegate.d.ts.map +1 -0
  6. package/dist/delegate.js +106 -0
  7. package/dist/extensions/delegation.d.ts +15 -0
  8. package/dist/extensions/delegation.d.ts.map +1 -0
  9. package/dist/extensions/delegation.js +93 -0
  10. package/dist/paths.d.ts +6 -0
  11. package/dist/paths.d.ts.map +1 -1
  12. package/dist/paths.js +16 -0
  13. package/dist/provider/chat-config.d.ts +6 -5
  14. package/dist/provider/chat-config.d.ts.map +1 -1
  15. package/dist/provider/chat-config.js +21 -7
  16. package/dist/provider/index.d.ts.map +1 -1
  17. package/dist/provider/index.js +8 -1
  18. package/dist/provider/model-host.d.ts +1 -1
  19. package/dist/provider/model-host.d.ts.map +1 -1
  20. package/dist/provider/model-host.js +25 -7
  21. package/dist/provider/models.d.ts +3 -14
  22. package/dist/provider/models.d.ts.map +1 -1
  23. package/dist/provider/models.js +17 -239
  24. package/dist/provider/stream-adapter.d.ts +2 -2
  25. package/dist/provider/stream-adapter.d.ts.map +1 -1
  26. package/dist/provider/stream-adapter.js +8 -5
  27. package/dist/run-agent.d.ts +4 -0
  28. package/dist/run-agent.d.ts.map +1 -1
  29. package/dist/run-agent.js +8 -2
  30. package/dist/types.d.ts +1 -1
  31. package/dist/types.d.ts.map +1 -1
  32. package/package.json +23 -5
  33. package/src/catalog.ts +194 -0
  34. package/src/cold-tier.ts +152 -0
  35. package/src/delegate.ts +136 -0
  36. package/src/extensions/approval-detail.ts +57 -0
  37. package/src/extensions/delegation.ts +109 -0
  38. package/src/extensions/local-image-input.ts +132 -0
  39. package/src/extensions/permission-gate.ts +347 -0
  40. package/src/extensions/subagent.ts +743 -0
  41. package/src/extensions/terminal-title.ts +53 -0
  42. package/src/extensions/trace-notice.ts +37 -0
  43. package/src/index.ts +23 -0
  44. package/src/paths.ts +36 -0
  45. package/src/provider/chat-config.ts +132 -0
  46. package/src/provider/convert-messages.ts +273 -0
  47. package/src/provider/error-coercion.ts +36 -0
  48. package/src/provider/events.ts +341 -0
  49. package/src/provider/index.ts +255 -0
  50. package/src/provider/metrics-trace.ts +380 -0
  51. package/src/provider/mlx-identity.ts +16 -0
  52. package/src/provider/model-host.ts +276 -0
  53. package/src/provider/model-registry-filter.ts +336 -0
  54. package/src/provider/models.ts +48 -0
  55. package/src/provider/performance-status.ts +112 -0
  56. package/src/provider/reasoning-tag-buffer.ts +67 -0
  57. package/src/provider/stream-adapter.ts +515 -0
  58. package/src/provider/tool-call-buffer.ts +82 -0
  59. package/src/provider/warm-reuse.ts +125 -0
  60. package/src/run-agent.ts +178 -0
  61. package/src/types.ts +10 -0
package/dist/catalog.d.ts CHANGED
@@ -19,6 +19,12 @@ export interface CatalogEntry {
19
19
  hfRepoCuda?: string;
20
20
  /** Approximate download size in GB, for display. */
21
21
  sizeGb: number;
22
+ /** Optional companion, downloaded separately and never offered as a chat model. */
23
+ draft?: {
24
+ label: string;
25
+ hfRepo: string;
26
+ sizeGb: number;
27
+ };
22
28
  /** One line for the wizard. */
23
29
  description: string;
24
30
  /** Exactly one entry carries this. */
@@ -41,6 +47,8 @@ export interface CatalogEntry {
41
47
  hidden?: boolean;
42
48
  }
43
49
  export declare const MODEL_CATALOG: readonly CatalogEntry[];
50
+ /** Visible target repos plus their optional companion downloads. */
51
+ export declare function catalogDownloadRepos(): string[];
44
52
  /**
45
53
  * The repo THIS platform installs for `entry`. Linux is the CUDA preview
46
54
  * target (README "Platform Support"); everything else is Apple Silicon.
@@ -86,7 +94,8 @@ export declare function visibleCatalog(): CatalogEntry[];
86
94
  * Cold-tier facts and family registration data, re-exported through this
87
95
  * subpath.
88
96
  *
89
- * `@mlx-node/agent/catalog` is the agent package's one NATIVE-FREE entry point:
97
+ * `@mlx-node/agent/catalog` is a NATIVE-FREE entry point, alongside the
98
+ * `delegate` client and `models` discovery subpaths:
90
99
  * the package root re-exports `provider/index.ts`, which value-imports
91
100
  * `@mlx-node/core`. The dashboard is a separate viewer process that must never
92
101
  * link the addon (docs/dashboard.md: "no Metal init, instant start"), and
@@ -1 +1 @@
1
- {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EA0DhD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAErF;AAED,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,YAAY,EAAE,CAE/C;AAED;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC"}
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,YAAY;IAC3B,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1D,+BAA+B;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EA2DhD,CAAC;AAEF,oEAAoE;AACpE,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAQ/C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAEvD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,GAAG,MAAM,CAErF;AAED,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,YAAY,EAAE,CAE/C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EACL,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,0BAA0B,CAAC"}
package/dist/catalog.js CHANGED
@@ -5,6 +5,7 @@
5
5
  * entry's `catalogRepo()` to `mlx download model`. Slugs are verified against the Brooooooklyn
6
6
  * HF account — use them verbatim.
7
7
  */
8
+ import { QWEN38_DFLASH2 } from '@mlx-node/lm/draft-companion';
8
9
  export const MODEL_CATALOG = [
9
10
  {
10
11
  label: 'Qwen3.8-27B',
@@ -13,9 +14,10 @@ export const MODEL_CATALOG = [
13
14
  sizeGb: 23.3,
14
15
  description: 'Best tool use — recommended default',
15
16
  isDefault: true,
17
+ draft: QWEN38_DFLASH2,
16
18
  },
17
19
  {
18
- label: 'Qwen-AgentWorld-35B',
20
+ label: 'Qwen-AgentWorld-35B-A3B',
19
21
  hfRepo: 'Brooooooklyn/Qwen-AgentWorld-35B-A3B-mxfp4-mlx',
20
22
  hfRepoCuda: 'Brooooooklyn/Qwen-AgentWorld-35B-A3B-nvfp4-mlx',
21
23
  sizeGb: 23.3,
@@ -64,6 +66,12 @@ export const MODEL_CATALOG = [
64
66
  hidden: true,
65
67
  },
66
68
  ];
69
+ /** Visible target repos plus their optional companion downloads. */
70
+ export function catalogDownloadRepos() {
71
+ return [
72
+ ...new Set(MODEL_CATALOG.filter((entry) => !entry.hidden).flatMap((entry) => entry.draft === undefined ? [catalogRepo(entry)] : [catalogRepo(entry), entry.draft.hfRepo])),
73
+ ];
74
+ }
67
75
  /**
68
76
  * The repo THIS platform installs for `entry`. Linux is the CUDA preview
69
77
  * target (README "Platform Support"); everything else is Apple Silicon.
@@ -115,7 +123,8 @@ export function visibleCatalog() {
115
123
  * Cold-tier facts and family registration data, re-exported through this
116
124
  * subpath.
117
125
  *
118
- * `@mlx-node/agent/catalog` is the agent package's one NATIVE-FREE entry point:
126
+ * `@mlx-node/agent/catalog` is a NATIVE-FREE entry point, alongside the
127
+ * `delegate` client and `models` discovery subpaths:
119
128
  * the package root re-exports `provider/index.ts`, which value-imports
120
129
  * `@mlx-node/core`. The dashboard is a separate viewer process that must never
121
130
  * link the addon (docs/dashboard.md: "no Metal init, instant start"), and
@@ -0,0 +1,29 @@
1
+ export declare const DELEGATION_PROMPT = "Delegate GitHub investigation to `mlx delegate github --caller-approved --repo OWNER/REPO \"TASK\"`. Approve the bounded task and its tool execution before invoking. Include the PR, issue, or run number. Wait for the final handoff and read it once; do not poll by dumping worker transcripts. Use its findings and evidence for implementation; address specific gaps with a focused follow-up or relevant evidence excerpt. Add `--allow-write` only for GitHub changes already authorized by the user. If delegation fails or reports incomplete work, continue from its handoff.";
2
+ /** Absolute paths avoid dependence on each coding agent's shell startup/PATH. */
3
+ export declare function delegationCommand(path: string): string;
4
+ export declare function delegationPrompt(path: string): string;
5
+ export interface LocalInferenceConnection {
6
+ url: string;
7
+ token?: string;
8
+ model: string;
9
+ }
10
+ export interface LocalMessage {
11
+ role: 'user' | 'assistant';
12
+ content: string;
13
+ }
14
+ /** Shared by setup requests and their verdict cache key. Includes thinking and final output. */
15
+ export declare const INSTALL_CHECK_GENERATION: Readonly<{
16
+ reasoning: Readonly<{
17
+ effort: 'medium';
18
+ }>;
19
+ max_output_tokens: 16384;
20
+ temperature: 0;
21
+ }>;
22
+ export declare const INSTALL_CHECK_TIMEOUT_MS = 600000;
23
+ export declare function expandHome(path: string, home?: string): string;
24
+ /** Match the persisted default used by `mlx agent`; never select a cloud provider. */
25
+ export declare function preferredLocalModel(home?: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
26
+ /** Refuse remote endpoints even if a caller accidentally supplies a cloud API URL. */
27
+ export declare function localCompletion(connection: LocalInferenceConnection, system: string, messages: LocalMessage[], signal?: AbortSignal, maxTokens?: number): Promise<string>;
28
+ export declare function parseLocalJson(text: string): unknown;
29
+ //# sourceMappingURL=delegate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegate.d.ts","sourceRoot":"","sources":["../src/delegate.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,iBAAiB,8jBAC6hB,CAAC;AAE5jB,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,MAAM,WAAW,wBAAwB;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,gGAAgG;AAChG,eAAO,MAAM,wBAAwB;;gBACA,QAAQ;;;;EAG3C,CAAC;AACH,eAAO,MAAM,wBAAwB,SAAU,CAAC;AAEhD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAY,GAAG,MAAM,CAEjE;AAED,sFAAsF;AACtF,wBAAsB,mBAAmB,CAAC,IAAI,SAAY,EAAE,GAAG,oBAAc,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAe1G;AAED,sFAAsF;AACtF,wBAAsB,eAAe,CACnC,UAAU,EAAE,wBAAwB,EACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,YAAY,EAAE,EACxB,MAAM,CAAC,EAAE,WAAW,EACpB,SAAS,GAAE,MAAmD,GAC7D,OAAO,CAAC,MAAM,CAAC,CA0DjB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD"}
@@ -0,0 +1,106 @@
1
+ /** Addon-free client for the desktop's global instruction installation checks. */
2
+ import { readFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { expandPiAgentDir } from './paths.js';
6
+ export const DELEGATION_PROMPT = 'Delegate GitHub investigation to `mlx delegate github --caller-approved --repo OWNER/REPO "TASK"`. Approve the bounded task and its tool execution before invoking. Include the PR, issue, or run number. Wait for the final handoff and read it once; do not poll by dumping worker transcripts. Use its findings and evidence for implementation; address specific gaps with a focused follow-up or relevant evidence excerpt. Add `--allow-write` only for GitHub changes already authorized by the user. If delegation fails or reports incomplete work, continue from its handoff.';
7
+ /** Absolute paths avoid dependence on each coding agent's shell startup/PATH. */
8
+ export function delegationCommand(path) {
9
+ if (/[\0\r\n`]/.test(path))
10
+ throw new Error('The delegation command path cannot be represented in instructions.');
11
+ return `'${path.replaceAll("'", "'\\''")}'`;
12
+ }
13
+ export function delegationPrompt(path) {
14
+ return DELEGATION_PROMPT.replace('`mlx delegate', `\`${delegationCommand(path)} delegate`);
15
+ }
16
+ /** Shared by setup requests and their verdict cache key. Includes thinking and final output. */
17
+ export const INSTALL_CHECK_GENERATION = Object.freeze({
18
+ reasoning: Object.freeze({ effort: 'medium' }),
19
+ max_output_tokens: 16384,
20
+ temperature: 0,
21
+ });
22
+ export const INSTALL_CHECK_TIMEOUT_MS = 600_000;
23
+ export function expandHome(path, home = homedir()) {
24
+ return path === '~' ? home : path.startsWith('~/') ? join(home, path.slice(2)) : path;
25
+ }
26
+ /** Match the persisted default used by `mlx agent`; never select a cloud provider. */
27
+ export async function preferredLocalModel(home = homedir(), env = process.env) {
28
+ try {
29
+ const dir = env.PI_CODING_AGENT_DIR
30
+ ? expandPiAgentDir(env.PI_CODING_AGENT_DIR, home)
31
+ : join(home, '.mlx-node', 'agent');
32
+ const value = JSON.parse(await readFile(join(dir, 'settings.json'), 'utf8'));
33
+ if (value.defaultProvider === 'mlx' && typeof value.defaultModel === 'string') {
34
+ return value.defaultModel.replace(/^mlx\//, '');
35
+ }
36
+ }
37
+ catch (error) {
38
+ if (error.code !== 'ENOENT') {
39
+ throw new Error('Could not read the default local model. Open mlx agent and select a model again.');
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ /** Refuse remote endpoints even if a caller accidentally supplies a cloud API URL. */
45
+ export async function localCompletion(connection, system, messages, signal, maxTokens = INSTALL_CHECK_GENERATION.max_output_tokens) {
46
+ const url = new URL(connection.url);
47
+ if (url.protocol !== 'http:' || !['127.0.0.1', '[::1]', 'localhost'].includes(url.hostname)) {
48
+ throw new Error('Delegation requires a local inference server.');
49
+ }
50
+ const controller = new AbortController();
51
+ const cancel = () => controller.abort(signal?.reason);
52
+ if (signal?.aborted)
53
+ cancel();
54
+ else
55
+ signal?.addEventListener('abort', cancel, { once: true });
56
+ const timer = setTimeout(() => controller.abort(new Error('The local model request timed out.')), INSTALL_CHECK_TIMEOUT_MS);
57
+ try {
58
+ const headers = {
59
+ 'content-type': 'application/json',
60
+ ...(connection.token ? { 'x-api-key': connection.token } : {}),
61
+ };
62
+ const catalog = await fetch(new URL('/v1/models', url), { headers, signal: controller.signal, redirect: 'error' });
63
+ if (!catalog.ok)
64
+ throw new Error('The local model service is unavailable. Restart it and try again.');
65
+ const models = (await catalog.json());
66
+ if (!models.data?.some((model) => model.id === connection.model)) {
67
+ throw new Error('The default model is not available in the running service. Restart the local model service and try again.');
68
+ }
69
+ const response = await fetch(new URL('/v1/responses', url), {
70
+ method: 'POST',
71
+ headers,
72
+ body: JSON.stringify({
73
+ model: connection.model,
74
+ instructions: system,
75
+ input: messages,
76
+ ...INSTALL_CHECK_GENERATION,
77
+ max_output_tokens: maxTokens,
78
+ store: false,
79
+ stream: false,
80
+ }),
81
+ signal: controller.signal,
82
+ redirect: 'error',
83
+ });
84
+ if (!response.ok)
85
+ throw new Error(`The local model could not complete the request (HTTP ${response.status}).`);
86
+ const body = (await response.json());
87
+ if (body.incomplete_details?.reason === 'max_output_tokens')
88
+ throw new Error('The local model ran out of output space. Try checking again.');
89
+ if (body.status !== 'completed')
90
+ throw new Error('The local model did not complete its answer. Try checking again.');
91
+ const text = body.output_text?.trim();
92
+ if (!text)
93
+ throw new Error('The local model returned no answer. Try checking again.');
94
+ return text;
95
+ }
96
+ finally {
97
+ clearTimeout(timer);
98
+ signal?.removeEventListener('abort', cancel);
99
+ }
100
+ }
101
+ export function parseLocalJson(text) {
102
+ return JSON.parse(text
103
+ .trim()
104
+ .replace(/^```(?:json)?\s*\n?/, '')
105
+ .replace(/\n?```$/, ''));
106
+ }
@@ -0,0 +1,15 @@
1
+ import type { InlineExtension } from '@earendil-works/pi-coding-agent';
2
+ export interface DelegateCallerPermissions {
3
+ source: 'codex';
4
+ /** Opaque Codex profile/backend when supplied; otherwise just inherited process permissions. */
5
+ profile: string;
6
+ networkDisabled: boolean;
7
+ }
8
+ /** Child processes stay inside Codex's actual sandbox; this metadata grants no OS access. */
9
+ export declare function delegateCallerPermissions(env?: NodeJS.ProcessEnv): DelegateCallerPermissions | undefined;
10
+ /** Only failed tool executions qualify; quoted CI logs in a successful result do not. */
11
+ export declare function delegationBlocker(text: string): boolean;
12
+ export declare function createDelegationExtension(options?: {
13
+ callerApproved?: boolean;
14
+ }): InlineExtension;
15
+ //# sourceMappingURL=delegation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation.d.ts","sourceRoot":"","sources":["../../src/extensions/delegation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkC,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAEvG,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,OAAO,CAAC;IAChB,gGAAgG;IAChG,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,6FAA6F;AAC7F,wBAAgB,yBAAyB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,yBAAyB,GAAG,SAAS,CAWrH;AAID,yFAAyF;AACzF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAIvD;AAED,wBAAgB,yBAAyB,CAAC,OAAO,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,eAAe,CA4ErG"}
@@ -0,0 +1,93 @@
1
+ /** Child processes stay inside Codex's actual sandbox; this metadata grants no OS access. */
2
+ export function delegateCallerPermissions(env = process.env) {
3
+ if (!env.CODEX_THREAD_ID?.trim())
4
+ return undefined;
5
+ const profile = env.CODEX_PERMISSION_PROFILE?.trim();
6
+ // Permission metadata is optional in Codex launches. The thread identifies
7
+ // the caller; subprocesses inherit its real sandbox regardless of these labels.
8
+ const sandbox = env.CODEX_SANDBOX;
9
+ return {
10
+ source: 'codex',
11
+ profile: profile || (sandbox === 'seatbelt' || sandbox === 'landlock' ? `sandbox:${sandbox}` : 'inherited'),
12
+ networkDisabled: env.CODEX_SANDBOX_NETWORK_DISABLED === '1',
13
+ };
14
+ }
15
+ const READ_TOOLS = new Set(['read', 'grep', 'find', 'ls']);
16
+ /** Only failed tool executions qualify; quoted CI logs in a successful result do not. */
17
+ export function delegationBlocker(text) {
18
+ return /permission denied|operation not permitted|\bEACCES\b|\bEPERM\b|network access was denied|sandbox.*(?:denied|blocked)|(?:HTTP\s+|status(?: code)?[: ]+)(?:401|403)\b|gh auth login|could not resolve host|could not resolve proxy|failed to connect|error connecting to api\.github\.com/i.test(text);
19
+ }
20
+ export function createDelegationExtension(options = {}) {
21
+ // Capture before any model/tool code runs. Never consult model-written settings.
22
+ const caller = delegateCallerPermissions();
23
+ const explicitlyApproved = options.callerApproved === true || process.env.MLX_AGENT_AUTO_APPROVE === '1';
24
+ return {
25
+ name: 'mlx-delegation',
26
+ factory: (pi) => {
27
+ let handoff;
28
+ const stop = (reason, ctx) => {
29
+ if (!handoff) {
30
+ const sessionFile = ctx.sessionManager.getSessionFile();
31
+ handoff = `Delegation incomplete: ${reason}\nContinue this task in the calling agent. Do not retry with changed permissions or another agent.${sessionFile ? `\nSaved evidence and tool results: ${sessionFile}` : ''}`;
32
+ pi.appendEntry('mlx-delegate-handoff', { reason, caller, sessionFile });
33
+ // Print mode otherwise shows only the final assistant message. A deterministic
34
+ // diagnostic and failure status let the caller recover without another inference.
35
+ process.stderr.write(`${handoff}\n`);
36
+ process.exitCode = 1;
37
+ }
38
+ ctx.abort();
39
+ return handoff;
40
+ };
41
+ pi.on('before_agent_start', (event, ctx) => {
42
+ handoff = undefined;
43
+ // A boundary per invocation keeps ordinary history/resumes out of the
44
+ // delegate estimate. Custom entries are persisted, never model context.
45
+ pi.appendEntry('mlx-delegate-session', { version: 1, sessionId: ctx.sessionManager.getSessionId() });
46
+ const permissions = caller
47
+ ? `Tool execution inherits the calling Codex process's permissions (${caller.profile}).${caller.networkDisabled ? ' The caller disables network access.' : ''} Additional approval must be handled by the calling agent; this worker cannot request escalation.`
48
+ : explicitlyApproved
49
+ ? "The caller explicitly approved tool execution for this bounded task. Child processes inherit its OS sandbox, environment and credentials; this does not copy the calling agent's tool approval rules or grant additional access. Stay within the task authorization."
50
+ : 'No inherited caller permission context is available. Tools requiring approval will stop this task and return a handoff.';
51
+ return { systemPrompt: `${event.systemPrompt}\n\n${permissions}` };
52
+ });
53
+ pi.on('tool_call', (event, ctx) => {
54
+ if (handoff)
55
+ return { block: true, terminate: true, reason: handoff };
56
+ if (event.toolName === 'subagent') {
57
+ return { block: true, terminate: true, reason: stop('This worker cannot create subagents.', ctx) };
58
+ }
59
+ if (READ_TOOLS.has(event.toolName))
60
+ return undefined;
61
+ if (!caller && !explicitlyApproved) {
62
+ return {
63
+ block: true,
64
+ terminate: true,
65
+ reason: stop(`No caller permission context is available to authorize ${event.toolName}. The calling agent can use --caller-approved after approving this bounded task.`, ctx),
66
+ };
67
+ }
68
+ if (event.toolName === 'bash') {
69
+ const input = event.input;
70
+ if (typeof input.command === 'string') {
71
+ // Preserve failures through pipelines such as `gh ... | head`, and
72
+ // stop a multi-command script before later output masks a denial.
73
+ // This changes shell options only; cwd, environment and sandbox remain inherited.
74
+ input.command = `set -e -o pipefail\n${input.command}`;
75
+ }
76
+ }
77
+ return undefined;
78
+ });
79
+ pi.on('tool_result', (event, ctx) => {
80
+ if (!event.isError)
81
+ return undefined;
82
+ const text = event.content
83
+ .filter((part) => part.type === 'text')
84
+ .map((part) => part.text)
85
+ .join('\n');
86
+ if (!delegationBlocker(text))
87
+ return undefined;
88
+ const reason = stop(`${event.toolName} was blocked. ${text.slice(0, 2000)}`, ctx);
89
+ return { content: [{ type: 'text', text: reason }], isError: true };
90
+ });
91
+ },
92
+ };
93
+ }
package/dist/paths.d.ts CHANGED
@@ -6,6 +6,12 @@
6
6
  * `resolveMlxNodeHome()` in `@mlx-node/server/host/paths`
7
7
  * (`packages/server/src/host/paths.ts`).
8
8
  */
9
+ /**
10
+ * Expand an agent directory for both the CLI and desktop settings readers.
11
+ * Accept home-relative paths and file URLs without trimming literal paths or
12
+ * expanding another user's `~user` prefix. `home` is a test seam.
13
+ */
14
+ export declare function expandPiAgentDir(dir: string, home?: string): string;
9
15
  /** Absolute path to `$HOME/.mlx-node`. */
10
16
  export declare function mlxNodeHome(): string;
11
17
  /** Directory holding per-process `MetricsTrace` JSONL files. */
@@ -1 +1 @@
1
- {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,0CAA0C;AAC1C,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,gEAAgE;AAChE,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAO9E;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED,gEAAgE;AAChE,wBAAgB,eAAe,IAAI,MAAM,CAExC"}
package/dist/paths.js CHANGED
@@ -8,6 +8,22 @@
8
8
  */
9
9
  import { homedir } from 'node:os';
10
10
  import { join } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ /**
13
+ * Expand an agent directory for both the CLI and desktop settings readers.
14
+ * Accept home-relative paths and file URLs without trimming literal paths or
15
+ * expanding another user's `~user` prefix. `home` is a test seam.
16
+ */
17
+ export function expandPiAgentDir(dir, home = homedir()) {
18
+ if (dir === '~')
19
+ return home;
20
+ if (dir.startsWith('~/') || (process.platform === 'win32' && dir.startsWith('~\\'))) {
21
+ return join(home, dir.slice(2));
22
+ }
23
+ if (dir.startsWith('file://'))
24
+ return fileURLToPath(dir);
25
+ return dir;
26
+ }
11
27
  /** Absolute path to `$HOME/.mlx-node`. */
12
28
  export function mlxNodeHome() {
13
29
  return join(homedir(), '.mlx-node');
@@ -7,16 +7,17 @@
7
7
  import type { SimpleStreamOptions, ThinkingLevel } from '@earendil-works/pi-ai';
8
8
  import { type ChatConfig, type ModelType, type ToolDefinition } from '@mlx-node/lm';
9
9
  export interface ResolvedReasoningMode {
10
- reasoningEffort: 'none' | 'low' | 'medium' | 'high';
10
+ reasoningEffort: 'none' | 'low' | 'medium' | 'high' | 'xhigh' | 'max';
11
11
  /** The `enable_thinking` value implied by `reasoningEffort` for templates. */
12
12
  thinkingEnabled: boolean;
13
13
  }
14
14
  /**
15
15
  * Resolve Pi's thinking level once for both native config and persisted replay
16
- * provenance. Keeping these values together prevents a low/minimal turn from
17
- * being replayed later as an enabled-thinking turn merely because the Pi
18
- * option was present.
16
+ * provenance. Keeping these values together gives generation and replay the same
17
+ * enabled-thinking state, including low/minimal turns.
19
18
  */
20
19
  export declare function resolveReasoningMode(reasoning: ThinkingLevel | undefined): ResolvedReasoningMode;
21
- export declare function buildChatConfig(modelType: ModelType, options: SimpleStreamOptions | undefined, tools: ToolDefinition[] | undefined, rootCacheOwnerId?: string, resolvedReasoning?: ResolvedReasoningMode, modelMaxTokens?: unknown): ChatConfig;
20
+ /** An explicit hard cap, independent from the model's reasoning effort. */
21
+ export declare function parseThinkingBudget(value: unknown): number | undefined;
22
+ export declare function buildChatConfig(modelType: ModelType, options: SimpleStreamOptions | undefined, tools: ToolDefinition[] | undefined, rootCacheOwnerId?: string, resolvedReasoning?: ResolvedReasoningMode, modelMaxTokens?: unknown, thinkingTokenBudget?: number): ChatConfig;
22
23
  //# sourceMappingURL=chat-config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-config.d.ts","sourceRoot":"","sources":["../../src/provider/chat-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AA8BtB,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpD,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAMD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,GAAG,qBAAqB,CAMhG;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,EACnC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,wBAA2C,EAC5D,cAAc,CAAC,EAAE,OAAO,GACvB,UAAU,CAsCZ"}
1
+ {"version":3,"file":"chat-config.d.ts","sourceRoot":"","sources":["../../src/provider/chat-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AA8BtB,MAAM,WAAW,qBAAqB;IACpC,eAAe,EAAE,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;IACtE,8EAA8E;IAC9E,eAAe,EAAE,OAAO,CAAC;CAC1B;AAMD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,GAAG,qBAAqB,CAMhG;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAOtE;AAED,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,mBAAmB,GAAG,SAAS,EACxC,KAAK,EAAE,cAAc,EAAE,GAAG,SAAS,EACnC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,wBAA2C,EAC5D,cAAc,CAAC,EAAE,OAAO,EACxB,mBAAmB,CAAC,EAAE,MAAM,GAC3B,UAAU,CA6CZ"}
@@ -28,32 +28,46 @@ const THINKING_LEVEL_TO_EFFORT = {
28
28
  low: 'low',
29
29
  medium: 'medium',
30
30
  high: 'high',
31
- xhigh: 'high',
32
- max: 'high',
31
+ xhigh: 'xhigh',
32
+ max: 'max',
33
33
  };
34
34
  function isPositiveSafeInteger(value) {
35
35
  return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
36
36
  }
37
37
  /**
38
38
  * Resolve Pi's thinking level once for both native config and persisted replay
39
- * provenance. Keeping these values together prevents a low/minimal turn from
40
- * being replayed later as an enabled-thinking turn merely because the Pi
41
- * option was present.
39
+ * provenance. Keeping these values together gives generation and replay the same
40
+ * enabled-thinking state, including low/minimal turns.
42
41
  */
43
42
  export function resolveReasoningMode(reasoning) {
44
43
  const reasoningEffort = reasoning === undefined ? 'none' : THINKING_LEVEL_TO_EFFORT[reasoning];
45
44
  return {
46
45
  reasoningEffort,
47
- thinkingEnabled: reasoningEffort === 'medium' || reasoningEffort === 'high',
46
+ thinkingEnabled: reasoningEffort !== 'none',
48
47
  };
49
48
  }
50
- export function buildChatConfig(modelType, options, tools, rootCacheOwnerId, resolvedReasoning = resolveReasoningMode(options?.reasoning), modelMaxTokens) {
49
+ /** An explicit hard cap, independent from the model's reasoning effort. */
50
+ export function parseThinkingBudget(value) {
51
+ if (value === undefined)
52
+ return undefined;
53
+ const budget = typeof value === 'string' && /^\d+$/.test(value) ? Number(value) : value;
54
+ if (typeof budget !== 'number' || !Number.isSafeInteger(budget) || budget < 0 || budget > 2_147_483_647) {
55
+ throw new Error('Thinking budget must be an integer between 0 and 2147483647.');
56
+ }
57
+ return budget;
58
+ }
59
+ export function buildChatConfig(modelType, options, tools, rootCacheOwnerId, resolvedReasoning = resolveReasoningMode(options?.reasoning), modelMaxTokens, thinkingTokenBudget) {
51
60
  const preset = launchPresetFor(modelType);
52
61
  if (!preset) {
53
62
  const known = KNOWN_PRESET_MODEL_TYPES.join(', ');
54
63
  throw new Error(`buildChatConfig: no launch preset for model type "${modelType}" (known types: ${known})`);
55
64
  }
65
+ // Pi's numeric budget settings currently stop at high. Preserve effort
66
+ // itself while using that highest configured cap for xhigh/max.
67
+ const budgetLevel = options?.reasoning === 'xhigh' || options?.reasoning === 'max' ? 'high' : options?.reasoning;
68
+ const budget = parseThinkingBudget(thinkingTokenBudget ?? (budgetLevel === undefined ? undefined : options?.thinkingBudgets?.[budgetLevel]));
56
69
  const config = {
70
+ ...(budget === undefined ? {} : { thinkingTokenBudget: budget }),
57
71
  ...preset.sampling,
58
72
  maxNewTokens: preset.maxOutputTokens,
59
73
  reasoningEffort: resolvedReasoning.reasoningEffort,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACrF,OAAO,EAAoC,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG9G,OAAO,EAAE,YAAY,EAA2B,MAAM,oBAAoB,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA4BhD,4EAA4E;AAC5E,MAAM,WAAW,wBAAwB;IACvC,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAC7C,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IAClD,iFAAiF;IACjF,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,YAAY,EAAE,EACtB,IAAI,CAAC,EAAE,YAAY,EACnB,IAAI,GAAE,wBAA6B,GAClC,eAAe,CA6KjB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACrF,OAAO,EAAoC,KAAK,cAAc,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAI9G,OAAO,EAAE,YAAY,EAA2B,MAAM,oBAAoB,CAAC;AAE3E,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA4BhD,4EAA4E;AAC5E,MAAM,WAAW,wBAAwB;IACvC,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAC7C,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,gBAAgB,GAAG,SAAS,CAAC;IAClD,iFAAiF;IACjF,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,YAAY,EAAE,EACtB,IAAI,CAAC,EAAE,YAAY,EACnB,IAAI,GAAE,wBAA6B,GAClC,eAAe,CAoLjB"}
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { coldCacheStats, coldSidecarStats } from '@mlx-node/core';
18
18
  import { canonicalCacheRoot } from '../cold-tier.js';
19
+ import { parseThinkingBudget } from './chat-config.js';
19
20
  import { MetricsTrace } from './metrics-trace.js';
20
21
  import { MLX_API, MLX_API_KEY, MLX_BASE_URL, MLX_PROVIDER_ID } from './mlx-identity.js';
21
22
  import { MlxModelHost } from './model-host.js';
@@ -173,10 +174,16 @@ export function createMlxProviderExtension(models, host, deps = {}) {
173
174
  }
174
175
  metricsTrace.record(rec);
175
176
  };
176
- const streamSimple = makeMlxStreamSimple(resolvedHost, performanceStatus.record, () => rootCacheOwnerId, onTurnRecord, onTurnStart, () => rootSessionFile);
177
+ let getThinkingBudget = () => undefined;
178
+ const streamSimple = makeMlxStreamSimple(resolvedHost, performanceStatus.record, () => rootCacheOwnerId, onTurnRecord, onTurnStart, () => rootSessionFile, () => getThinkingBudget());
177
179
  return {
178
180
  name: 'mlx-provider',
179
181
  factory: (pi) => {
182
+ pi.registerFlag('thinking-budget', {
183
+ type: 'string',
184
+ description: 'Maximum reasoning tokens per model turn; 0 closes thinking immediately.',
185
+ });
186
+ getThinkingBudget = () => parseThinkingBudget(pi.getFlag('thinking-budget'));
180
187
  pi.registerProvider(MLX_PROVIDER_ID, {
181
188
  api: MLX_API,
182
189
  baseUrl: MLX_BASE_URL,
@@ -105,7 +105,7 @@ export declare class MlxModelHost {
105
105
  * retries); a failure thrown by `fn` rejects only this call's promise
106
106
  * and keeps the resident loaded for later callers.
107
107
  */
108
- runWithResident<T>(modelId: string, fn: (session: ChatSession, resident: boolean) => Promise<T>): Promise<T>;
108
+ runWithResident<T>(modelId: string, fn: (session: ChatSession, resident: boolean) => Promise<T>, ownerId?: string): Promise<T>;
109
109
  /**
110
110
  * Flag the current resident as post-error so the next turn does a full
111
111
  * reset instead of a warm reuse. No-op unless `modelId` is the live
@@ -1 +1 @@
1
- {"version":3,"file":"model-host.d.ts","sourceRoot":"","sources":["../../src/provider/model-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAA4B,MAAM,cAAc,CAAC;AAEhF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;GAQG;AACH,OAAO,EAAE,0BAA0B,EAAE,CAAC;AAEtC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;OAUG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/F;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAmBD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4E;IAC/G,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,KAAK,CAAuC;IAEpD,YAAY,MAAM,EAAE,mBAAmB,EAAE,EAAE,IAAI,GAAE,mBAAwB,EAMxE;IAED,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAE1D;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAuD3G;IAED;;;;;OAKG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIvC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO7C;IAED;;;;OAIG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAQtB"}
1
+ {"version":3,"file":"model-host.d.ts","sourceRoot":"","sources":["../../src/provider/model-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,WAAW,EAAE,SAAS,EAA4B,MAAM,cAAc,CAAC;AAGhF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;GAQG;AACH,OAAO,EAAE,0BAA0B,EAAE,CAAC;AAEtC,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;OAUG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,EAAE,MAAM,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/F;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAqBD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAmB;IAC/C,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA4E;IAC/G,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;IAC5C,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,KAAK,CAAuC;IAEpD,YAAY,MAAM,EAAE,mBAAmB,EAAE,EAAE,IAAI,GAAE,mBAAwB,EAMxE;IAED,IAAI,UAAU,IAAI,MAAM,GAAG,IAAI,CAE9B;IAED;;;;;OAKG;IACH,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAE1D;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,eAAe,CAAC,CAAC,EACf,OAAO,EAAE,MAAM,EACf,EAAE,EAAE,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,EAC3D,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC,CAuEZ;IAED;;;;;OAKG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIvC;IAED;;;;;OAKG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO7C;IAED;;;;OAIG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAIxC;IAED;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAQtB"}
@@ -13,6 +13,7 @@
13
13
  * overlapping native activity on the compiled-path globals).
14
14
  */
15
15
  import { ChatSession, loadModel } from '@mlx-node/lm';
16
+ import { findDFlash2Draft } from '@mlx-node/lm/draft-companion';
16
17
  import { COLD_TIER_RESTORE_FAMILIES } from '../cold-tier.js';
17
18
  /**
18
19
  * Re-exported so the HOST consults exactly the symbol the drift guard and the
@@ -72,7 +73,8 @@ export class MlxModelHost {
72
73
  * retries); a failure thrown by `fn` rejects only this call's promise
73
74
  * and keeps the resident loaded for later callers.
74
75
  */
75
- runWithResident(modelId, fn) {
76
+ runWithResident(modelId, fn, ownerId) {
77
+ ownerId = ownerId || undefined;
76
78
  const entry = this.byName.get(modelId);
77
79
  if (!entry) {
78
80
  const known = [...this.byName.keys()].join(', ');
@@ -84,6 +86,21 @@ export class MlxModelHost {
84
86
  // it loaded/swapped the checkpoint first (cold).
85
87
  let resident;
86
88
  if (this.resident?.id === modelId) {
89
+ if (this.resident.ownerId !== ownerId) {
90
+ // Stay on the same serialization chain: the preceding native turn must
91
+ // finish before its owner is released. Keep weights loaded, but never
92
+ // reassign the immutable owner of the old ChatSession.
93
+ try {
94
+ await this.resident.session.dispose();
95
+ }
96
+ catch (error) {
97
+ this.resident = null;
98
+ throw error;
99
+ }
100
+ this.resident.session = new ChatSession(this.resident.model);
101
+ this.resident.ownerId = ownerId;
102
+ this.resident.dirty = false;
103
+ }
87
104
  session = this.resident.session;
88
105
  resident = true;
89
106
  }
@@ -100,12 +117,13 @@ export class MlxModelHost {
100
117
  ? await this.resolveModelPathFn(entry, { persistPagedCache: this.persistPagedCache })
101
118
  : await this.resolveModelPathFn(entry);
102
119
  // Preserve the ordinary one-argument call for unpaired checkpoints.
103
- // A discovered DFlash2 companion is an explicit load option rather
104
- // than a second advertised model: target and draft become one
105
- // resident session and the native loader validates their compatibility.
106
- const model = entry.draftModelPath === undefined
120
+ // Supplied paths are authoritative across draft families. Let the
121
+ // loader validate them and report errors; only absent paths opt into
122
+ // Qwen DFlash2 discovery, resolved against the original target path.
123
+ const draftModelPath = entry.draftModelPath ?? findDFlash2Draft(entry.path, entry.modelType);
124
+ const model = draftModelPath === undefined
107
125
  ? await this.loadModelFn(resolvedPath)
108
- : await this.loadModelFn(resolvedPath, { draftModelPath: entry.draftModelPath });
126
+ : await this.loadModelFn(resolvedPath, { draftModelPath });
109
127
  const sessionModel = model;
110
128
  const gemmaDraftActive = entry.modelType === 'gemma4' && sessionModel.hasMtpWeights?.() === true;
111
129
  if (this.requirePagedCache && sessionModel.hasBlockPagedCache?.() !== true && !gemmaDraftActive) {
@@ -119,7 +137,7 @@ export class MlxModelHost {
119
137
  `this agent session to paged autoregressive decoding`);
120
138
  }
121
139
  session = new ChatSession(sessionModel);
122
- this.resident = { id: modelId, session, model, dirty: false };
140
+ this.resident = { id: modelId, session, model, ownerId, dirty: false };
123
141
  }
124
142
  return await fn(session, resident);
125
143
  });
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Local model discovery for the mlx pi provider.
3
3
  *
4
- * The same discovery walk as `@mlx-node/server/host`
5
- * (`packages/server/src/host/discover.ts`), pairing every discovered checkpoint
4
+ * Uses the same native-free inventory as the inference host and setup UI,
5
+ * pairing every discovered checkpoint
6
6
  * with a pi `ProviderModelConfig` entry ready for
7
7
  * `pi.registerProvider('mlx', { models })`.
8
8
  *
@@ -22,17 +22,6 @@ export interface MlxModelInfo {
22
22
  discovered: DiscoveredModelLike;
23
23
  piModel: ProviderModelConfig;
24
24
  }
25
- /**
26
- * Scan `modelsDir` for chat-capable model subdirectories and native dense
27
- * Qwen3.5/Qwen3.8 `Q<number>_K_XL.gguf` files, then build their pi provider
28
- * entries. XL files may live directly under `modelsDir` or one level inside a
29
- * downloaded GGUF repository. Each is registered by filename stem so multiple
30
- * quant variants in one repository remain independently selectable.
31
- *
32
- * Same tolerance as the cli discover walk: an unreadable dir yields `[]`;
33
- * entries with an undetectable config, a non-generative type, or no launch
34
- * preset are skipped silently (warnings only when `MLX_DEBUG` is set). Cheap
35
- * by contract — no weights are loaded here. Results are sorted by model name.
36
- */
25
+ /** Pair the shared local inventory with pi provider metadata, without loading weights. */
37
26
  export declare function discoverMlxModels(modelsDir: string): Promise<MlxModelInfo[]>;
38
27
  //# sourceMappingURL=models.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/provider/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAS3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,mBAAmB,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAyHD;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAsIlF"}
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/provider/models.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,mBAAmB,CAAC;IAChC,OAAO,EAAE,mBAAmB,CAAC;CAC9B;AAED,0FAA0F;AAC1F,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAiBlF"}