@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/provider/models.js
CHANGED
|
@@ -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
|
*
|
|
@@ -15,243 +15,21 @@
|
|
|
15
15
|
* the per-family fallback documented on `FamilyTraits` (`@mlx-node/lm`
|
|
16
16
|
* family-data) applies.
|
|
17
17
|
*/
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
import { launchPresetFor, detectModelType, familyTraitsFor, NON_GENERATIVE_FAMILY_IDS, } from '@mlx-node/lm';
|
|
21
|
-
/**
|
|
22
|
-
* Native direct-GGUF loading currently exists for dense Qwen3.5/Qwen3.8.
|
|
23
|
-
* Match the Unsloth Dynamic XL target names users download, while excluding
|
|
24
|
-
* ordinary Q4_K_M files and companion artifacts such as imatrix/mmproj/draft.
|
|
25
|
-
*/
|
|
26
|
-
const QWEN35_XL_GGUF = /(?:^|[-_.])Q\d+_K_XL\.gguf$/i;
|
|
27
|
-
const GGUF_COMPANION_NAME = /(?:^|[-_.])(?:imatrix|mmproj|dflash|draft)(?:[-_.]|$)/i;
|
|
28
|
-
function isQwen35XlGguf(name) {
|
|
29
|
-
return QWEN35_XL_GGUF.test(name) && !GGUF_COMPANION_NAME.test(name);
|
|
30
|
-
}
|
|
31
|
-
function ggufModelName(name) {
|
|
32
|
-
return name.slice(0, -'.gguf'.length);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Agent-local convention for pairing a dense Qwen3.5/Qwen3.8 target with an
|
|
36
|
-
* external DFlash2 checkpoint. mlx-vlm accepts an explicit `--draft-model`
|
|
37
|
-
* path and does not define a combined directory layout; the agent needs a
|
|
38
|
-
* deterministic relationship it can discover without another CLI flag.
|
|
39
|
-
*/
|
|
40
|
-
async function embeddedDFlash2Path(modelDir) {
|
|
41
|
-
const draftPath = join(modelDir, 'draft');
|
|
42
|
-
try {
|
|
43
|
-
const raw = await readFile(join(draftPath, 'config.json'), 'utf-8');
|
|
44
|
-
const config = JSON.parse(raw);
|
|
45
|
-
return Array.isArray(config.architectures) && config.architectures.includes('DFlash2DraftModel')
|
|
46
|
-
? draftPath
|
|
47
|
-
: undefined;
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
async function modelFileInventory(modelDir) {
|
|
54
|
-
try {
|
|
55
|
-
const files = (await readdir(modelDir, { withFileTypes: true }))
|
|
56
|
-
.filter((entry) => entry.isFile())
|
|
57
|
-
.map((entry) => entry.name);
|
|
58
|
-
return {
|
|
59
|
-
xlGgufs: files.filter(isQwen35XlGguf).sort(),
|
|
60
|
-
hasGguf: files.some((name) => name.toLowerCase().endsWith('.gguf')),
|
|
61
|
-
hasSafetensors: files.some((name) => name.toLowerCase().endsWith('.safetensors')),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return { xlGgufs: [], hasGguf: false, hasSafetensors: false };
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function positiveInteger(value) {
|
|
69
|
-
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
|
|
70
|
-
}
|
|
71
|
-
function nonEmptyRecord(value) {
|
|
72
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value) && Object.keys(value).length > 0;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Read cheap discovery metadata from `<modelPath>/config.json`.
|
|
76
|
-
*
|
|
77
|
-
* The trained context window comes from:
|
|
78
|
-
* root `max_position_embeddings` first (qwen3, lfm2), then
|
|
79
|
-
* `text_config.max_position_embeddings` (qwen3_5, qwen3_5_moe, gemma4
|
|
80
|
-
* unified), else the family fallback.
|
|
81
|
-
*
|
|
82
|
-
* Image support is advertised only when a family with a native multimodal
|
|
83
|
-
* implementation carries its valid, non-empty vision marker: `vision_config`
|
|
84
|
-
* for Qwen, and either `vision_config` or `unified_vision_config` for Gemma.
|
|
85
|
-
* This lets Pi's model picker and `--list-models` expose checkpoint capability
|
|
86
|
-
* without loading weights. The first resident load remains authoritative and
|
|
87
|
-
* reconciles this optimistic config-level advertisement via
|
|
88
|
-
* `session.supportsImages()` (for example, when conversion stripped an
|
|
89
|
-
* incompatible vision tower).
|
|
90
|
-
*
|
|
91
|
-
* `detectModelType` already parsed this file, so a read/parse failure here
|
|
92
|
-
* (e.g. a racing rewrite) lands on the context fallback and text-only input
|
|
93
|
-
* instead of dropping the model or guessing a positive capability.
|
|
94
|
-
*/
|
|
95
|
-
async function readDiscoveryMetadata(modelPath, modelType, fallbackContextWindow) {
|
|
96
|
-
try {
|
|
97
|
-
const raw = await readFile(join(modelPath, 'config.json'), 'utf-8');
|
|
98
|
-
const config = JSON.parse(raw);
|
|
99
|
-
const root = positiveInteger(config.max_position_embeddings);
|
|
100
|
-
const textConfig = config.text_config;
|
|
101
|
-
const nested = nonEmptyRecord(textConfig) ? positiveInteger(textConfig.max_position_embeddings) : undefined;
|
|
102
|
-
const hasVisionConfig = nonEmptyRecord(config.vision_config);
|
|
103
|
-
const supportsImages = modelType === 'gemma4'
|
|
104
|
-
? hasVisionConfig || nonEmptyRecord(config.unified_vision_config)
|
|
105
|
-
: (modelType === 'qwen3_5' || modelType === 'qwen3_5_moe') && hasVisionConfig;
|
|
106
|
-
const draftOnly = Array.isArray(config.architectures) && config.architectures.includes('DFlash2DraftModel');
|
|
107
|
-
return {
|
|
108
|
-
contextWindow: root ?? nested ?? fallbackContextWindow,
|
|
109
|
-
supportsImages,
|
|
110
|
-
draftOnly,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return { contextWindow: fallbackContextWindow, supportsImages: false, draftOnly: false };
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Scan `modelsDir` for chat-capable model subdirectories and native dense
|
|
119
|
-
* Qwen3.5/Qwen3.8 `Q<number>_K_XL.gguf` files, then build their pi provider
|
|
120
|
-
* entries. XL files may live directly under `modelsDir` or one level inside a
|
|
121
|
-
* downloaded GGUF repository. Each is registered by filename stem so multiple
|
|
122
|
-
* quant variants in one repository remain independently selectable.
|
|
123
|
-
*
|
|
124
|
-
* Same tolerance as the cli discover walk: an unreadable dir yields `[]`;
|
|
125
|
-
* entries with an undetectable config, a non-generative type, or no launch
|
|
126
|
-
* preset are skipped silently (warnings only when `MLX_DEBUG` is set). Cheap
|
|
127
|
-
* by contract — no weights are loaded here. Results are sorted by model name.
|
|
128
|
-
*/
|
|
18
|
+
import { discoverLocalChatModels } from '@mlx-node/lm/model-discovery';
|
|
19
|
+
/** Pair the shared local inventory with pi provider metadata, without loading weights. */
|
|
129
20
|
export async function discoverMlxModels(modelsDir) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
// Collision resolution below gives the first occurrence the bare filename
|
|
139
|
-
// stem. Directory enumeration order is unspecified, so sort before assigning
|
|
140
|
-
// IDs to keep persisted `mlx/<id>` selections stable across filesystems.
|
|
141
|
-
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
142
|
-
const out = [];
|
|
143
|
-
const usedNames = new Set();
|
|
144
|
-
const append = async (preferredName, path, metadataRoot, modelType, scopeName, draftModelPath) => {
|
|
145
|
-
if (NON_GENERATIVE_FAMILY_IDS.has(modelType))
|
|
146
|
-
return;
|
|
147
|
-
// Fail-closed guards: dead-by-construction for chat families (the
|
|
148
|
-
// family-data row type requires traits + a preset), live for any foreign
|
|
149
|
-
// string that slips through detection.
|
|
150
|
-
const preset = launchPresetFor(modelType);
|
|
151
|
-
if (!preset) {
|
|
152
|
-
if (debug)
|
|
153
|
-
console.warn(`[mlx] skip ${path}: no launch preset for ${modelType}`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const traits = familyTraitsFor(modelType);
|
|
157
|
-
if (!traits) {
|
|
158
|
-
if (debug)
|
|
159
|
-
console.warn(`[mlx] skip ${path}: no FAMILY_TRAITS entry for ${modelType}`);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const metadata = await readDiscoveryMetadata(metadataRoot, modelType, traits.fallbackContextWindow);
|
|
163
|
-
if (metadata.draftOnly) {
|
|
164
|
-
if (debug)
|
|
165
|
-
console.warn(`[mlx] skip ${path}: companion draft checkpoint is not a chat model`);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
let name = preferredName;
|
|
169
|
-
if (usedNames.has(name)) {
|
|
170
|
-
name = `${scopeName}-${preferredName}`;
|
|
171
|
-
let suffix = 2;
|
|
172
|
-
while (usedNames.has(name))
|
|
173
|
-
name = `${scopeName}-${preferredName}-${suffix++}`;
|
|
174
|
-
}
|
|
175
|
-
usedNames.add(name);
|
|
176
|
-
const discovered = {
|
|
21
|
+
return (await discoverLocalChatModels(modelsDir)).map(({ name, path, modelType, preset, traits, supportsImages, contextWindow }) => ({
|
|
22
|
+
discovered: { name, path, modelType },
|
|
23
|
+
piModel: {
|
|
24
|
+
id: name,
|
|
177
25
|
name,
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
reasoning: traits.reasoning,
|
|
188
|
-
// The structural FamilyThinkingLevelMap must stay assignable to the
|
|
189
|
-
// pi type (pi types are agent-only, so family-data cannot name it).
|
|
190
|
-
thinkingLevelMap: traits.thinkingLevelMap,
|
|
191
|
-
input: metadata.supportsImages ? ['text', 'image'] : ['text'],
|
|
192
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
193
|
-
contextWindow: metadata.contextWindow,
|
|
194
|
-
maxTokens: preset.maxOutputTokens,
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
for (const entry of entries) {
|
|
199
|
-
if (entry.isFile() && isQwen35XlGguf(entry.name)) {
|
|
200
|
-
const full = join(modelsDir, entry.name);
|
|
201
|
-
try {
|
|
202
|
-
const modelType = await detectModelType(full);
|
|
203
|
-
if (modelType === 'qwen3_5') {
|
|
204
|
-
await append(ggufModelName(entry.name), full, modelsDir, modelType, basename(modelsDir));
|
|
205
|
-
}
|
|
206
|
-
else if (debug) {
|
|
207
|
-
console.warn(`[mlx] skip ${full}: direct XL GGUF loading is not supported for ${modelType}`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
catch (err) {
|
|
211
|
-
if (debug)
|
|
212
|
-
console.warn(`[mlx] skip ${full}: ${err.message}`);
|
|
213
|
-
}
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (!entry.isDirectory())
|
|
217
|
-
continue;
|
|
218
|
-
const full = join(modelsDir, entry.name);
|
|
219
|
-
let modelType;
|
|
220
|
-
try {
|
|
221
|
-
modelType = await detectModelType(full);
|
|
222
|
-
}
|
|
223
|
-
catch (err) {
|
|
224
|
-
if (debug)
|
|
225
|
-
console.warn(`[mlx] skip ${full}: ${err.message}`);
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
const inventory = await modelFileInventory(full);
|
|
229
|
-
const { xlGgufs } = inventory;
|
|
230
|
-
if (xlGgufs.length > 0) {
|
|
231
|
-
if (modelType !== 'qwen3_5') {
|
|
232
|
-
if (debug) {
|
|
233
|
-
console.warn(`[mlx] skip ${full}: direct XL GGUF loading is not supported for ${modelType}`);
|
|
234
|
-
}
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
const draftModelPath = await embeddedDFlash2Path(full);
|
|
238
|
-
for (const gguf of xlGgufs) {
|
|
239
|
-
await append(ggufModelName(gguf), join(full, gguf), full, modelType, entry.name, draftModelPath);
|
|
240
|
-
}
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
// A raw GGUF repository is not itself a loadable model path. Only the
|
|
244
|
-
// selected native Qwen3.5 XL files above may be handed directly to the
|
|
245
|
-
// loader. Keep converted model directories discoverable when they retain
|
|
246
|
-
// an imatrix/source GGUF beside their actual SafeTensors weights.
|
|
247
|
-
if (inventory.hasGguf && !inventory.hasSafetensors) {
|
|
248
|
-
if (debug)
|
|
249
|
-
console.warn(`[mlx] skip ${full}: no supported direct GGUF target`);
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
const draftModelPath = modelType === 'qwen3_5' ? await embeddedDFlash2Path(full) : undefined;
|
|
253
|
-
await append(basename(full), full, full, modelType, entry.name, draftModelPath);
|
|
254
|
-
}
|
|
255
|
-
out.sort((a, b) => (a.discovered.name < b.discovered.name ? -1 : a.discovered.name > b.discovered.name ? 1 : 0));
|
|
256
|
-
return out;
|
|
26
|
+
reasoning: traits.reasoning,
|
|
27
|
+
// pi types are agent-only; keep the shared structural map assignable.
|
|
28
|
+
thinkingLevelMap: traits.thinkingLevelMap,
|
|
29
|
+
input: supportsImages ? ['text', 'image'] : ['text'],
|
|
30
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
31
|
+
contextWindow,
|
|
32
|
+
maxTokens: preset.maxOutputTokens,
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
257
35
|
}
|
|
@@ -51,7 +51,7 @@ export interface StreamSimpleHost {
|
|
|
51
51
|
* `fn` receives a `resident` boolean: `true` when the model was already warm
|
|
52
52
|
* (reused), `false` when this turn had to load/swap it.
|
|
53
53
|
*/
|
|
54
|
-
runWithResident<T>(modelId: string, fn: (session: ChatSession, resident: boolean) => Promise<T
|
|
54
|
+
runWithResident<T>(modelId: string, fn: (session: ChatSession, resident: boolean) => Promise<T>, ownerId?: string): Promise<T>;
|
|
55
55
|
/** Flag the resident as post-error so the next turn does a full reset (see `MlxModelHost`). */
|
|
56
56
|
markResidentDirty(modelId: string): void;
|
|
57
57
|
/** Read-and-clear the resident's post-error flag; `true` ⇒ full-reset this turn. */
|
|
@@ -90,5 +90,5 @@ export type TurnRecorder = (rec: {
|
|
|
90
90
|
/** `true` when the model was already warm/resident, `false` on a cold load/swap this turn. */
|
|
91
91
|
resident: boolean;
|
|
92
92
|
}) => void;
|
|
93
|
-
export declare function makeMlxStreamSimple(host: StreamSimpleHost, onPerformance?: PerformanceRecorder, resolveRootCacheOwner?: RootCacheOwnerResolver, onTurnRecord?: TurnRecorder, onTurnStart?: () => void, resolveRootSessionFile?: RootSessionFileResolver): (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
93
|
+
export declare function makeMlxStreamSimple(host: StreamSimpleHost, onPerformance?: PerformanceRecorder, resolveRootCacheOwner?: RootCacheOwnerResolver, onTurnRecord?: TurnRecorder, onTurnStart?: () => void, resolveRootSessionFile?: RootSessionFileResolver, resolveThinkingBudget?: () => number | undefined): (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
|
|
94
94
|
//# sourceMappingURL=stream-adapter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream-adapter.d.ts","sourceRoot":"","sources":["../../src/provider/stream-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,gBAAgB,EAChB,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOvD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;IAC5D;;;;OAIG;IACH,eAAe,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"stream-adapter.d.ts","sourceRoot":"","sources":["../../src/provider/stream-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EACV,GAAG,EACH,gBAAgB,EAChB,2BAA2B,EAC3B,OAAO,EACP,KAAK,EACL,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAErF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAOvD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kFAAkF;IAClF,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAAC;IAC5D;;;;OAIG;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,CAAC;IACd,+FAA+F;IAC/F,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,oFAAoF;IACpF,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC/C,gFAAgF;IAChF,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3C;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,kBAAkB,KAAK,IAAI,CAAC;AACvG,MAAM,MAAM,sBAAsB,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAC9D,qFAAqF;AACrF,MAAM,MAAM,uBAAuB,GAAG,MAAM,MAAM,GAAG,SAAS,CAAC;AAE/D;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,eAAe,CAAC;IACvB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,QAAQ,EAAE,OAAO,CAAC;CACnB,KAAK,IAAI,CAAC;AAwGX,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,gBAAgB,EACtB,aAAa,CAAC,EAAE,mBAAmB,EACnC,qBAAqB,CAAC,EAAE,sBAAsB,EAC9C,YAAY,CAAC,EAAE,YAAY,EAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,EACxB,sBAAsB,CAAC,EAAE,uBAAuB,EAChD,qBAAqB,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GAC/C,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,mBAAmB,KAAK,2BAA2B,CAiSrG"}
|
|
@@ -140,9 +140,10 @@ function failsafeMessage(model, reason, message) {
|
|
|
140
140
|
timestamp: Date.now(),
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner, onTurnRecord, onTurnStart, resolveRootSessionFile) {
|
|
143
|
+
export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner, onTurnRecord, onTurnStart, resolveRootSessionFile, resolveThinkingBudget) {
|
|
144
144
|
return (model, context, options) => {
|
|
145
145
|
const stream = createAssistantMessageEventStream();
|
|
146
|
+
let thinkingTokenBudget;
|
|
146
147
|
/**
|
|
147
148
|
* Exactly-one-terminal guard for the WHOLE turn. `TurnEmitter` has its
|
|
148
149
|
* own `finished` flag, but it cannot cover pre-emitter failures or the
|
|
@@ -231,7 +232,8 @@ export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner,
|
|
|
231
232
|
rootSessionFile = resolveRootSessionFile?.();
|
|
232
233
|
// Snapshot once: the native config and the replay provenance must describe
|
|
233
234
|
// the same resolved template mode. Presence alone is wrong for Pi's
|
|
234
|
-
//
|
|
235
|
+
// off is disabled; low remains enabled thinking.
|
|
236
|
+
thinkingTokenBudget = resolveThinkingBudget?.();
|
|
235
237
|
resolvedReasoning = resolveReasoningMode(options?.reasoning);
|
|
236
238
|
emitter = new TurnEmitter(stream, model, onPerformance, resolvedReasoning.thinkingEnabled);
|
|
237
239
|
}
|
|
@@ -261,7 +263,7 @@ export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner,
|
|
|
261
263
|
// prefill + decode) so a MetricsTrace record can report wall-clock
|
|
262
264
|
// duration. Read at the terminal only on the success path below.
|
|
263
265
|
const turnStartedAt = Date.now();
|
|
264
|
-
|
|
266
|
+
const runTurn = async (session, resident) => {
|
|
265
267
|
// Callback entry: the queue wait + any cold model load/swap have now
|
|
266
268
|
// resolved, but no native work (prime/prefill/decode) has started. This
|
|
267
269
|
// is the seam that separates queue+load latency from execution time —
|
|
@@ -319,7 +321,7 @@ export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner,
|
|
|
319
321
|
return;
|
|
320
322
|
try {
|
|
321
323
|
session.primeHistory(contextToChatMessages(context, supportsImages));
|
|
322
|
-
const config = buildChatConfig(discovered.modelType, options, toolsToDefinitions(context.tools), rootCacheOwnerId, resolvedReasoning, configuredModelMaxTokens);
|
|
324
|
+
const config = buildChatConfig(discovered.modelType, options, toolsToDefinitions(context.tools), rootCacheOwnerId, resolvedReasoning, configuredModelMaxTokens, thinkingTokenBudget);
|
|
323
325
|
for await (const event of session.startFromHistoryStream(config, signal)) {
|
|
324
326
|
if (event.done) {
|
|
325
327
|
if (event.finishReason === 'error') {
|
|
@@ -414,7 +416,8 @@ export function makeMlxStreamSimple(host, onPerformance, resolveRootCacheOwner,
|
|
|
414
416
|
}
|
|
415
417
|
throw err;
|
|
416
418
|
}
|
|
417
|
-
}
|
|
419
|
+
};
|
|
420
|
+
await host.runWithResident(model.id, runTurn, options?.sessionId);
|
|
418
421
|
if (!terminated && !sawNativeFinal) {
|
|
419
422
|
// An aborted native stream ends cleanly with NO final event; any
|
|
420
423
|
// other final-less ending is a native-protocol violation.
|
package/dist/run-agent.d.ts
CHANGED
|
@@ -33,6 +33,10 @@ export interface AgentPagedConfigOverrides {
|
|
|
33
33
|
cleanup(): Promise<void>;
|
|
34
34
|
}
|
|
35
35
|
export interface RunAgentOptions {
|
|
36
|
+
/** Focused worker tools/permissions; inference and session defaults remain shared. */
|
|
37
|
+
mode?: 'delegate';
|
|
38
|
+
/** Explicit approval supplied by the outer caller, captured before the worker starts. */
|
|
39
|
+
delegateCallerApproved?: boolean;
|
|
36
40
|
/** Resolved models directory (context for callers/diagnostics — discovery already ran). */
|
|
37
41
|
modelsDir: string;
|
|
38
42
|
/** Discovered models to serve through the in-process `mlx` provider. */
|
package/dist/run-agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-agent.d.ts","sourceRoot":"","sources":["../src/run-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"run-agent.d.ts","sourceRoot":"","sources":["../src/run-agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAYvE,OAAO,EACL,KAAK,iCAAiC,EAEvC,MAAM,qCAAqC,CAAC;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,+EAA+E;AAC/E,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE;IAAE,kBAAkB,EAAE,eAAe,EAAE,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9G,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,YAAY,EAAE,iCAAiC,CAAC;CACjD;AAED,6EAA6E;AAC7E,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7F,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yFAAyF;IACzF,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,yDAAyD;IACzD,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gFAAgF;IAChF,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,uEAAuE;IACvE,oBAAoB,CAAC,EAAE,yBAAyB,CAAC;CAClD;AAED,mFAAmF;AACnF,wBAAgB,sBAAsB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAEpF;AAED;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgFnE"}
|
package/dist/run-agent.js
CHANGED
|
@@ -20,6 +20,7 @@ import { homedir } from 'node:os';
|
|
|
20
20
|
import { join } from 'node:path';
|
|
21
21
|
import { coldCacheDrain } from '@mlx-node/core';
|
|
22
22
|
import { PagedConfigOverrideManager } from '@mlx-node/lm';
|
|
23
|
+
import { createDelegationExtension } from './extensions/delegation.js';
|
|
23
24
|
import { createLocalImageInputExtension } from './extensions/local-image-input.js';
|
|
24
25
|
import { createPermissionGateExtension } from './extensions/permission-gate.js';
|
|
25
26
|
import { createSubagentExtension } from './extensions/subagent.js';
|
|
@@ -77,13 +78,18 @@ export async function runAgent(opts) {
|
|
|
77
78
|
// facade), which is where the selector / listing / resolution paths read.
|
|
78
79
|
const pi = opts.piImpl ?? (await import('@earendil-works/pi-coding-agent'));
|
|
79
80
|
const restoreModelRegistry = installMlxOnlyModelRegistryFilter(pi.ModelRuntime, opts.models.map((model) => model.discovered.name));
|
|
80
|
-
const subagentsEnabled = opts.
|
|
81
|
+
const subagentsEnabled = opts.mode !== 'delegate' &&
|
|
82
|
+
opts.models.length > 0 &&
|
|
83
|
+
!opts.argv.includes('--no-extensions') &&
|
|
84
|
+
!opts.argv.includes('-ne');
|
|
81
85
|
try {
|
|
82
86
|
await pi.main(opts.argv, {
|
|
83
87
|
extensionFactories: [
|
|
84
88
|
createMlxProviderExtension(opts.models, modelHost),
|
|
85
89
|
createLocalImageInputExtension(),
|
|
86
|
-
|
|
90
|
+
opts.mode === 'delegate'
|
|
91
|
+
? createDelegationExtension({ callerApproved: opts.delegateCallerApproved })
|
|
92
|
+
: createPermissionGateExtension(),
|
|
87
93
|
...(subagentsEnabled ? [createSubagentExtension()] : []),
|
|
88
94
|
...(opts.traceLogFile !== undefined ? [createTraceNoticeExtension(opts.traceLogFile)] : []),
|
|
89
95
|
createTerminalTitleExtension(),
|
package/dist/types.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export interface DiscoveredModelLike {
|
|
|
4
4
|
name: string;
|
|
5
5
|
path: string;
|
|
6
6
|
modelType: ModelType;
|
|
7
|
-
/**
|
|
7
|
+
/** External speculative drafter. When supplied, forwarded unchanged for loader validation. */
|
|
8
8
|
draftModelPath?: string;
|
|
9
9
|
}
|
|
10
10
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,4FAA4F;AAC5F,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,8FAA8F;IAC9F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mlx-node/agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"homepage": "https://github.com/mlx-node/mlx-node",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/mlx-node/mlx-node/issues"
|
|
@@ -12,19 +12,37 @@
|
|
|
12
12
|
"directory": "packages/agent"
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
|
-
"dist"
|
|
15
|
+
"dist",
|
|
16
|
+
"src"
|
|
16
17
|
],
|
|
17
18
|
"type": "module",
|
|
18
19
|
"main": "./dist/index.js",
|
|
19
20
|
"types": "./dist/index.d.ts",
|
|
20
21
|
"exports": {
|
|
21
22
|
".": {
|
|
23
|
+
"@mlx-node/source": "./src/index.ts",
|
|
22
24
|
"types": "./dist/index.d.ts",
|
|
23
25
|
"import": "./dist/index.js"
|
|
24
26
|
},
|
|
27
|
+
"./delegate": {
|
|
28
|
+
"@mlx-node/source": "./src/delegate.ts",
|
|
29
|
+
"types": "./dist/delegate.d.ts",
|
|
30
|
+
"default": "./dist/delegate.js"
|
|
31
|
+
},
|
|
25
32
|
"./catalog": {
|
|
33
|
+
"@mlx-node/source": "./src/catalog.ts",
|
|
26
34
|
"types": "./dist/catalog.d.ts",
|
|
27
35
|
"default": "./dist/catalog.js"
|
|
36
|
+
},
|
|
37
|
+
"./paths": {
|
|
38
|
+
"@mlx-node/source": "./src/paths.ts",
|
|
39
|
+
"types": "./dist/paths.d.ts",
|
|
40
|
+
"default": "./dist/paths.js"
|
|
41
|
+
},
|
|
42
|
+
"./models": {
|
|
43
|
+
"@mlx-node/source": "./src/provider/models.ts",
|
|
44
|
+
"types": "./dist/provider/models.d.ts",
|
|
45
|
+
"default": "./dist/provider/models.js"
|
|
28
46
|
}
|
|
29
47
|
},
|
|
30
48
|
"scripts": {
|
|
@@ -33,9 +51,9 @@
|
|
|
33
51
|
"dependencies": {
|
|
34
52
|
"@earendil-works/pi-ai": "0.84.4",
|
|
35
53
|
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
36
|
-
"@mlx-node/core": "0.0.
|
|
37
|
-
"@mlx-node/lm": "0.0.
|
|
38
|
-
"@mlx-node/server": "0.0.
|
|
54
|
+
"@mlx-node/core": "0.0.15",
|
|
55
|
+
"@mlx-node/lm": "0.0.15",
|
|
56
|
+
"@mlx-node/server": "0.0.15",
|
|
39
57
|
"typebox": "1.3.23"
|
|
40
58
|
},
|
|
41
59
|
"devDependencies": {
|