@modular-prompt/driver 0.13.5 → 0.15.0

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 (213) hide show
  1. package/README.md +117 -4
  2. package/dist/driver-registry/ai-service.d.ts +23 -1
  3. package/dist/driver-registry/ai-service.d.ts.map +1 -1
  4. package/dist/driver-registry/ai-service.js +44 -10
  5. package/dist/driver-registry/ai-service.js.map +1 -1
  6. package/dist/driver-registry/config-based-factory.d.ts.map +1 -1
  7. package/dist/driver-registry/config-based-factory.js +16 -0
  8. package/dist/driver-registry/config-based-factory.js.map +1 -1
  9. package/dist/driver-registry/factory-helper.d.ts +2 -0
  10. package/dist/driver-registry/factory-helper.d.ts.map +1 -1
  11. package/dist/driver-registry/factory-helper.js +21 -3
  12. package/dist/driver-registry/factory-helper.js.map +1 -1
  13. package/dist/driver-registry/index.d.ts +2 -2
  14. package/dist/driver-registry/index.d.ts.map +1 -1
  15. package/dist/driver-registry/index.js +1 -1
  16. package/dist/driver-registry/index.js.map +1 -1
  17. package/dist/driver-registry/registry.d.ts.map +1 -1
  18. package/dist/driver-registry/registry.js +3 -1
  19. package/dist/driver-registry/registry.js.map +1 -1
  20. package/dist/driver-registry/types.d.ts +8 -2
  21. package/dist/driver-registry/types.d.ts.map +1 -1
  22. package/dist/index.d.ts +11 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +11 -1
  25. package/dist/index.js.map +1 -1
  26. package/dist/local-inference/adapters.d.ts +66 -0
  27. package/dist/local-inference/adapters.d.ts.map +1 -0
  28. package/dist/local-inference/adapters.js +2 -0
  29. package/dist/local-inference/adapters.js.map +1 -0
  30. package/dist/local-inference/driver.d.ts +51 -0
  31. package/dist/local-inference/driver.d.ts.map +1 -0
  32. package/dist/local-inference/driver.js +309 -0
  33. package/dist/local-inference/driver.js.map +1 -0
  34. package/dist/local-inference/index.d.ts +22 -0
  35. package/dist/local-inference/index.d.ts.map +1 -0
  36. package/dist/local-inference/index.js +17 -0
  37. package/dist/local-inference/index.js.map +1 -0
  38. package/dist/local-inference/process-client.d.ts +50 -0
  39. package/dist/local-inference/process-client.d.ts.map +1 -0
  40. package/dist/local-inference/process-client.js +92 -0
  41. package/dist/local-inference/process-client.js.map +1 -0
  42. package/dist/local-inference/process-communication.d.ts +41 -0
  43. package/dist/local-inference/process-communication.d.ts.map +1 -0
  44. package/dist/{mlx-ml/process → local-inference}/process-communication.js +40 -47
  45. package/dist/local-inference/process-communication.js.map +1 -0
  46. package/dist/local-inference/process-port.d.ts +12 -0
  47. package/dist/local-inference/process-port.d.ts.map +1 -0
  48. package/dist/local-inference/process-port.js +2 -0
  49. package/dist/local-inference/process-port.js.map +1 -0
  50. package/dist/local-inference/prompt-utils.d.ts +6 -0
  51. package/dist/local-inference/prompt-utils.d.ts.map +1 -0
  52. package/dist/local-inference/prompt-utils.js +17 -0
  53. package/dist/local-inference/prompt-utils.js.map +1 -0
  54. package/dist/local-inference/protocol.d.ts +192 -0
  55. package/dist/local-inference/protocol.d.ts.map +1 -0
  56. package/dist/local-inference/protocol.js +2 -0
  57. package/dist/local-inference/protocol.js.map +1 -0
  58. package/dist/local-inference/queue-types.d.ts +54 -0
  59. package/dist/local-inference/queue-types.d.ts.map +1 -0
  60. package/dist/local-inference/queue-types.js +2 -0
  61. package/dist/local-inference/queue-types.js.map +1 -0
  62. package/dist/local-inference/request-queue.d.ts +36 -0
  63. package/dist/local-inference/request-queue.d.ts.map +1 -0
  64. package/dist/{mlx-ml/process/queue.js → local-inference/request-queue.js} +89 -56
  65. package/dist/local-inference/request-queue.js.map +1 -0
  66. package/dist/local-inference/stream-utils.d.ts +19 -0
  67. package/dist/local-inference/stream-utils.d.ts.map +1 -0
  68. package/dist/local-inference/stream-utils.js +76 -0
  69. package/dist/local-inference/stream-utils.js.map +1 -0
  70. package/dist/mlx-ml/mlx-cache-support.d.ts +23 -0
  71. package/dist/mlx-ml/mlx-cache-support.d.ts.map +1 -0
  72. package/dist/mlx-ml/mlx-cache-support.js +45 -0
  73. package/dist/mlx-ml/mlx-cache-support.js.map +1 -0
  74. package/dist/mlx-ml/mlx-driver.d.ts +20 -59
  75. package/dist/mlx-ml/mlx-driver.d.ts.map +1 -1
  76. package/dist/mlx-ml/mlx-driver.js +86 -415
  77. package/dist/mlx-ml/mlx-driver.js.map +1 -1
  78. package/dist/mlx-ml/mlx-local-inference-adapters.d.ts +3 -0
  79. package/dist/mlx-ml/mlx-local-inference-adapters.d.ts.map +1 -0
  80. package/dist/mlx-ml/mlx-local-inference-adapters.js +19 -0
  81. package/dist/mlx-ml/mlx-local-inference-adapters.js.map +1 -0
  82. package/dist/mlx-ml/mlx-options.d.ts +19 -0
  83. package/dist/mlx-ml/mlx-options.d.ts.map +1 -0
  84. package/dist/mlx-ml/mlx-options.js +30 -0
  85. package/dist/mlx-ml/mlx-options.js.map +1 -0
  86. package/dist/mlx-ml/process/index.d.ts +11 -8
  87. package/dist/mlx-ml/process/index.d.ts.map +1 -1
  88. package/dist/mlx-ml/process/index.js +75 -52
  89. package/dist/mlx-ml/process/index.js.map +1 -1
  90. package/dist/mlx-ml/process/model-specific.d.ts +2 -1
  91. package/dist/mlx-ml/process/model-specific.d.ts.map +1 -1
  92. package/dist/mlx-ml/process/model-specific.js.map +1 -1
  93. package/dist/mlx-ml/process/prompt-builder.d.ts +11 -0
  94. package/dist/mlx-ml/process/prompt-builder.d.ts.map +1 -0
  95. package/dist/mlx-ml/process/prompt-builder.js +51 -0
  96. package/dist/mlx-ml/process/prompt-builder.js.map +1 -0
  97. package/dist/mlx-ml/process/types.d.ts +15 -183
  98. package/dist/mlx-ml/process/types.d.ts.map +1 -1
  99. package/dist/mlx-ml/tool-call-parser/tool-formatter.js +2 -2
  100. package/dist/mlx-ml/tool-call-parser/tool-formatter.js.map +1 -1
  101. package/dist/mlx-ml/types.d.ts +2 -45
  102. package/dist/mlx-ml/types.d.ts.map +1 -1
  103. package/dist/models-config/index.d.ts +8 -0
  104. package/dist/models-config/index.d.ts.map +1 -0
  105. package/dist/models-config/index.js +7 -0
  106. package/dist/models-config/index.js.map +1 -0
  107. package/dist/models-config/loader.d.ts +20 -0
  108. package/dist/models-config/loader.d.ts.map +1 -0
  109. package/dist/models-config/loader.js +85 -0
  110. package/dist/models-config/loader.js.map +1 -0
  111. package/dist/models-config/paths.d.ts +7 -0
  112. package/dist/models-config/paths.d.ts.map +1 -0
  113. package/dist/models-config/paths.js +11 -0
  114. package/dist/models-config/paths.js.map +1 -0
  115. package/dist/models-config/resolve.d.ts +57 -0
  116. package/dist/models-config/resolve.d.ts.map +1 -0
  117. package/dist/models-config/resolve.js +187 -0
  118. package/dist/models-config/resolve.js.map +1 -0
  119. package/dist/models-config/types.d.ts +60 -0
  120. package/dist/models-config/types.d.ts.map +1 -0
  121. package/dist/models-config/types.js +5 -0
  122. package/dist/models-config/types.js.map +1 -0
  123. package/dist/pytorch/process/index.d.ts +35 -0
  124. package/dist/pytorch/process/index.d.ts.map +1 -0
  125. package/dist/pytorch/process/index.js +69 -0
  126. package/dist/pytorch/process/index.js.map +1 -0
  127. package/dist/pytorch/pytorch-driver.d.ts +35 -0
  128. package/dist/pytorch/pytorch-driver.d.ts.map +1 -0
  129. package/dist/pytorch/pytorch-driver.js +48 -0
  130. package/dist/pytorch/pytorch-driver.js.map +1 -0
  131. package/dist/pytorch/pytorch-local-inference-adapters.d.ts +3 -0
  132. package/dist/pytorch/pytorch-local-inference-adapters.d.ts.map +1 -0
  133. package/dist/pytorch/pytorch-local-inference-adapters.js +19 -0
  134. package/dist/pytorch/pytorch-local-inference-adapters.js.map +1 -0
  135. package/dist/pytorch/pytorch-options.d.ts +8 -0
  136. package/dist/pytorch/pytorch-options.d.ts.map +1 -0
  137. package/dist/pytorch/pytorch-options.js +21 -0
  138. package/dist/pytorch/pytorch-options.js.map +1 -0
  139. package/dist/query-logger.js +1 -1
  140. package/dist/query-logger.js.map +1 -1
  141. package/dist/query-utils.d.ts +29 -0
  142. package/dist/query-utils.d.ts.map +1 -0
  143. package/dist/query-utils.js +61 -0
  144. package/dist/query-utils.js.map +1 -0
  145. package/dist/runtime/check.d.ts +10 -0
  146. package/dist/runtime/check.d.ts.map +1 -0
  147. package/dist/runtime/check.js +24 -0
  148. package/dist/runtime/check.js.map +1 -0
  149. package/dist/runtime/index.d.ts +4 -0
  150. package/dist/runtime/index.d.ts.map +1 -0
  151. package/dist/runtime/index.js +4 -0
  152. package/dist/runtime/index.js.map +1 -0
  153. package/dist/runtime/manifest-core.d.mts +27 -0
  154. package/dist/runtime/manifest-core.d.mts.map +1 -0
  155. package/dist/runtime/manifest-core.mjs +68 -0
  156. package/dist/runtime/manifest-core.mjs.map +1 -0
  157. package/dist/runtime/manifest.d.ts +18 -0
  158. package/dist/runtime/manifest.d.ts.map +1 -0
  159. package/dist/runtime/manifest.js +9 -0
  160. package/dist/runtime/manifest.js.map +1 -0
  161. package/dist/runtime/paths-core.d.mts +17 -0
  162. package/dist/runtime/paths-core.d.mts.map +1 -0
  163. package/dist/runtime/paths-core.mjs +67 -0
  164. package/dist/runtime/paths-core.mjs.map +1 -0
  165. package/dist/runtime/paths.d.ts +12 -0
  166. package/dist/runtime/paths.d.ts.map +1 -0
  167. package/dist/runtime/paths.js +17 -0
  168. package/dist/runtime/paths.js.map +1 -0
  169. package/dist/types.d.ts +20 -1
  170. package/dist/types.d.ts.map +1 -1
  171. package/dist/types.js.map +1 -1
  172. package/package.json +8 -5
  173. package/scripts/download-model.js +25 -9
  174. package/scripts/runtime-cli.js +315 -0
  175. package/skills/driver-usage/SKILL.md +56 -1
  176. package/src/mlx-ml/python/__main__.py +43 -4
  177. package/src/mlx-ml/python/handlers/__init__.py +2 -1
  178. package/src/mlx-ml/python/handlers/cancel.py +53 -0
  179. package/src/mlx-ml/python/handlers/completion.py +3 -24
  180. package/src/mlx-ml/python/handlers/{chat.py → generate.py} +36 -106
  181. package/src/mlx-ml/python/handlers/render.py +40 -0
  182. package/src/mlx-ml/python/pyproject.toml +3 -2
  183. package/src/mlx-ml/python/server.py +35 -8
  184. package/src/mlx-ml/python/utils/template_render.py +80 -0
  185. package/src/mlx-ml/python/utils/token_utils.py +2 -2
  186. package/src/mlx-ml/python/uv.lock +549 -454
  187. package/src/pytorch/python/__main__.py +19 -0
  188. package/src/pytorch/python/backends/__init__.py +3 -0
  189. package/src/pytorch/python/backends/base.py +84 -0
  190. package/src/pytorch/python/backends/transformers_lm.py +127 -0
  191. package/src/pytorch/python/handlers/__init__.py +6 -0
  192. package/src/pytorch/python/handlers/cancel.py +53 -0
  193. package/src/pytorch/python/handlers/capabilities.py +6 -0
  194. package/src/pytorch/python/handlers/completion.py +15 -0
  195. package/src/pytorch/python/handlers/format_test.py +70 -0
  196. package/src/pytorch/python/handlers/generate.py +68 -0
  197. package/src/pytorch/python/handlers/render.py +40 -0
  198. package/src/pytorch/python/handlers/tokenize.py +63 -0
  199. package/src/pytorch/python/pyproject.toml +36 -0
  200. package/src/pytorch/python/server.py +140 -0
  201. package/src/pytorch/python/utils/__init__.py +0 -0
  202. package/src/pytorch/python/utils/chat_template_constraints.py +164 -0
  203. package/src/pytorch/python/utils/prompt_builder.py +54 -0
  204. package/src/pytorch/python/utils/template_render.py +80 -0
  205. package/src/pytorch/python/utils/token_utils.py +376 -0
  206. package/src/pytorch/python/uv.lock +694 -0
  207. package/dist/mlx-ml/process/process-communication.d.ts +0 -37
  208. package/dist/mlx-ml/process/process-communication.d.ts.map +0 -1
  209. package/dist/mlx-ml/process/process-communication.js.map +0 -1
  210. package/dist/mlx-ml/process/queue.d.ts +0 -33
  211. package/dist/mlx-ml/process/queue.d.ts.map +0 -1
  212. package/dist/mlx-ml/process/queue.js.map +0 -1
  213. package/scripts/setup-mlx.js +0 -53
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Python runtime 管理 CLI
5
+ *
6
+ * setup mlx — ~/.modular-prompt/runtimes/mlx に venv を作成
7
+ * setup pytorch — ~/.modular-prompt/runtimes/pytorch に cpu-minimal venv を作成
8
+ * setup --status
9
+ * cleanup mlx [--yes]
10
+ * cleanup --all [--yes]
11
+ */
12
+
13
+ import { execSync } from 'child_process';
14
+ import { existsSync, readFileSync, rmSync, mkdirSync } from 'fs';
15
+ import { createInterface } from 'readline';
16
+ import { dirname, join } from 'path';
17
+ import { fileURLToPath, pathToFileURL } from 'url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const packageRoot = join(__dirname, '..');
21
+ const driverVersion = readPackageVersion();
22
+
23
+ function runtimeModuleUrl(name) {
24
+ const distPath = join(packageRoot, 'dist', 'runtime', name);
25
+ const srcPath = join(packageRoot, 'src', 'runtime', name);
26
+ return pathToFileURL(existsSync(distPath) ? distPath : srcPath).href;
27
+ }
28
+
29
+ const {
30
+ RUNTIME_PROFILES,
31
+ getModularPromptHome,
32
+ getRuntimeDir,
33
+ getVenvPath,
34
+ getMlxPythonDir,
35
+ getPytorchPythonDir,
36
+ isRuntimeReady,
37
+ } = await import(runtimeModuleUrl('paths-core.mjs'));
38
+
39
+ const {
40
+ collectInstalledPackages,
41
+ readManifest,
42
+ writeManifest,
43
+ } = await import(runtimeModuleUrl('manifest-core.mjs'));
44
+
45
+ function readPackageVersion() {
46
+ try {
47
+ const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
48
+ return pkg.version ?? 'unknown';
49
+ } catch {
50
+ return 'unknown';
51
+ }
52
+ }
53
+
54
+ function ensureUv() {
55
+ try {
56
+ execSync('uv --version', { stdio: 'ignore' });
57
+ return;
58
+ } catch {
59
+ console.log('⚠️ uv is not installed. Installing uv...');
60
+ execSync('curl -LsSf https://astral.sh/uv/install.sh | sh', { stdio: 'inherit' });
61
+ }
62
+ }
63
+
64
+ function setupMlx() {
65
+ if (process.platform !== 'darwin') {
66
+ console.error('❌ MLX runtime is only available on macOS (Apple Silicon).');
67
+ console.error(' For local inference on this platform, use setup-pytorch or vLLM.');
68
+ process.exit(1);
69
+ }
70
+
71
+ const pythonDir = getMlxPythonDir(packageRoot);
72
+ if (!existsSync(pythonDir)) {
73
+ console.error(`❌ MLX Python project not found: ${pythonDir}`);
74
+ process.exit(1);
75
+ }
76
+
77
+ const venvPath = getVenvPath('mlx');
78
+ const runtimeDir = getRuntimeDir('mlx');
79
+
80
+ console.log('🚀 Setting up MLX runtime...\n');
81
+ console.log(`📁 Python project: ${pythonDir}`);
82
+ console.log(`📁 Runtime venv: ${venvPath}\n`);
83
+
84
+ ensureUv();
85
+ mkdirSync(runtimeDir, { recursive: true });
86
+
87
+ const env = {
88
+ ...process.env,
89
+ UV_PROJECT_ENVIRONMENT: venvPath,
90
+ };
91
+
92
+ try {
93
+ execSync('uv venv --python 3.13', { cwd: pythonDir, stdio: 'inherit', env });
94
+ execSync('uv pip install -e .', { cwd: pythonDir, stdio: 'inherit', env });
95
+
96
+ writeManifest('mlx', {
97
+ profile: 'mlx',
98
+ driverVersion,
99
+ platform: process.platform,
100
+ pythonVersion: '3.13',
101
+ createdAt: new Date().toISOString(),
102
+ packages: collectInstalledPackages(pythonDir, venvPath),
103
+ });
104
+
105
+ console.log('\n✅ MLX runtime setup completed.');
106
+ console.log(` Home: ${getModularPromptHome()}`);
107
+ console.log(' You can now use MlxDriver from @modular-prompt/driver');
108
+ } catch (error) {
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ console.error('❌ Failed to setup MLX runtime:', message);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ const PYTORCH_CPU_INDEX = 'https://download.pytorch.org/whl/cpu';
116
+ const PYTORCH_PYTHON_VERSION = '3.12';
117
+
118
+ function setupPytorch() {
119
+ const pythonDir = getPytorchPythonDir(packageRoot);
120
+ if (!existsSync(pythonDir)) {
121
+ console.error(`❌ PyTorch Python project not found: ${pythonDir}`);
122
+ process.exit(1);
123
+ }
124
+
125
+ const venvPath = getVenvPath('pytorch');
126
+ const runtimeDir = getRuntimeDir('pytorch');
127
+
128
+ console.log('🚀 Setting up PyTorch runtime (cpu-minimal)...\n');
129
+ console.log(`📁 Python project: ${pythonDir}`);
130
+ console.log(`📁 Runtime venv: ${venvPath}`);
131
+ console.log(`📦 torch index: ${PYTORCH_CPU_INDEX}\n`);
132
+
133
+ ensureUv();
134
+ mkdirSync(runtimeDir, { recursive: true });
135
+
136
+ const env = {
137
+ ...process.env,
138
+ UV_PROJECT_ENVIRONMENT: venvPath,
139
+ };
140
+
141
+ try {
142
+ execSync(`uv venv --python ${PYTORCH_PYTHON_VERSION}`, { cwd: pythonDir, stdio: 'inherit', env });
143
+ const venvPython =
144
+ process.platform === 'win32'
145
+ ? join(venvPath, 'Scripts', 'python.exe')
146
+ : join(venvPath, 'bin', 'python');
147
+ execSync(`uv pip install --python "${venvPython}" "torch==2.9.1" --index-url ${PYTORCH_CPU_INDEX}`, {
148
+ cwd: pythonDir,
149
+ stdio: 'inherit',
150
+ env,
151
+ });
152
+ execSync(`uv pip install --python "${venvPython}" -e .`, { cwd: pythonDir, stdio: 'inherit', env });
153
+
154
+ const packages = collectInstalledPackages(pythonDir, venvPath);
155
+ writeManifest('pytorch', {
156
+ profile: 'pytorch',
157
+ variant: 'cpu-minimal',
158
+ driverVersion,
159
+ platform: process.platform,
160
+ pythonVersion: PYTORCH_PYTHON_VERSION,
161
+ torchVersion: packages?.torch,
162
+ createdAt: new Date().toISOString(),
163
+ packages,
164
+ });
165
+
166
+ console.log('\n✅ PyTorch runtime setup completed (cpu-minimal).');
167
+ console.log(` Home: ${getModularPromptHome()}`);
168
+ console.log(' You can now use PyTorchDriver from @modular-prompt/driver');
169
+ console.log(' For CUDA / custom environments, see docs/LOCAL_MODEL_SETUP.md');
170
+ } catch (error) {
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ console.error('❌ Failed to setup PyTorch runtime:', message);
173
+ process.exit(1);
174
+ }
175
+ }
176
+
177
+ function formatManifestDetail(manifest) {
178
+ const parts = [`driver ${manifest.driverVersion}`];
179
+ if (manifest.variant) {
180
+ parts.push(`variant ${manifest.variant}`);
181
+ }
182
+ const torchVersion = manifest.torchVersion ?? manifest.packages?.torch;
183
+ if (torchVersion) {
184
+ parts.push(`torch ${torchVersion}`);
185
+ }
186
+ parts.push(manifest.createdAt);
187
+ return ` (${parts.join(', ')})`;
188
+ }
189
+
190
+ function printStatus() {
191
+ console.log(`modular-prompt home: ${getModularPromptHome()}\n`);
192
+ for (const profile of RUNTIME_PROFILES) {
193
+ const ready = isRuntimeReady(profile);
194
+ const manifest = ready ? readManifest(profile) : null;
195
+ const detail = manifest ? formatManifestDetail(manifest) : '';
196
+ const icon = ready ? '✅' : '❌';
197
+ const runtimePath = getRuntimeDir(profile);
198
+ console.log(`${icon} ${profile}: ${ready ? 'ready' : 'not installed'}${detail}`);
199
+ console.log(` ${runtimePath}`);
200
+ }
201
+ const setupHints = [];
202
+ if (!isRuntimeReady('mlx') && process.platform === 'darwin') {
203
+ setupHints.push('pnpm run setup-mlx -w @modular-prompt/driver');
204
+ }
205
+ if (!isRuntimeReady('pytorch')) {
206
+ setupHints.push('pnpm run setup-pytorch -w @modular-prompt/driver');
207
+ }
208
+ if (setupHints.length > 0) {
209
+ console.log(`\nRun: ${setupHints.join(' or ')}`);
210
+ }
211
+ }
212
+
213
+ async function confirm(message) {
214
+ if (process.argv.includes('--yes') || process.argv.includes('-y')) {
215
+ return true;
216
+ }
217
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
218
+ const answer = await new Promise((resolve) => {
219
+ rl.question(`${message} [y/N] `, resolve);
220
+ });
221
+ rl.close();
222
+ return String(answer).toLowerCase() === 'y' || String(answer).toLowerCase() === 'yes';
223
+ }
224
+
225
+ async function cleanupProfile(profile) {
226
+ if (!existsSync(getRuntimeDir(profile))) {
227
+ console.log(`ℹ️ ${profile}: nothing to clean (${getRuntimeDir(profile)} does not exist)`);
228
+ return;
229
+ }
230
+ const ok = await confirm(`Delete ${profile} runtime at ${getRuntimeDir(profile)}?`);
231
+ if (!ok) {
232
+ console.log('Cancelled.');
233
+ return;
234
+ }
235
+ rmSync(getRuntimeDir(profile), { recursive: true, force: true });
236
+ console.log(`✅ Removed ${profile} runtime.`);
237
+ }
238
+
239
+ async function cleanupAll() {
240
+ const home = getModularPromptHome();
241
+ if (!existsSync(home)) {
242
+ console.log(`ℹ️ Nothing to clean (${home} does not exist)`);
243
+ return;
244
+ }
245
+ const ok = await confirm(`Delete entire modular-prompt home at ${home}?`);
246
+ if (!ok) {
247
+ console.log('Cancelled.');
248
+ return;
249
+ }
250
+ rmSync(home, { recursive: true, force: true });
251
+ console.log(`✅ Removed ${home}`);
252
+ }
253
+
254
+ function printUsage() {
255
+ console.log(`Usage:
256
+ node scripts/runtime-cli.js setup mlx Set up MLX Python runtime (macOS only)
257
+ node scripts/runtime-cli.js setup pytorch Set up PyTorch runtime (cpu-minimal)
258
+ node scripts/runtime-cli.js setup --status Show runtime status
259
+ node scripts/runtime-cli.js cleanup mlx Remove MLX runtime
260
+ node scripts/runtime-cli.js cleanup pytorch Remove PyTorch runtime
261
+ node scripts/runtime-cli.js cleanup --all Remove entire ~/.modular-prompt
262
+ node scripts/runtime-cli.js cleanup ... --yes Skip confirmation
263
+
264
+ npm scripts: setup-mlx, setup-pytorch, runtime:status, runtime:cleanup`);
265
+ }
266
+
267
+ async function main() {
268
+ const [command, target] = process.argv.slice(2);
269
+
270
+ if (!command || command === '--help' || command === '-h') {
271
+ printUsage();
272
+ process.exit(0);
273
+ }
274
+
275
+ if (command === 'setup') {
276
+ if (target === '--status' || target === 'status') {
277
+ printStatus();
278
+ return;
279
+ }
280
+ if (target === 'mlx') {
281
+ setupMlx();
282
+ return;
283
+ }
284
+ if (target === 'pytorch') {
285
+ setupPytorch();
286
+ return;
287
+ }
288
+ console.error(`Unknown setup target: ${target ?? '(none)'}`);
289
+ printUsage();
290
+ process.exit(1);
291
+ }
292
+
293
+ if (command === 'cleanup') {
294
+ if (target === '--all' || target === 'all') {
295
+ await cleanupAll();
296
+ return;
297
+ }
298
+ if (target === 'mlx' || target === 'pytorch') {
299
+ await cleanupProfile(target);
300
+ return;
301
+ }
302
+ console.error(`Unknown cleanup target: ${target ?? '(none)'}`);
303
+ printUsage();
304
+ process.exit(1);
305
+ }
306
+
307
+ console.error(`Unknown command: ${command}`);
308
+ printUsage();
309
+ process.exit(1);
310
+ }
311
+
312
+ main().catch((error) => {
313
+ console.error(error);
314
+ process.exit(1);
315
+ });
@@ -54,6 +54,8 @@ interface QueryOptions {
54
54
  stream?: boolean;
55
55
  tools?: ToolDefinition[];
56
56
  toolChoice?: ToolChoice;
57
+ cache?: boolean | 'read-only'; // プロンプトキャッシュ(ドライバー依存)
58
+ signal?: AbortSignal; // 推論キャンセル(未対応ドライバーは無視)
57
59
  }
58
60
  ```
59
61
 
@@ -67,6 +69,8 @@ interface QueryResult {
67
69
  promptTokens: number;
68
70
  completionTokens: number;
69
71
  totalTokens: number;
72
+ cacheReadTokens?: number; // キャッシュヒット分(MLX 等)
73
+ cacheWriteTokens?: number; // 新規キャッシュ書き込み分(MLX 等)
70
74
  };
71
75
  toolCalls?: ToolCall[]; // ツール呼び出し
72
76
  finishReason?: FinishReason; // 'stop' | 'length' | 'error' | 'tool_calls'
@@ -78,10 +82,47 @@ interface QueryResult {
78
82
  ```typescript
79
83
  interface StreamResult {
80
84
  stream: AsyncIterable<string>; // テキストチャンクのストリーム
81
- result: Promise<QueryResult>; // 最終結果(ストリーム完了後に解決)
85
+ result: Promise<QueryResult>; // 最終結果(ストリーム完了または abort 後に解決)
82
86
  }
83
87
  ```
84
88
 
89
+ - usage は `stream` チャンクではなく **`result.usage` のみ**
90
+ - `signal` でキャンセルした場合も `result` は reject しない(`finishReason: 'error'` + `signal.aborted` で判定)
91
+
92
+ ## 推論キャンセル(AbortSignal)
93
+
94
+ ```typescript
95
+ const controller = new AbortController();
96
+ const { stream, result } = await driver.streamQuery(compiled, {
97
+ signal: controller.signal,
98
+ });
99
+
100
+ for await (const chunk of stream) {
101
+ process.stdout.write(chunk);
102
+ if (userPressedEsc) controller.abort();
103
+ }
104
+
105
+ const final = await result;
106
+ ```
107
+
108
+ | ドライバー | 対応 |
109
+ |---|---|
110
+ | MlxDriver | ✅ stdin cancel + Python `poll_cancel()` |
111
+ | その他 | 未実装(`signal` 無視) |
112
+
113
+ ヘルパー: `watchAbortSignal`, `createAbortedStreamResult`, `isAborted`(`@modular-prompt/driver`)
114
+
115
+ ## トークン使用量とキャッシュ
116
+
117
+ ```typescript
118
+ const final = await result;
119
+ final.usage?.promptTokens; // プロバイダ報告の prompt 側総数
120
+ final.usage?.cacheReadTokens; // MLX: KV キャッシュヒット分
121
+ final.usage?.cacheWriteTokens; // MLX: 同一クエリ内の新規 prefill 分
122
+ ```
123
+
124
+ `buildQueryUsage()` でカスタムドライバーから同形式の usage を組み立てられます。
125
+
85
126
  ## 各ドライバーのConfig
86
127
 
87
128
  ### OpenAIDriver
@@ -219,6 +260,20 @@ await driver.close();
219
260
 
220
261
  Apple Silicon専用。Python 3.11以上が必要。
221
262
 
263
+ #### AbortSignal(推論キャンセル)
264
+
265
+ `QueryOptions.signal` を渡すと、ストリーム中の推論をキャンセルできます。
266
+
267
+ ```typescript
268
+ const controller = new AbortController();
269
+ const { stream, result } = await driver.streamQuery(compiled, {
270
+ signal: controller.signal,
271
+ });
272
+ // controller.abort() → Python 子プロセスに cancel 送信、部分 content 保持
273
+ ```
274
+
275
+ 実装: TS が stdin に `{"method":"cancel"}` を送り、Python の `stream_generate` ループが `poll_cancel()` で協調的に終了します。
276
+
222
277
  #### textOnlyオプション
223
278
 
224
279
  VLM(Vision Language Model)対応モデルを画像なしのテキストのみで使用する場合に`textOnly: true`を指定します。
@@ -6,7 +6,28 @@ from utils.vlm_utils import detect_model_kind
6
6
  from server import Server
7
7
 
8
8
  model_name = sys.argv[1] if len(sys.argv) > 1 else "mlx-community/gemma-3-270m-it-qat-4bit"
9
- text_only = "--text-only" in sys.argv
9
+
10
+ VALID_BACKENDS = {"auto", "lm", "vlm", "optiq"}
11
+
12
+
13
+ def parse_backend_mode() -> str:
14
+ if "--backend" in sys.argv:
15
+ idx = sys.argv.index("--backend")
16
+ if idx + 1 < len(sys.argv):
17
+ mode = sys.argv[idx + 1]
18
+ if mode not in VALID_BACKENDS:
19
+ sys.stderr.write(
20
+ f"Invalid --backend value: {mode}. "
21
+ f"Use one of: {', '.join(sorted(VALID_BACKENDS))}\n"
22
+ )
23
+ sys.exit(1)
24
+ return mode
25
+ if "--text-only" in sys.argv:
26
+ return "lm"
27
+ return "auto"
28
+
29
+
30
+ backend_mode = parse_backend_mode()
10
31
 
11
32
  drafter_model = None
12
33
  if "--drafter" in sys.argv:
@@ -25,8 +46,26 @@ if "--draft-block-size" in sys.argv:
25
46
  sys.exit(1)
26
47
 
27
48
 
28
- def create_backend(model_name: str, text_only: bool = False):
29
- model_kind = "lm" if text_only else detect_model_kind(model_name)
49
+ def ensure_optiq_registered() -> None:
50
+ import optiq # noqa: F401 - registers vendored architectures into mlx-lm
51
+
52
+
53
+ def create_backend(model_name: str, mode: str = "auto"):
54
+ if mode == "optiq":
55
+ ensure_optiq_registered()
56
+ mode = "lm"
57
+
58
+ if mode == "lm":
59
+ backend = MlxLmBackend()
60
+ backend.load(model_name)
61
+ return backend, "lm"
62
+
63
+ if mode == "vlm":
64
+ backend = MlxVlmBackend()
65
+ backend.load(model_name)
66
+ return backend, "vlm"
67
+
68
+ model_kind = detect_model_kind(model_name)
30
69
 
31
70
  if model_kind == "vlm":
32
71
  backend = MlxVlmBackend()
@@ -42,7 +81,7 @@ def create_backend(model_name: str, text_only: bool = False):
42
81
 
43
82
 
44
83
  if __name__ == "__main__":
45
- backend, model_kind = create_backend(model_name, text_only)
84
+ backend, model_kind = create_backend(model_name, backend_mode)
46
85
 
47
86
  if drafter_model:
48
87
  backend.load_drafter(drafter_model)
@@ -1,6 +1,7 @@
1
1
  from handlers.cache import handle_cache_prefill
2
2
  from handlers.capabilities import handle_capabilities
3
- from handlers.chat import handle_chat
4
3
  from handlers.completion import handle_completion
5
4
  from handlers.format_test import handle_format_test
5
+ from handlers.generate import handle_generate
6
+ from handlers.render import handle_render
6
7
  from handlers.tokenize import handle_tokenize
@@ -0,0 +1,53 @@
1
+ """Cancel request handling for in-flight streaming generation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import select
7
+ import sys
8
+
9
+ _cancel_requested = False
10
+
11
+
12
+ def request_cancel() -> None:
13
+ global _cancel_requested
14
+ _cancel_requested = True
15
+
16
+
17
+ def reset_cancel() -> None:
18
+ global _cancel_requested
19
+ _cancel_requested = False
20
+
21
+
22
+ def is_cancel_requested() -> bool:
23
+ return _cancel_requested
24
+
25
+
26
+ def poll_cancel() -> bool:
27
+ """Non-blocking check for a cancel command on stdin during streaming."""
28
+ global _cancel_requested
29
+ if _cancel_requested:
30
+ return True
31
+
32
+ try:
33
+ ready, _, _ = select.select([sys.stdin], [], [], 0)
34
+ except (ValueError, OSError):
35
+ return False
36
+
37
+ if not ready:
38
+ return False
39
+
40
+ line = sys.stdin.readline()
41
+ if not line:
42
+ return False
43
+
44
+ try:
45
+ req = json.loads(line)
46
+ except json.JSONDecodeError:
47
+ return False
48
+
49
+ if req.get("method") == "cancel":
50
+ _cancel_requested = True
51
+ return True
52
+
53
+ return False
@@ -1,10 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- import os
4
- import re
5
- import sys
6
-
7
3
  from backends.base import ModelBackend
4
+ from handlers.generate import handle_generate
8
5
 
9
6
 
10
7
  def handle_completion(
@@ -14,23 +11,5 @@ def handle_completion(
14
11
  images: list | None = None,
15
12
  max_image_size: int = 768,
16
13
  ) -> None:
17
- """completion API の処理"""
18
- if options is None:
19
- options = {}
20
-
21
- final_options = dict(options)
22
- if images:
23
- final_options["max_image_size"] = max_image_size
24
- if os.getenv('MLX_DEBUG'):
25
- display_prompt = re.sub(r'(<\|image_pad\|>)+', '<|image_pad|>...', prompt)
26
- sys.stderr.write(f"--- vlm completion (images: {len(images)}, max_size: {max_image_size})\n{display_prompt}\n")
27
- elif os.getenv('MLX_DEBUG'):
28
- if isinstance(prompt, list):
29
- sys.stderr.write(f"--- prompt: len={len(prompt)}\n")
30
- else:
31
- sys.stderr.write(f"--- prompt\n{prompt}\n")
32
-
33
- for response in backend.stream_generate(prompt, final_options, images):
34
- print(response.text.replace("\0", ""), end="", flush=True)
35
-
36
- print("\n", end="\0", flush=True)
14
+ """completion API(後方互換)— generate に委譲"""
15
+ handle_generate(backend, prompt, options, images, max_image_size)