@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.
- package/dist/catalog.d.ts +10 -1
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +11 -2
- package/dist/delegate.d.ts +29 -0
- package/dist/delegate.d.ts.map +1 -0
- package/dist/delegate.js +106 -0
- package/dist/extensions/delegation.d.ts +15 -0
- package/dist/extensions/delegation.d.ts.map +1 -0
- package/dist/extensions/delegation.js +93 -0
- package/dist/paths.d.ts +6 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +16 -0
- package/dist/provider/chat-config.d.ts +6 -5
- package/dist/provider/chat-config.d.ts.map +1 -1
- package/dist/provider/chat-config.js +21 -7
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +8 -1
- package/dist/provider/model-host.d.ts +1 -1
- package/dist/provider/model-host.d.ts.map +1 -1
- package/dist/provider/model-host.js +25 -7
- package/dist/provider/models.d.ts +3 -14
- package/dist/provider/models.d.ts.map +1 -1
- package/dist/provider/models.js +17 -239
- package/dist/provider/stream-adapter.d.ts +2 -2
- package/dist/provider/stream-adapter.d.ts.map +1 -1
- package/dist/provider/stream-adapter.js +8 -5
- package/dist/run-agent.d.ts +4 -0
- package/dist/run-agent.d.ts.map +1 -1
- package/dist/run-agent.js +8 -2
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +23 -5
- package/src/catalog.ts +194 -0
- package/src/cold-tier.ts +152 -0
- package/src/delegate.ts +136 -0
- package/src/extensions/approval-detail.ts +57 -0
- package/src/extensions/delegation.ts +109 -0
- package/src/extensions/local-image-input.ts +132 -0
- package/src/extensions/permission-gate.ts +347 -0
- package/src/extensions/subagent.ts +743 -0
- package/src/extensions/terminal-title.ts +53 -0
- package/src/extensions/trace-notice.ts +37 -0
- package/src/index.ts +23 -0
- package/src/paths.ts +36 -0
- package/src/provider/chat-config.ts +132 -0
- package/src/provider/convert-messages.ts +273 -0
- package/src/provider/error-coercion.ts +36 -0
- package/src/provider/events.ts +341 -0
- package/src/provider/index.ts +255 -0
- package/src/provider/metrics-trace.ts +380 -0
- package/src/provider/mlx-identity.ts +16 -0
- package/src/provider/model-host.ts +276 -0
- package/src/provider/model-registry-filter.ts +336 -0
- package/src/provider/models.ts +48 -0
- package/src/provider/performance-status.ts +112 -0
- package/src/provider/reasoning-tag-buffer.ts +67 -0
- package/src/provider/stream-adapter.ts +515 -0
- package/src/provider/tool-call-buffer.ts +82 -0
- package/src/provider/warm-reuse.ts +125 -0
- package/src/run-agent.ts +178 -0
- 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
|
|
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
|
package/dist/catalog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
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
|
|
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"}
|
package/dist/delegate.js
ADDED
|
@@ -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. */
|
package/dist/paths.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
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
|
|
17
|
-
*
|
|
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
|
-
|
|
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;
|
|
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: '
|
|
32
|
-
max: '
|
|
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
|
|
40
|
-
*
|
|
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
|
|
46
|
+
thinkingEnabled: reasoningEffort !== 'none',
|
|
48
47
|
};
|
|
49
48
|
}
|
|
50
|
-
|
|
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;
|
|
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"}
|
package/dist/provider/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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;
|
|
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
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
const
|
|
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
|
|
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
|
-
*
|
|
5
|
-
*
|
|
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;
|
|
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"}
|