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.
Files changed (179) hide show
  1. package/README.md +8 -0
  2. package/bin/create-walle.js +815 -45
  3. package/package.json +2 -2
  4. package/template/bin/ctm-dev-cleanup.js +90 -4
  5. package/template/bin/ctm-launch.sh +49 -1
  6. package/template/bin/dev.sh +45 -1
  7. package/template/bin/ensure-stable-node.js +132 -0
  8. package/template/bin/install-service.sh +9 -0
  9. package/template/claude-task-manager/api-prompts.js +899 -119
  10. package/template/claude-task-manager/approval-agent.js +360 -40
  11. package/template/claude-task-manager/bin/ctm-disclaim.c +42 -0
  12. package/template/claude-task-manager/bin/ctm-hotkey.swift +67 -81
  13. package/template/claude-task-manager/bin/ctm-screen-auth.swift +37 -0
  14. package/template/claude-task-manager/bin/install-hotkey.sh +97 -49
  15. package/template/claude-task-manager/bin/restart-ctm.sh +14 -0
  16. package/template/claude-task-manager/db.js +399 -48
  17. package/template/claude-task-manager/docs/approval-hook-sandbox.md +84 -0
  18. package/template/claude-task-manager/docs/codex-app-server-approvals.md +72 -0
  19. package/template/claude-task-manager/docs/codex-native-sandbox.md +47 -0
  20. package/template/claude-task-manager/docs/prompt-editing-tree-design.md +18 -1
  21. package/template/claude-task-manager/lib/approval-hook.js +200 -0
  22. package/template/claude-task-manager/lib/approval-self-adapt.js +1 -0
  23. package/template/claude-task-manager/lib/auth-rules.js +11 -0
  24. package/template/claude-task-manager/lib/background-llm.js +32 -4
  25. package/template/claude-task-manager/lib/codesign-identity.js +140 -0
  26. package/template/claude-task-manager/lib/codex-app-server-client.js +119 -0
  27. package/template/claude-task-manager/lib/codex-approval-bridge.js +118 -0
  28. package/template/claude-task-manager/lib/codex-history-terminal-renderer.js +571 -0
  29. package/template/claude-task-manager/lib/codex-paths.js +73 -0
  30. package/template/claude-task-manager/lib/codex-rollout-snapshot.js +164 -0
  31. package/template/claude-task-manager/lib/codex-rollout-tail.js +72 -0
  32. package/template/claude-task-manager/lib/codex-sandbox-args.js +47 -0
  33. package/template/claude-task-manager/lib/coding-agent-models.js +118 -71
  34. package/template/claude-task-manager/lib/command-targets.js +163 -0
  35. package/template/claude-task-manager/lib/conversation-tail-merge.js +61 -19
  36. package/template/claude-task-manager/lib/db-owner-worker-client.js +29 -1
  37. package/template/claude-task-manager/lib/escalation-review.js +80 -3
  38. package/template/claude-task-manager/lib/flow-control.js +52 -0
  39. package/template/claude-task-manager/lib/fs-watcher.js +24 -15
  40. package/template/claude-task-manager/lib/ingest-cooldown.js +68 -0
  41. package/template/claude-task-manager/lib/jsonl-conversation-parser.js +8 -4
  42. package/template/claude-task-manager/lib/launchd-recovery.js +92 -0
  43. package/template/claude-task-manager/lib/microsoft-dev-tunnel-setup.js +207 -52
  44. package/template/claude-task-manager/lib/mobile-push-store.js +7 -0
  45. package/template/claude-task-manager/lib/model-overview-brain-fallback.js +102 -1
  46. package/template/claude-task-manager/lib/model-overview-cache.js +1 -0
  47. package/template/claude-task-manager/lib/oauth-proxy-supervisor.js +2 -1
  48. package/template/claude-task-manager/lib/perf-tracker.js +29 -2
  49. package/template/claude-task-manager/lib/permission-match.js +146 -16
  50. package/template/claude-task-manager/lib/project-slug.js +33 -0
  51. package/template/claude-task-manager/lib/prompt-intent.js +51 -4
  52. package/template/claude-task-manager/lib/read-pool-client.js +48 -3
  53. package/template/claude-task-manager/lib/real-node.js +73 -0
  54. package/template/claude-task-manager/lib/runtime-work-registry.js +131 -14
  55. package/template/claude-task-manager/lib/session-content-backfill.js +24 -5
  56. package/template/claude-task-manager/lib/session-diagnostics-batch.js +87 -0
  57. package/template/claude-task-manager/lib/session-history.js +5 -7
  58. package/template/claude-task-manager/lib/session-host-manager.js +19 -0
  59. package/template/claude-task-manager/lib/session-jobs.js +6 -0
  60. package/template/claude-task-manager/lib/session-message-response-cache.js +89 -0
  61. package/template/claude-task-manager/lib/session-messages-page.js +211 -0
  62. package/template/claude-task-manager/lib/session-messages-projection.js +170 -0
  63. package/template/claude-task-manager/lib/session-standup.js +8 -0
  64. package/template/claude-task-manager/lib/session-timeline-summary.js +16 -2
  65. package/template/claude-task-manager/lib/session-token-usage.js +30 -8
  66. package/template/claude-task-manager/lib/session-workspace-binding.js +29 -15
  67. package/template/claude-task-manager/lib/storage-migration.js +2 -1
  68. package/template/claude-task-manager/lib/transcript-store.js +179 -12
  69. package/template/claude-task-manager/lib/walle-ctm-history.js +298 -11
  70. package/template/claude-task-manager/lib/walle-permission-reply.js +49 -0
  71. package/template/claude-task-manager/lib/walle-session-cache.js +22 -1
  72. package/template/claude-task-manager/lib/walle-supervisor.js +42 -3
  73. package/template/claude-task-manager/package.json +5 -2
  74. package/template/claude-task-manager/prompt-harvest.js +31 -11
  75. package/template/claude-task-manager/providers/claude-code.js +29 -1
  76. package/template/claude-task-manager/providers/codex.js +13 -1
  77. package/template/claude-task-manager/public/css/setup.css +11 -0
  78. package/template/claude-task-manager/public/css/walle-session.css +132 -4
  79. package/template/claude-task-manager/public/css/walle.css +89 -0
  80. package/template/claude-task-manager/public/icon-16.png +0 -0
  81. package/template/claude-task-manager/public/icon-32.png +0 -0
  82. package/template/claude-task-manager/public/icon-512.png +0 -0
  83. package/template/claude-task-manager/public/index.html +2483 -165
  84. package/template/claude-task-manager/public/js/activation-render-check.js +55 -0
  85. package/template/claude-task-manager/public/js/flow-control-policy.js +52 -0
  86. package/template/claude-task-manager/public/js/message-renderer.js +60 -1
  87. package/template/claude-task-manager/public/js/prompts.js +13 -1
  88. package/template/claude-task-manager/public/js/session-status-precedence.js +9 -3
  89. package/template/claude-task-manager/public/js/setup.js +54 -10
  90. package/template/claude-task-manager/public/js/stream-resize-policy.js +80 -0
  91. package/template/claude-task-manager/public/js/stream-view.js +78 -0
  92. package/template/claude-task-manager/public/js/terminal-reconciler.js +52 -2
  93. package/template/claude-task-manager/public/js/tool-state.js +155 -0
  94. package/template/claude-task-manager/public/js/walle-session.js +887 -326
  95. package/template/claude-task-manager/public/js/walle.js +306 -195
  96. package/template/claude-task-manager/public/m/app.css +1 -0
  97. package/template/claude-task-manager/public/m/app.js +33 -3
  98. package/template/claude-task-manager/queue-engine.js +45 -1
  99. package/template/claude-task-manager/server.js +3367 -540
  100. package/template/claude-task-manager/workers/approval-blocklist.js +130 -17
  101. package/template/claude-task-manager/workers/db-owner-worker.js +31 -1
  102. package/template/claude-task-manager/workers/read-pool-worker.js +92 -5
  103. package/template/claude-task-manager/workers/session-host-process.js +10 -0
  104. package/template/claude-task-manager/workers/state-detectors/codex.js +58 -7
  105. package/template/package.json +2 -3
  106. package/template/shared/icons/AppIcon-ctm.icns +0 -0
  107. package/template/shared/icons/AppIcon-walle.icns +0 -0
  108. package/template/wall-e/agent.js +139 -18
  109. package/template/wall-e/api-walle.js +201 -22
  110. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +1981 -0
  111. package/template/wall-e/brain.js +1049 -39
  112. package/template/wall-e/chat.js +427 -86
  113. package/template/wall-e/coding/acceptance-contract.js +26 -1
  114. package/template/wall-e/coding/action-memory-policy.js +353 -0
  115. package/template/wall-e/coding/action-memory-store.js +814 -0
  116. package/template/wall-e/coding/initial-messages.js +197 -0
  117. package/template/wall-e/coding/no-progress-guard.js +327 -0
  118. package/template/wall-e/coding/permission-service.js +88 -22
  119. package/template/wall-e/coding/session-workspaces.js +81 -0
  120. package/template/wall-e/coding/shell-sandbox.js +124 -0
  121. package/template/wall-e/coding/stream-processor.js +63 -2
  122. package/template/wall-e/coding/tool-execution-controller.js +14 -1
  123. package/template/wall-e/coding/tool-registry.js +1 -1
  124. package/template/wall-e/coding/transcript-writer.js +3 -0
  125. package/template/wall-e/coding-orchestrator.js +636 -35
  126. package/template/wall-e/coding-prompts.js +51 -2
  127. package/template/wall-e/docs/model-routing-policy.md +59 -0
  128. package/template/wall-e/docs/walle-shell-sandbox.md +61 -0
  129. package/template/wall-e/extraction/knowledge-extractor.js +76 -23
  130. package/template/wall-e/http/chat-api.js +30 -12
  131. package/template/wall-e/http/model-admin.js +93 -1
  132. package/template/wall-e/lib/background-lanes.js +133 -0
  133. package/template/wall-e/lib/boot-profile.js +11 -0
  134. package/template/wall-e/lib/brain-owner-worker-client.js +324 -0
  135. package/template/wall-e/lib/brain-read-pool-client.js +311 -0
  136. package/template/wall-e/lib/diagnostics-flags.js +87 -0
  137. package/template/wall-e/lib/event-loop-monitor.js +74 -3
  138. package/template/wall-e/lib/mcp-integration.js +7 -1
  139. package/template/wall-e/lib/real-node.js +98 -0
  140. package/template/wall-e/lib/runtime-health.js +206 -0
  141. package/template/wall-e/lib/runtime-worker-pool.js +101 -0
  142. package/template/wall-e/lib/scheduler-worker-jobs.js +231 -0
  143. package/template/wall-e/lib/scheduler.js +446 -17
  144. package/template/wall-e/lib/service-health.js +61 -2
  145. package/template/wall-e/lib/service-readiness.js +258 -0
  146. package/template/wall-e/lib/usage.js +152 -0
  147. package/template/wall-e/lib/worker-thread-pool.js +389 -0
  148. package/template/wall-e/llm/client.js +81 -4
  149. package/template/wall-e/llm/default-fallback.js +54 -8
  150. package/template/wall-e/llm/mlx.js +536 -73
  151. package/template/wall-e/llm/mlx.plugin.json +1 -1
  152. package/template/wall-e/llm/ollama.js +342 -43
  153. package/template/wall-e/llm/provider-error.js +18 -1
  154. package/template/wall-e/llm/provider-health-state.js +176 -0
  155. package/template/wall-e/llm/routing-policy.js +796 -0
  156. package/template/wall-e/llm/supported-models.js +5 -0
  157. package/template/wall-e/loops/tasks.js +60 -14
  158. package/template/wall-e/loops/think.js +89 -24
  159. package/template/wall-e/mcp-server.js +192 -28
  160. package/template/wall-e/server.js +32 -7
  161. package/template/wall-e/skills/script-skill-runner.js +8 -1
  162. package/template/wall-e/skills/skill-planner.js +64 -1
  163. package/template/wall-e/tools/builtin-middleware.js +67 -2
  164. package/template/wall-e/tools/local-tools.js +116 -26
  165. package/template/wall-e/tools/permission-checker.js +52 -4
  166. package/template/wall-e/tools/permission-rules.js +36 -0
  167. package/template/wall-e/tools/shell-analyzer.js +46 -1
  168. package/template/wall-e/training/gemma-e4b-qlora.js +314 -0
  169. package/template/wall-e/training/real-trajectory-miner.js +2617 -0
  170. package/template/wall-e/training/replay-eval-analysis.js +151 -0
  171. package/template/wall-e/training/run-shell-command-selector.js +277 -0
  172. package/template/wall-e/training/tool-sft-dataset.js +312 -0
  173. package/template/wall-e/training/tool-sft-renderers.js +144 -0
  174. package/template/wall-e/training/tool-trace-harvester.js +1440 -0
  175. package/template/wall-e/training/trajectory-action-selector.js +364 -0
  176. package/template/wall-e/weather-runtime.js +232 -0
  177. package/template/wall-e/workers/brain-owner-worker.js +162 -0
  178. package/template/wall-e/workers/brain-read-worker.js +148 -0
  179. 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 resolveModel(model) {
127
+ function resolveModelSpec(model, config = {}) {
41
128
  if (!model) return null;
42
- if (MLX_MODEL_REGISTRY[model]) return MLX_MODEL_REGISTRY[model];
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(model)) return null;
45
- if (model.includes('/')) return model; // Full HF repo
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 `mlx-community/${model}`;
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 mlxModel = resolveModel(model || config.model);
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
- // Build the prompt from messages
63
- const chatMessages = [];
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
- // Build tool descriptions as system context (MLX doesn't natively support tool calling)
89
- let toolContext = '';
90
- if (tools && tools.length > 0) {
91
- toolContext = '\n\nYou have access to these tools. To use a tool, respond with a JSON block:\n```json\n{"tool": "tool_name", "input": {…}}\n```\n\nAvailable tools:\n' +
92
- tools.map(t => `- **${t.name}**: ${t.description}\n Parameters: ${JSON.stringify(t.input_schema?.properties || {})}`).join('\n');
93
- if (chatMessages.length > 0 && chatMessages[0].role === 'system') {
94
- chatMessages[0].content += toolContext;
95
- } else {
96
- chatMessages.unshift({ role: 'system', content: toolContext.trim() });
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
- MLX_PYTHON,
133
- ['-c', pyScript, JSON.stringify(chatMessages), String(maxTok), mlxModel],
134
- { env: { ...process.env, TOKENIZERS_PARALLELISM: 'false' } }
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
- const content = [];
568
+ let content = data.text;
569
+ const toolCalls = [];
147
570
  let stopReason = 'end_turn';
148
571
 
149
- if (tools && tools.length > 0) {
150
- const toolMatch = data.text.match(/```json\s*\n?(\{[\s\S]*?\})\s*\n?```/);
151
- if (toolMatch) {
152
- try {
153
- const toolCall = JSON.parse(toolMatch[1]);
154
- if (toolCall.tool && toolCall.input) {
155
- content.push({
156
- type: 'tool_use',
157
- id: `mlx_${Date.now()}`,
158
- name: toolCall.tool,
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, repo] of Object.entries(MLX_MODEL_REGISTRY)) {
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
- capabilities: ['code'],
200
- cached: true,
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
- if (!fs.existsSync(MLX_PYTHON)) return false;
638
+ const mlxPython = resolveMlxPython(config);
639
+ if (!fs.existsSync(mlxPython)) return false;
210
640
  try {
211
- const { stdout } = await execAsync(MLX_PYTHON, ['-c', 'import mlx_lm; print("ok")'], { timeout: 10000 });
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 = { createMLXProvider, resolveModel, MLX_MODEL_REGISTRY, MLX_PYTHON, MLX_VENV };
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
+ };