create-walle 0.9.25 → 0.9.26
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/README.md +8 -0
- package/bin/create-walle.js +815 -45
- package/package.json +2 -2
- package/template/bin/ctm-dev-cleanup.js +90 -4
- package/template/bin/ctm-launch.sh +49 -1
- package/template/bin/dev.sh +45 -1
- package/template/bin/ensure-stable-node.js +132 -0
- package/template/bin/install-service.sh +9 -0
- package/template/claude-task-manager/api-prompts.js +899 -119
- package/template/claude-task-manager/approval-agent.js +360 -40
- package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
- package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
- package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
- package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
- package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
- package/template/claude-task-manager/db.js +399 -48
- package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
- package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
- package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
- package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
- package/template/claude-task-manager/lib/approval-hook.js +200 -0
- package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
- package/template/claude-task-manager/lib/auth-rules.js +11 -0
- package/template/claude-task-manager/lib/background-llm.js +32 -4
- package/template/claude-task-manager/lib/codesign-identity.js +140 -0
- package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
- package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
- package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
- package/template/claude-task-manager/lib/codex-paths.js +73 -0
- package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
- package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
- package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
- package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
- package/template/claude-task-manager/lib/command-targets.js +163 -0
- package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
- package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
- package/template/claude-task-manager/lib/escalation-review.js +80 -3
- package/template/claude-task-manager/lib/flow-control.js +52 -0
- package/template/claude-task-manager/lib/fs-watcher.js +24 -15
- package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
- package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
- package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
- package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
- package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
- package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
- package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
- package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
- package/template/claude-task-manager/lib/perf-tracker.js +29 -2
- package/template/claude-task-manager/lib/permission-match.js +146 -16
- package/template/claude-task-manager/lib/project-slug.js +33 -0
- package/template/claude-task-manager/lib/prompt-intent.js +51 -4
- package/template/claude-task-manager/lib/read-pool-client.js +48 -3
- package/template/claude-task-manager/lib/real-node.js +73 -0
- package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
- package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
- package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
- package/template/claude-task-manager/lib/session-history.js +5 -7
- package/template/claude-task-manager/lib/session-host-manager.js +19 -0
- package/template/claude-task-manager/lib/session-jobs.js +6 -0
- package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
- package/template/claude-task-manager/lib/session-messages-page.js +211 -0
- package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
- package/template/claude-task-manager/lib/session-standup.js +8 -0
- package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
- package/template/claude-task-manager/lib/session-token-usage.js +30 -8
- package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
- package/template/claude-task-manager/lib/storage-migration.js +2 -1
- package/template/claude-task-manager/lib/transcript-store.js +179 -12
- package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
- package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
- package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
- package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
- package/template/claude-task-manager/package.json +5 -2
- package/template/claude-task-manager/prompt-harvest.js +31 -11
- package/template/claude-task-manager/providers/claude-code.js +29 -1
- package/template/claude-task-manager/providers/codex.js +13 -1
- package/template/claude-task-manager/public/css/setup.css +11 -0
- package/template/claude-task-manager/public/css/walle-session.css +132 -4
- package/template/claude-task-manager/public/css/walle.css +89 -0
- package/template/claude-task-manager/public/icon-16.png +0 -0
- package/template/claude-task-manager/public/icon-32.png +0 -0
- package/template/claude-task-manager/public/icon-512.png +0 -0
- package/template/claude-task-manager/public/index.html +2483 -165
- package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
- package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
- package/template/claude-task-manager/public/js/message-renderer.js +60 -1
- package/template/claude-task-manager/public/js/prompts.js +13 -1
- package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
- package/template/claude-task-manager/public/js/setup.js +54 -10
- package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
- package/template/claude-task-manager/public/js/stream-view.js +78 -0
- package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
- package/template/claude-task-manager/public/js/tool-state.js +155 -0
- package/template/claude-task-manager/public/js/walle-session.js +887 -326
- package/template/claude-task-manager/public/js/walle.js +306 -195
- package/template/claude-task-manager/public/m/app.css +1 -0
- package/template/claude-task-manager/public/m/app.js +33 -3
- package/template/claude-task-manager/queue-engine.js +45 -1
- package/template/claude-task-manager/server.js +3367 -540
- package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
- package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
- package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
- package/template/claude-task-manager/workers/session-host-process.js +10 -0
- package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
- package/template/package.json +2 -3
- package/template/shared/icons/AppIcon-ctm.icns +0 -0
- package/template/shared/icons/AppIcon-walle.icns +0 -0
- package/template/wall-e/agent.js +139 -18
- package/template/wall-e/api-walle.js +201 -22
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
- package/template/wall-e/brain.js +1049 -39
- package/template/wall-e/chat.js +427 -86
- package/template/wall-e/coding/acceptance-contract.js +26 -1
- package/template/wall-e/coding/action-memory-policy.js +353 -0
- package/template/wall-e/coding/action-memory-store.js +814 -0
- package/template/wall-e/coding/initial-messages.js +197 -0
- package/template/wall-e/coding/no-progress-guard.js +327 -0
- package/template/wall-e/coding/permission-service.js +88 -22
- package/template/wall-e/coding/session-workspaces.js +81 -0
- package/template/wall-e/coding/shell-sandbox.js +124 -0
- package/template/wall-e/coding/stream-processor.js +63 -2
- package/template/wall-e/coding/tool-execution-controller.js +14 -1
- package/template/wall-e/coding/tool-registry.js +1 -1
- package/template/wall-e/coding/transcript-writer.js +3 -0
- package/template/wall-e/coding-orchestrator.js +636 -35
- package/template/wall-e/coding-prompts.js +51 -2
- package/template/wall-e/docs/model-routing-policy.md +59 -0
- package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
- package/template/wall-e/extraction/knowledge-extractor.js +76 -23
- package/template/wall-e/http/chat-api.js +30 -12
- package/template/wall-e/http/model-admin.js +93 -1
- package/template/wall-e/lib/background-lanes.js +133 -0
- package/template/wall-e/lib/boot-profile.js +11 -0
- package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
- package/template/wall-e/lib/brain-read-pool-client.js +311 -0
- package/template/wall-e/lib/diagnostics-flags.js +87 -0
- package/template/wall-e/lib/event-loop-monitor.js +74 -3
- package/template/wall-e/lib/mcp-integration.js +7 -1
- package/template/wall-e/lib/real-node.js +98 -0
- package/template/wall-e/lib/runtime-health.js +206 -0
- package/template/wall-e/lib/runtime-worker-pool.js +101 -0
- package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
- package/template/wall-e/lib/scheduler.js +446 -17
- package/template/wall-e/lib/service-health.js +61 -2
- package/template/wall-e/lib/service-readiness.js +258 -0
- package/template/wall-e/lib/usage.js +152 -0
- package/template/wall-e/lib/worker-thread-pool.js +389 -0
- package/template/wall-e/llm/client.js +81 -4
- package/template/wall-e/llm/default-fallback.js +54 -8
- package/template/wall-e/llm/mlx.js +536 -73
- package/template/wall-e/llm/mlx.plugin.json +1 -1
- package/template/wall-e/llm/ollama.js +342 -43
- package/template/wall-e/llm/provider-error.js +18 -1
- package/template/wall-e/llm/provider-health-state.js +176 -0
- package/template/wall-e/llm/routing-policy.js +796 -0
- package/template/wall-e/llm/supported-models.js +5 -0
- package/template/wall-e/loops/tasks.js +60 -14
- package/template/wall-e/loops/think.js +89 -24
- package/template/wall-e/mcp-server.js +192 -28
- package/template/wall-e/server.js +32 -7
- package/template/wall-e/skills/script-skill-runner.js +8 -1
- package/template/wall-e/skills/skill-planner.js +64 -1
- package/template/wall-e/tools/builtin-middleware.js +67 -2
- package/template/wall-e/tools/local-tools.js +116 -26
- package/template/wall-e/tools/permission-checker.js +52 -4
- package/template/wall-e/tools/permission-rules.js +36 -0
- package/template/wall-e/tools/shell-analyzer.js +46 -1
- package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
- package/template/wall-e/training/real-trajectory-miner.js +2617 -0
- package/template/wall-e/training/replay-eval-analysis.js +151 -0
- package/template/wall-e/training/run-shell-command-selector.js +277 -0
- package/template/wall-e/training/tool-sft-dataset.js +312 -0
- package/template/wall-e/training/tool-sft-renderers.js +144 -0
- package/template/wall-e/training/tool-trace-harvester.js +1440 -0
- package/template/wall-e/training/trajectory-action-selector.js +364 -0
- package/template/wall-e/weather-runtime.js +232 -0
- package/template/wall-e/workers/brain-owner-worker.js +162 -0
- package/template/wall-e/workers/brain-read-worker.js +148 -0
- package/template/wall-e/workers/runtime-worker.js +145 -0
|
@@ -7,6 +7,32 @@ const fs = require('fs');
|
|
|
7
7
|
|
|
8
8
|
const MLX_VENV = path.join(os.homedir(), '.mlx-env');
|
|
9
9
|
const MLX_PYTHON = path.join(MLX_VENV, 'bin', 'python3');
|
|
10
|
+
const WALLE_LOCAL_MODEL_ROOT = path.join(os.homedir(), '.walle', 'models');
|
|
11
|
+
const GEMMA_E4B_QAT_REPO = 'mlx-community/gemma-4-E4B-it-qat-4bit';
|
|
12
|
+
const GEMMA_E4B_TOOLUSE_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v1';
|
|
13
|
+
const GEMMA_E4B_TOOLUSE_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_MODEL_ID);
|
|
14
|
+
const GEMMA_E4B_TOOLUSE_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_MODEL_DIR, 'adapter');
|
|
15
|
+
const GEMMA_E4B_TOOLUSE_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_MODEL_DIR, 'candidate.json');
|
|
16
|
+
const GEMMA_E4B_TOOLUSE_V2_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v2';
|
|
17
|
+
const GEMMA_E4B_TOOLUSE_V2_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_V2_MODEL_ID);
|
|
18
|
+
const GEMMA_E4B_TOOLUSE_V2_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_V2_MODEL_DIR, 'adapter');
|
|
19
|
+
const GEMMA_E4B_TOOLUSE_V2_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_V2_MODEL_DIR, 'candidate.json');
|
|
20
|
+
const GEMMA_E4B_TOOLUSE_V3_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v3';
|
|
21
|
+
const GEMMA_E4B_TOOLUSE_V3_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_V3_MODEL_ID);
|
|
22
|
+
const GEMMA_E4B_TOOLUSE_V3_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_V3_MODEL_DIR, 'adapter');
|
|
23
|
+
const GEMMA_E4B_TOOLUSE_V3_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_V3_MODEL_DIR, 'candidate.json');
|
|
24
|
+
const GEMMA_E4B_TOOLUSE_V4_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v4';
|
|
25
|
+
const GEMMA_E4B_TOOLUSE_V4_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_V4_MODEL_ID);
|
|
26
|
+
const GEMMA_E4B_TOOLUSE_V4_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_V4_MODEL_DIR, 'adapter');
|
|
27
|
+
const GEMMA_E4B_TOOLUSE_V4_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_V4_MODEL_DIR, 'candidate.json');
|
|
28
|
+
const GEMMA_E4B_TOOLUSE_V5_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v5';
|
|
29
|
+
const GEMMA_E4B_TOOLUSE_V5_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_V5_MODEL_ID);
|
|
30
|
+
const GEMMA_E4B_TOOLUSE_V5_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_V5_MODEL_DIR, 'adapter');
|
|
31
|
+
const GEMMA_E4B_TOOLUSE_V5_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_V5_MODEL_DIR, 'candidate.json');
|
|
32
|
+
const GEMMA_E4B_TOOLUSE_V6_MODEL_ID = 'gemma4-e4b-it-qat-tooluse-v6';
|
|
33
|
+
const GEMMA_E4B_TOOLUSE_V6_MODEL_DIR = path.join(WALLE_LOCAL_MODEL_ROOT, GEMMA_E4B_TOOLUSE_V6_MODEL_ID);
|
|
34
|
+
const GEMMA_E4B_TOOLUSE_V6_ADAPTER_PATH = path.join(GEMMA_E4B_TOOLUSE_V6_MODEL_DIR, 'adapter');
|
|
35
|
+
const GEMMA_E4B_TOOLUSE_V6_METADATA_PATH = path.join(GEMMA_E4B_TOOLUSE_V6_MODEL_DIR, 'candidate.json');
|
|
10
36
|
|
|
11
37
|
// Map of known model aliases to HuggingFace MLX repos
|
|
12
38
|
const MLX_MODEL_REGISTRY = {
|
|
@@ -17,6 +43,38 @@ const MLX_MODEL_REGISTRY = {
|
|
|
17
43
|
'phi4': 'mlx-community/phi-4-4bit',
|
|
18
44
|
'phi4:latest': 'mlx-community/phi-4-4bit',
|
|
19
45
|
'gemma3:4b': 'mlx-community/gemma-3-4b-it-4bit',
|
|
46
|
+
'gemma4:e4b': GEMMA_E4B_QAT_REPO,
|
|
47
|
+
'gemma4:e4b-it-qat': GEMMA_E4B_QAT_REPO,
|
|
48
|
+
'gemma4:e4b-it-qat-tooluse-v1': {
|
|
49
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
50
|
+
adapterPath: GEMMA_E4B_TOOLUSE_ADAPTER_PATH,
|
|
51
|
+
metadataPath: GEMMA_E4B_TOOLUSE_METADATA_PATH,
|
|
52
|
+
},
|
|
53
|
+
'gemma4:e4b-it-qat-tooluse-v2': {
|
|
54
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
55
|
+
adapterPath: GEMMA_E4B_TOOLUSE_V2_ADAPTER_PATH,
|
|
56
|
+
metadataPath: GEMMA_E4B_TOOLUSE_V2_METADATA_PATH,
|
|
57
|
+
},
|
|
58
|
+
'gemma4:e4b-it-qat-tooluse-v3': {
|
|
59
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
60
|
+
adapterPath: GEMMA_E4B_TOOLUSE_V3_ADAPTER_PATH,
|
|
61
|
+
metadataPath: GEMMA_E4B_TOOLUSE_V3_METADATA_PATH,
|
|
62
|
+
},
|
|
63
|
+
'gemma4:e4b-it-qat-tooluse-v4': {
|
|
64
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
65
|
+
adapterPath: GEMMA_E4B_TOOLUSE_V4_ADAPTER_PATH,
|
|
66
|
+
metadataPath: GEMMA_E4B_TOOLUSE_V4_METADATA_PATH,
|
|
67
|
+
},
|
|
68
|
+
'gemma4:e4b-it-qat-tooluse-v5': {
|
|
69
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
70
|
+
adapterPath: GEMMA_E4B_TOOLUSE_V5_ADAPTER_PATH,
|
|
71
|
+
metadataPath: GEMMA_E4B_TOOLUSE_V5_METADATA_PATH,
|
|
72
|
+
},
|
|
73
|
+
'gemma4:e4b-it-qat-tooluse-v6': {
|
|
74
|
+
repo: GEMMA_E4B_QAT_REPO,
|
|
75
|
+
adapterPath: GEMMA_E4B_TOOLUSE_V6_ADAPTER_PATH,
|
|
76
|
+
metadataPath: GEMMA_E4B_TOOLUSE_V6_METADATA_PATH,
|
|
77
|
+
},
|
|
20
78
|
'llama3.2:3b': 'mlx-community/Llama-3.2-3B-Instruct-4bit',
|
|
21
79
|
'mistral:7b': 'mlx-community/Mistral-7B-Instruct-v0.3-4bit',
|
|
22
80
|
};
|
|
@@ -33,18 +91,392 @@ function execAsync(cmd, args, opts = {}) {
|
|
|
33
91
|
});
|
|
34
92
|
}
|
|
35
93
|
|
|
94
|
+
function resolveInferenceTimeoutMs(config = {}) {
|
|
95
|
+
const raw = config.inferenceTimeoutMs ?? process.env.MLX_INFERENCE_TIMEOUT_MS;
|
|
96
|
+
if (raw === undefined || raw === null || raw === '') return 300000;
|
|
97
|
+
const parsed = Number(raw);
|
|
98
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 300000;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildInferenceExecOptions(config = {}, signal) {
|
|
102
|
+
return {
|
|
103
|
+
timeout: resolveInferenceTimeoutMs(config),
|
|
104
|
+
killSignal: 'SIGTERM',
|
|
105
|
+
signal,
|
|
106
|
+
env: { ...process.env, TOKENIZERS_PARALLELISM: 'false' },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function normalizeRegistryEntry(entry) {
|
|
111
|
+
if (!entry) return null;
|
|
112
|
+
if (typeof entry === 'string') return { repo: entry, adapterPath: null, metadataPath: null };
|
|
113
|
+
if (typeof entry === 'object' && entry.repo) {
|
|
114
|
+
return {
|
|
115
|
+
repo: entry.repo,
|
|
116
|
+
adapterPath: entry.adapterPath || null,
|
|
117
|
+
metadataPath: entry.metadataPath || null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
|
|
36
123
|
/**
|
|
37
|
-
* Resolve a model name to an MLX HuggingFace repo.
|
|
124
|
+
* Resolve a model name to an MLX HuggingFace repo plus optional adapter.
|
|
38
125
|
* Accepts: registry aliases, full HF repo names (org/model), or mlx-community/ prefixed names.
|
|
39
126
|
*/
|
|
40
|
-
function
|
|
127
|
+
function resolveModelSpec(model, config = {}) {
|
|
41
128
|
if (!model) return null;
|
|
42
|
-
|
|
129
|
+
const requested = String(model).trim();
|
|
130
|
+
const registered = normalizeRegistryEntry(MLX_MODEL_REGISTRY[requested]);
|
|
131
|
+
if (registered) {
|
|
132
|
+
return {
|
|
133
|
+
alias: requested,
|
|
134
|
+
repo: registered.repo,
|
|
135
|
+
adapterPath: config.adapterPath || registered.adapterPath || null,
|
|
136
|
+
metadataPath: registered.metadataPath || null,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
43
139
|
// Validate model name to prevent arbitrary HF repo downloads
|
|
44
|
-
if (!/^[\w\-.:\/]+$/.test(
|
|
45
|
-
if (
|
|
140
|
+
if (!/^[\w\-.:\/]+$/.test(requested)) return null;
|
|
141
|
+
if (requested.includes('/')) {
|
|
142
|
+
return {
|
|
143
|
+
alias: null,
|
|
144
|
+
repo: requested,
|
|
145
|
+
adapterPath: config.adapterPath || null,
|
|
146
|
+
metadataPath: null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
46
149
|
// Try with mlx-community prefix
|
|
47
|
-
return
|
|
150
|
+
return {
|
|
151
|
+
alias: requested,
|
|
152
|
+
repo: `mlx-community/${requested}`,
|
|
153
|
+
adapterPath: config.adapterPath || null,
|
|
154
|
+
metadataPath: null,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function resolveModel(model) {
|
|
159
|
+
return resolveModelSpec(model)?.repo || null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveMlxPython(config = {}) {
|
|
163
|
+
return config.python || process.env.MLX_PYTHON || MLX_PYTHON;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizeToolSpec(tool = {}) {
|
|
167
|
+
const fn = tool.function || {};
|
|
168
|
+
const schema = tool.input_schema || tool.inputSchema || tool.parameters || fn.parameters || {};
|
|
169
|
+
return {
|
|
170
|
+
name: tool.name || fn.name || '',
|
|
171
|
+
description: tool.description || fn.description || '',
|
|
172
|
+
schema: schema && typeof schema === 'object' ? schema : {},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeToolCallFormat(format) {
|
|
177
|
+
return format === 'bare-json' ? 'bare-json' : 'fenced';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildToolContext(tools = [], opts = {}) {
|
|
181
|
+
const normalized = tools.map(normalizeToolSpec).filter((tool) => tool.name);
|
|
182
|
+
if (normalized.length === 0) return '';
|
|
183
|
+
const toolCallFormat = normalizeToolCallFormat(opts.toolCallFormat || opts.format);
|
|
184
|
+
const instruction = toolCallFormat === 'bare-json'
|
|
185
|
+
? '\n\nYou have access to these tools. To use a tool, respond with exactly one JSON object and nothing else. The first non-whitespace character must be "{". Do not use markdown fences. Do not output <|channel>thought.\nSchema: {"tool":"tool_name","input":{"arg":"value"}}\n\nAvailable tools:\n'
|
|
186
|
+
: '\n\nYou have access to these tools. To use a tool, respond with a JSON block:\n' +
|
|
187
|
+
'```json\n{"tool": "tool_name", "input": {"arg": "value"}}\n```\n\nAvailable tools:\n';
|
|
188
|
+
return instruction +
|
|
189
|
+
normalized.map((tool) => {
|
|
190
|
+
const schema = Object.keys(tool.schema || {}).length ? tool.schema : { type: 'object', properties: {} };
|
|
191
|
+
return `- **${tool.name}**: ${tool.description || 'No description'}\n Parameters: ${JSON.stringify(schema)}`;
|
|
192
|
+
}).join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function toolCallInput(call = {}) {
|
|
196
|
+
const fn = call.function || {};
|
|
197
|
+
return normalizeToolInput(fn.arguments ?? call.input ?? call.arguments ?? {});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renderToolCallJsonBlock(call = {}, opts = {}) {
|
|
201
|
+
const fn = call.function || {};
|
|
202
|
+
const name = fn.name || call.name || '';
|
|
203
|
+
const payload = JSON.stringify({ tool: name, input: toolCallInput(call) });
|
|
204
|
+
return normalizeToolCallFormat(opts.toolCallFormat || opts.format) === 'bare-json'
|
|
205
|
+
? payload
|
|
206
|
+
: '```json\n' + payload + '\n```';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function messageTextContent(message = {}) {
|
|
210
|
+
if (typeof message.content === 'string') return message.content;
|
|
211
|
+
if (!Array.isArray(message.content)) return '';
|
|
212
|
+
return message.content
|
|
213
|
+
.filter((block) => block?.type === 'text')
|
|
214
|
+
.map((block) => block.text || '')
|
|
215
|
+
.filter(Boolean)
|
|
216
|
+
.join('\n');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function renderToolResultMessage(message = {}) {
|
|
220
|
+
const id = message.tool_call_id || message.toolCallId || message.tool_use_id || 'tool';
|
|
221
|
+
const content = typeof message.content === 'string'
|
|
222
|
+
? message.content
|
|
223
|
+
: Array.isArray(message.content)
|
|
224
|
+
? message.content.map((block) => block?.text || block?.content || '').filter(Boolean).join('\n')
|
|
225
|
+
: JSON.stringify(message.content ?? '');
|
|
226
|
+
return {
|
|
227
|
+
role: 'user',
|
|
228
|
+
content: `[Tool result for ${id}]: ${content}`,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function renderMlxProviderMessages(message = {}, opts = {}) {
|
|
233
|
+
if (!message || typeof message !== 'object') return [];
|
|
234
|
+
|
|
235
|
+
if (message.role === 'tool') {
|
|
236
|
+
return [renderToolResultMessage(message)];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (message.role === 'assistant' && Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
|
240
|
+
const parts = [];
|
|
241
|
+
const text = messageTextContent(message).trim();
|
|
242
|
+
if (text) parts.push(text);
|
|
243
|
+
for (const call of message.tool_calls) {
|
|
244
|
+
parts.push(renderToolCallJsonBlock(call, opts));
|
|
245
|
+
}
|
|
246
|
+
return [{ role: 'assistant', content: parts.join('\n\n') }];
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (typeof message.content === 'string') {
|
|
250
|
+
return [{ role: message.role, content: message.content }];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (Array.isArray(message.content)) {
|
|
254
|
+
const out = [];
|
|
255
|
+
const text = messageTextContent(message);
|
|
256
|
+
if (text) out.push({ role: message.role, content: text });
|
|
257
|
+
const toolResults = message.content.filter((block) => block?.type === 'tool_result');
|
|
258
|
+
for (const tr of toolResults) {
|
|
259
|
+
const resultText = typeof tr.content === 'string' ? tr.content
|
|
260
|
+
: Array.isArray(tr.content) ? tr.content.map((c) => c.text || '').join('\n')
|
|
261
|
+
: JSON.stringify(tr.content);
|
|
262
|
+
out.push({ role: 'user', content: `[Tool result for ${tr.tool_use_id}]: ${resultText}` });
|
|
263
|
+
}
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return [];
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function prepareMessagesForMlxProvider({
|
|
271
|
+
messages = [],
|
|
272
|
+
tools = [],
|
|
273
|
+
system = null,
|
|
274
|
+
compactDuplicates = true,
|
|
275
|
+
toolCallFormat = 'fenced',
|
|
276
|
+
} = {}) {
|
|
277
|
+
const chatMessages = [];
|
|
278
|
+
if (system) chatMessages.push({ role: 'system', content: system });
|
|
279
|
+
for (const message of messages || []) {
|
|
280
|
+
chatMessages.push(...renderMlxProviderMessages(message, { toolCallFormat }));
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const toolContext = buildToolContext(tools || [], { toolCallFormat });
|
|
284
|
+
if (toolContext) {
|
|
285
|
+
if (chatMessages.length > 0 && chatMessages[0].role === 'system') {
|
|
286
|
+
chatMessages[0] = {
|
|
287
|
+
...chatMessages[0],
|
|
288
|
+
content: `${chatMessages[0].content || ''}${toolContext}`,
|
|
289
|
+
};
|
|
290
|
+
} else {
|
|
291
|
+
chatMessages.unshift({ role: 'system', content: toolContext.trim() });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return compactDuplicates ? compactDuplicateToolResults(chatMessages) : chatMessages;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function duplicateToolResultSignature(content, { minLength = 1200 } = {}) {
|
|
299
|
+
const text = String(content || '');
|
|
300
|
+
const match = text.match(/^\[Tool result for [^\]]+\]:\s*([\s\S]*)$/);
|
|
301
|
+
if (!match) return null;
|
|
302
|
+
const body = match[1].trim();
|
|
303
|
+
if (body.length < minLength) return null;
|
|
304
|
+
return body;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function compactDuplicateToolResults(messages = [], opts = {}) {
|
|
308
|
+
const seen = new Set();
|
|
309
|
+
const duplicateIndexes = new Set();
|
|
310
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
311
|
+
const message = messages[i] || {};
|
|
312
|
+
if (message.role !== 'user') continue;
|
|
313
|
+
const signature = duplicateToolResultSignature(message.content, opts);
|
|
314
|
+
if (!signature) continue;
|
|
315
|
+
if (seen.has(signature)) duplicateIndexes.add(i);
|
|
316
|
+
else seen.add(signature);
|
|
317
|
+
}
|
|
318
|
+
if (duplicateIndexes.size === 0) return messages;
|
|
319
|
+
return messages.map((message, idx) => {
|
|
320
|
+
if (!duplicateIndexes.has(idx)) return message;
|
|
321
|
+
const id = String(message.content || '').match(/^\[Tool result for ([^\]]+)\]/)?.[1] || 'tool';
|
|
322
|
+
return {
|
|
323
|
+
...message,
|
|
324
|
+
content: `[Tool result for ${id}]: Duplicate large tool result omitted; the latest identical result remains later in this prompt. Use that latest visible result instead of repeating the same read.`,
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function extractJsonObject(text) {
|
|
330
|
+
const value = String(text || '');
|
|
331
|
+
const fenced = value.match(/```(?:json)?\s*\n?(\{[\s\S]*?\})\s*\n?```/i);
|
|
332
|
+
if (fenced) return fenced[1];
|
|
333
|
+
|
|
334
|
+
const start = value.indexOf('{');
|
|
335
|
+
if (start < 0) return null;
|
|
336
|
+
|
|
337
|
+
let depth = 0;
|
|
338
|
+
let inString = false;
|
|
339
|
+
let escaped = false;
|
|
340
|
+
for (let i = start; i < value.length; i += 1) {
|
|
341
|
+
const ch = value[i];
|
|
342
|
+
if (inString) {
|
|
343
|
+
if (escaped) escaped = false;
|
|
344
|
+
else if (ch === '\\') escaped = true;
|
|
345
|
+
else if (ch === '"') inString = false;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (ch === '"') {
|
|
349
|
+
inString = true;
|
|
350
|
+
} else if (ch === '{') {
|
|
351
|
+
depth += 1;
|
|
352
|
+
} else if (ch === '}') {
|
|
353
|
+
depth -= 1;
|
|
354
|
+
if (depth === 0) return value.slice(start, i + 1);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeToolInput(input) {
|
|
361
|
+
if (!input) return {};
|
|
362
|
+
if (typeof input === 'string') {
|
|
363
|
+
try {
|
|
364
|
+
const parsed = JSON.parse(input);
|
|
365
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
366
|
+
} catch {
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return typeof input === 'object' ? input : {};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function relativizeBenchPath(value) {
|
|
374
|
+
if (typeof value !== 'string') return value;
|
|
375
|
+
const match = value.match(/(?:^|\/)bench-(?!sandbox(?:\/|$))[^/]+\/(.+)$/);
|
|
376
|
+
return match ? match[1] : value;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function normalizeGeneratedToolCall(name, input = {}) {
|
|
380
|
+
let normalizedName = String(name || '');
|
|
381
|
+
if (/^(ls|ls_directory)$/i.test(normalizedName)) normalizedName = 'list_directory';
|
|
382
|
+
if (/^glob_files$/i.test(normalizedName)) normalizedName = 'glob';
|
|
383
|
+
const normalized = { ...normalizeToolInput(input) };
|
|
384
|
+
for (const key of ['file_path', 'path', 'directory', 'cwd']) {
|
|
385
|
+
if (normalized[key]) normalized[key] = relativizeBenchPath(normalized[key]);
|
|
386
|
+
}
|
|
387
|
+
if ((normalizedName === 'read_file' || normalizedName === 'write_file' || normalizedName === 'edit_file') && normalized.path && !normalized.file_path) {
|
|
388
|
+
normalized.file_path = normalized.path;
|
|
389
|
+
delete normalized.path;
|
|
390
|
+
}
|
|
391
|
+
if (normalizedName === 'list_directory' && normalized.path && !normalized.directory) {
|
|
392
|
+
normalized.directory = normalized.path;
|
|
393
|
+
delete normalized.path;
|
|
394
|
+
}
|
|
395
|
+
if (normalizedName === 'run_shell') delete normalized.cwd;
|
|
396
|
+
return { name: normalizedName, input: normalized };
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function parseLooseKeyValues(text) {
|
|
400
|
+
const out = {};
|
|
401
|
+
const pairRe = /"([A-Za-z_][A-Za-z0-9_]*)"\s*:\s*("(?:\\.|[^"\\])*"|-?\d+(?:\.\d+)?|true|false|null)/g;
|
|
402
|
+
let match;
|
|
403
|
+
while ((match = pairRe.exec(String(text || '')))) {
|
|
404
|
+
try {
|
|
405
|
+
out[match[1]] = JSON.parse(match[2]);
|
|
406
|
+
} catch {}
|
|
407
|
+
}
|
|
408
|
+
return out;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function recoverGemmaToolCallSegment(segment) {
|
|
412
|
+
const text = String(segment || '');
|
|
413
|
+
const named = text.match(/^\s*call:([^\s{<]+)/i);
|
|
414
|
+
const toolObjectName = text.match(/"tool"\s*:\s*"([^"]+)"/i) || text.match(/"name"\s*:\s*"([^"]+)"/i);
|
|
415
|
+
const name = named?.[1] || toolObjectName?.[1] || null;
|
|
416
|
+
if (!name) return null;
|
|
417
|
+
|
|
418
|
+
const argsStart = text.search(/\{+(?="?(?:command|file_path|path|directory|pattern|file_pattern|patch_text|old_string|new_string|content|limit|offset)"?\s*:)/);
|
|
419
|
+
if (argsStart >= 0) {
|
|
420
|
+
const argsCandidate = text.slice(argsStart).replace(/^\{+/, '{');
|
|
421
|
+
const argsText = extractJsonObject(argsCandidate);
|
|
422
|
+
if (argsText) {
|
|
423
|
+
try {
|
|
424
|
+
return normalizeGeneratedToolCall(name, JSON.parse(argsText));
|
|
425
|
+
} catch {}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const directArgs = named ? extractJsonObject(text.slice(named.index + named[0].length)) : null;
|
|
430
|
+
if (directArgs) {
|
|
431
|
+
try {
|
|
432
|
+
return normalizeGeneratedToolCall(name, JSON.parse(directArgs));
|
|
433
|
+
} catch {}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const loose = parseLooseKeyValues(text);
|
|
437
|
+
delete loose.tool;
|
|
438
|
+
delete loose.name;
|
|
439
|
+
return Object.keys(loose).length ? normalizeGeneratedToolCall(name, loose) : null;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function recoverMalformedBareJsonToolCall(text) {
|
|
443
|
+
const value = String(text || '');
|
|
444
|
+
const toolObjectName = value.match(/"tool"\s*:\s*"([^"]+)"/i) || value.match(/"name"\s*:\s*"([^"]+)"/i);
|
|
445
|
+
const name = toolObjectName?.[1] || null;
|
|
446
|
+
if (!name) return null;
|
|
447
|
+
|
|
448
|
+
const inputStart = value.search(/"input"\s*:\s*\{/i);
|
|
449
|
+
const inputText = inputStart >= 0 ? value.slice(inputStart) : value;
|
|
450
|
+
const loose = parseLooseKeyValues(inputText);
|
|
451
|
+
delete loose.tool;
|
|
452
|
+
delete loose.name;
|
|
453
|
+
return normalizeGeneratedToolCall(name, loose);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function parseToolCallText(text) {
|
|
457
|
+
const value = String(text || '');
|
|
458
|
+
const gemmaSegments = value.split(/<\|tool_call\>/i).slice(1);
|
|
459
|
+
for (const segment of gemmaSegments) {
|
|
460
|
+
const toolSegment = segment.split(/<tool_call\|>/i)[0];
|
|
461
|
+
const recovered = recoverGemmaToolCallSegment(toolSegment);
|
|
462
|
+
if (recovered?.name) return recovered;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const jsonText = extractJsonObject(text);
|
|
466
|
+
if (!jsonText) return recoverMalformedBareJsonToolCall(text);
|
|
467
|
+
try {
|
|
468
|
+
const parsed = JSON.parse(jsonText);
|
|
469
|
+
if (parsed.tool || parsed.name) {
|
|
470
|
+
const name = parsed.tool || parsed.name;
|
|
471
|
+
return normalizeGeneratedToolCall(name, parsed.input || parsed.arguments || parsed.parameters);
|
|
472
|
+
}
|
|
473
|
+
if (parsed.function?.name) {
|
|
474
|
+
return normalizeGeneratedToolCall(parsed.function.name, parsed.function.arguments || parsed.function.input || parsed.arguments);
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
return recoverMalformedBareJsonToolCall(text);
|
|
478
|
+
}
|
|
479
|
+
return recoverMalformedBareJsonToolCall(text);
|
|
48
480
|
}
|
|
49
481
|
|
|
50
482
|
/**
|
|
@@ -52,50 +484,30 @@ function resolveModel(model) {
|
|
|
52
484
|
* Uses Apple's MLX framework via Python subprocess for GPU-accelerated inference.
|
|
53
485
|
*/
|
|
54
486
|
function createMLXProvider(config = {}) {
|
|
487
|
+
const configuredToolCallFormat = normalizeToolCallFormat(config.toolCallFormat);
|
|
55
488
|
return {
|
|
56
489
|
type: 'mlx',
|
|
57
490
|
|
|
58
491
|
async chat({ model, messages, tools, system, maxTokens, temperature, signal } = {}) {
|
|
59
|
-
const
|
|
492
|
+
const modelSpec = resolveModelSpec(model || config.model, config);
|
|
493
|
+
const mlxModel = modelSpec?.repo;
|
|
60
494
|
if (!mlxModel) throw new Error('MLX: no model specified');
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (system) chatMessages.push({ role: 'system', content: system });
|
|
65
|
-
if (messages) {
|
|
66
|
-
for (const m of messages) {
|
|
67
|
-
if (typeof m.content === 'string') {
|
|
68
|
-
chatMessages.push({ role: m.role, content: m.content });
|
|
69
|
-
} else if (Array.isArray(m.content)) {
|
|
70
|
-
// Extract text from content blocks
|
|
71
|
-
const text = m.content
|
|
72
|
-
.filter(b => b.type === 'text')
|
|
73
|
-
.map(b => b.text)
|
|
74
|
-
.join('\n');
|
|
75
|
-
if (text) chatMessages.push({ role: m.role, content: text });
|
|
76
|
-
// Handle tool results
|
|
77
|
-
const toolResults = m.content.filter(b => b.type === 'tool_result');
|
|
78
|
-
for (const tr of toolResults) {
|
|
79
|
-
const resultText = typeof tr.content === 'string' ? tr.content
|
|
80
|
-
: Array.isArray(tr.content) ? tr.content.map(c => c.text || '').join('\n')
|
|
81
|
-
: JSON.stringify(tr.content);
|
|
82
|
-
chatMessages.push({ role: 'user', content: `[Tool result for ${tr.tool_use_id}]: ${resultText}` });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
495
|
+
const adapterPath = modelSpec.adapterPath;
|
|
496
|
+
if (adapterPath && !fs.existsSync(adapterPath)) {
|
|
497
|
+
throw new Error(`MLX adapter path does not exist: ${adapterPath}`);
|
|
86
498
|
}
|
|
87
499
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
500
|
+
const compactedChatMessages = prepareMessagesForMlxProvider({
|
|
501
|
+
messages,
|
|
502
|
+
tools: tools || [],
|
|
503
|
+
system,
|
|
504
|
+
compactDuplicates: true,
|
|
505
|
+
toolCallFormat: configuredToolCallFormat,
|
|
506
|
+
});
|
|
507
|
+
const toolContext = buildToolContext(tools || [], { toolCallFormat: configuredToolCallFormat });
|
|
508
|
+
const forcedGenerationPrefix = toolContext && configuredToolCallFormat === 'bare-json' && config.forceToolJsonPrefix
|
|
509
|
+
? '{'
|
|
510
|
+
: '';
|
|
99
511
|
|
|
100
512
|
// Python script for MLX inference
|
|
101
513
|
const pyScript = `
|
|
@@ -105,12 +517,22 @@ from mlx_lm import load, generate
|
|
|
105
517
|
messages = json.loads(sys.argv[1])
|
|
106
518
|
max_tokens = int(sys.argv[2])
|
|
107
519
|
model_name = sys.argv[3]
|
|
520
|
+
adapter_path = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else None
|
|
521
|
+
forced_generation_prefix = sys.argv[5] if len(sys.argv) > 5 else ""
|
|
522
|
+
|
|
523
|
+
load_kwargs = {}
|
|
524
|
+
if adapter_path:
|
|
525
|
+
load_kwargs["adapter_path"] = adapter_path
|
|
108
526
|
|
|
109
|
-
model, tokenizer = load(model_name)
|
|
527
|
+
model, tokenizer = load(model_name, **load_kwargs)
|
|
110
528
|
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
529
|
+
if forced_generation_prefix:
|
|
530
|
+
prompt = prompt + forced_generation_prefix
|
|
111
531
|
|
|
112
532
|
t0 = time.time()
|
|
113
533
|
result = generate(model, tokenizer, prompt=prompt, max_tokens=max_tokens, verbose=False)
|
|
534
|
+
if forced_generation_prefix:
|
|
535
|
+
result = forced_generation_prefix + result
|
|
114
536
|
t1 = time.time()
|
|
115
537
|
|
|
116
538
|
output_tokens = len(tokenizer.encode(result))
|
|
@@ -129,9 +551,9 @@ print(json.dumps({
|
|
|
129
551
|
|
|
130
552
|
try {
|
|
131
553
|
const { stdout, stderr } = await execAsync(
|
|
132
|
-
|
|
133
|
-
['-c', pyScript, JSON.stringify(
|
|
134
|
-
|
|
554
|
+
resolveMlxPython(config),
|
|
555
|
+
['-c', pyScript, JSON.stringify(compactedChatMessages), String(maxTok), mlxModel, adapterPath || '', forcedGenerationPrefix],
|
|
556
|
+
buildInferenceExecOptions(config, signal)
|
|
135
557
|
);
|
|
136
558
|
|
|
137
559
|
// Find JSON line in output
|
|
@@ -143,38 +565,39 @@ print(json.dumps({
|
|
|
143
565
|
const latencyMs = Date.now() - start;
|
|
144
566
|
|
|
145
567
|
// Parse tool calls from output if tools were provided
|
|
146
|
-
|
|
568
|
+
let content = data.text;
|
|
569
|
+
const toolCalls = [];
|
|
147
570
|
let stopReason = 'end_turn';
|
|
148
571
|
|
|
149
|
-
if (
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
input: toolCall.input,
|
|
160
|
-
});
|
|
161
|
-
stopReason = 'tool_use';
|
|
162
|
-
}
|
|
163
|
-
} catch { /* not a valid tool call */ }
|
|
572
|
+
if (toolContext) {
|
|
573
|
+
const toolCall = parseToolCallText(data.text);
|
|
574
|
+
if (toolCall?.name) {
|
|
575
|
+
toolCalls.push({
|
|
576
|
+
id: `mlx_${Date.now()}`,
|
|
577
|
+
name: toolCall.name,
|
|
578
|
+
input: toolCall.input,
|
|
579
|
+
});
|
|
580
|
+
content = '';
|
|
581
|
+
stopReason = 'tool_use';
|
|
164
582
|
}
|
|
165
583
|
}
|
|
166
584
|
|
|
167
|
-
if (content.length === 0) {
|
|
168
|
-
content.push({ type: 'text', text: data.text });
|
|
169
|
-
}
|
|
170
|
-
|
|
171
585
|
return {
|
|
172
586
|
content,
|
|
587
|
+
rawText: data.text,
|
|
588
|
+
toolCalls,
|
|
173
589
|
stopReason,
|
|
174
590
|
latencyMs,
|
|
591
|
+
usage: {
|
|
592
|
+
input: data.prompt_tokens,
|
|
593
|
+
output: data.output_tokens,
|
|
594
|
+
requestLatencyMs: latencyMs,
|
|
595
|
+
},
|
|
175
596
|
inputTokens: data.prompt_tokens,
|
|
176
597
|
outputTokens: data.output_tokens,
|
|
177
598
|
model: mlxModel,
|
|
599
|
+
modelAlias: modelSpec.alias,
|
|
600
|
+
adapterPath,
|
|
178
601
|
provider: 'mlx',
|
|
179
602
|
};
|
|
180
603
|
} catch (err) {
|
|
@@ -187,17 +610,23 @@ print(json.dumps({
|
|
|
187
610
|
const models = [];
|
|
188
611
|
const cacheDir = path.join(os.homedir(), '.cache', 'huggingface', 'hub');
|
|
189
612
|
|
|
190
|
-
for (const [alias,
|
|
613
|
+
for (const [alias, entry] of Object.entries(MLX_MODEL_REGISTRY)) {
|
|
614
|
+
const spec = normalizeRegistryEntry(entry);
|
|
615
|
+
if (!spec) continue;
|
|
616
|
+
const repo = spec.repo;
|
|
191
617
|
// Check if model is cached locally
|
|
192
618
|
const repoDir = `models--${repo.replace('/', '--')}`;
|
|
193
619
|
const cached = fs.existsSync(path.join(cacheDir, repoDir));
|
|
194
620
|
if (cached) {
|
|
621
|
+
const adapterInstalled = !spec.adapterPath || fs.existsSync(spec.adapterPath);
|
|
195
622
|
models.push({
|
|
196
623
|
id: alias,
|
|
197
624
|
name: `${alias} (MLX)`,
|
|
198
625
|
repo,
|
|
199
|
-
|
|
200
|
-
|
|
626
|
+
adapterPath: spec.adapterPath || null,
|
|
627
|
+
adapterInstalled,
|
|
628
|
+
capabilities: spec.adapterPath ? ['code', 'tools'] : ['code'],
|
|
629
|
+
cached: adapterInstalled,
|
|
201
630
|
});
|
|
202
631
|
}
|
|
203
632
|
}
|
|
@@ -206,9 +635,10 @@ print(json.dumps({
|
|
|
206
635
|
|
|
207
636
|
async isAvailable() {
|
|
208
637
|
// Check if MLX Python env exists
|
|
209
|
-
|
|
638
|
+
const mlxPython = resolveMlxPython(config);
|
|
639
|
+
if (!fs.existsSync(mlxPython)) return false;
|
|
210
640
|
try {
|
|
211
|
-
const { stdout } = await execAsync(
|
|
641
|
+
const { stdout } = await execAsync(mlxPython, ['-c', 'import mlx_lm; print("ok")'], { timeout: 10000 });
|
|
212
642
|
return stdout.trim() === 'ok';
|
|
213
643
|
} catch {
|
|
214
644
|
return false;
|
|
@@ -217,4 +647,37 @@ print(json.dumps({
|
|
|
217
647
|
};
|
|
218
648
|
}
|
|
219
649
|
|
|
220
|
-
module.exports = {
|
|
650
|
+
module.exports = {
|
|
651
|
+
createMLXProvider,
|
|
652
|
+
resolveModel,
|
|
653
|
+
resolveModelSpec,
|
|
654
|
+
resolveMlxPython,
|
|
655
|
+
resolveInferenceTimeoutMs,
|
|
656
|
+
buildInferenceExecOptions,
|
|
657
|
+
normalizeToolCallFormat,
|
|
658
|
+
normalizeToolSpec,
|
|
659
|
+
buildToolContext,
|
|
660
|
+
toolCallInput,
|
|
661
|
+
renderToolCallJsonBlock,
|
|
662
|
+
renderMlxProviderMessages,
|
|
663
|
+
prepareMessagesForMlxProvider,
|
|
664
|
+
compactDuplicateToolResults,
|
|
665
|
+
duplicateToolResultSignature,
|
|
666
|
+
extractJsonObject,
|
|
667
|
+
parseToolCallText,
|
|
668
|
+
MLX_MODEL_REGISTRY,
|
|
669
|
+
MLX_PYTHON,
|
|
670
|
+
MLX_VENV,
|
|
671
|
+
GEMMA_E4B_TOOLUSE_ADAPTER_PATH,
|
|
672
|
+
GEMMA_E4B_TOOLUSE_METADATA_PATH,
|
|
673
|
+
GEMMA_E4B_TOOLUSE_V2_ADAPTER_PATH,
|
|
674
|
+
GEMMA_E4B_TOOLUSE_V2_METADATA_PATH,
|
|
675
|
+
GEMMA_E4B_TOOLUSE_V3_ADAPTER_PATH,
|
|
676
|
+
GEMMA_E4B_TOOLUSE_V3_METADATA_PATH,
|
|
677
|
+
GEMMA_E4B_TOOLUSE_V4_ADAPTER_PATH,
|
|
678
|
+
GEMMA_E4B_TOOLUSE_V4_METADATA_PATH,
|
|
679
|
+
GEMMA_E4B_TOOLUSE_V5_ADAPTER_PATH,
|
|
680
|
+
GEMMA_E4B_TOOLUSE_V5_METADATA_PATH,
|
|
681
|
+
GEMMA_E4B_TOOLUSE_V6_ADAPTER_PATH,
|
|
682
|
+
GEMMA_E4B_TOOLUSE_V6_METADATA_PATH,
|
|
683
|
+
};
|