notioncode 0.1.1 → 0.1.3

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 (77) hide show
  1. package/README.md +10 -4
  2. package/agent-runtime-server/package-lock.json +4381 -0
  3. package/agent-runtime-server/package.json +36 -0
  4. package/agent-runtime-server/scripts/fix-node-pty.js +67 -0
  5. package/agent-runtime-server/server/agent-session-service.js +816 -0
  6. package/agent-runtime-server/server/claude-sdk.js +836 -0
  7. package/agent-runtime-server/server/cli.js +330 -0
  8. package/agent-runtime-server/server/constants/config.js +5 -0
  9. package/agent-runtime-server/server/cursor-cli.js +335 -0
  10. package/agent-runtime-server/server/database/db.js +653 -0
  11. package/agent-runtime-server/server/database/init.sql +99 -0
  12. package/agent-runtime-server/server/gemini-cli.js +460 -0
  13. package/agent-runtime-server/server/gemini-response-handler.js +79 -0
  14. package/agent-runtime-server/server/index.js +2569 -0
  15. package/agent-runtime-server/server/load-env.js +32 -0
  16. package/agent-runtime-server/server/middleware/auth.js +132 -0
  17. package/agent-runtime-server/server/openai-codex.js +512 -0
  18. package/agent-runtime-server/server/projects.js +2594 -0
  19. package/agent-runtime-server/server/providers/claude/adapter.js +278 -0
  20. package/agent-runtime-server/server/providers/codex/adapter.js +248 -0
  21. package/agent-runtime-server/server/providers/cursor/adapter.js +353 -0
  22. package/agent-runtime-server/server/providers/gemini/adapter.js +186 -0
  23. package/agent-runtime-server/server/providers/registry.js +44 -0
  24. package/agent-runtime-server/server/providers/types.js +119 -0
  25. package/agent-runtime-server/server/providers/utils.js +29 -0
  26. package/agent-runtime-server/server/routes/agent-sessions.js +238 -0
  27. package/agent-runtime-server/server/routes/agent.js +1244 -0
  28. package/agent-runtime-server/server/routes/auth.js +144 -0
  29. package/agent-runtime-server/server/routes/cli-auth.js +478 -0
  30. package/agent-runtime-server/server/routes/codex.js +329 -0
  31. package/agent-runtime-server/server/routes/commands.js +596 -0
  32. package/agent-runtime-server/server/routes/cursor.js +798 -0
  33. package/agent-runtime-server/server/routes/gemini.js +24 -0
  34. package/agent-runtime-server/server/routes/git.js +1508 -0
  35. package/agent-runtime-server/server/routes/mcp-utils.js +48 -0
  36. package/agent-runtime-server/server/routes/mcp.js +552 -0
  37. package/agent-runtime-server/server/routes/messages.js +61 -0
  38. package/agent-runtime-server/server/routes/plugins.js +307 -0
  39. package/agent-runtime-server/server/routes/projects.js +548 -0
  40. package/agent-runtime-server/server/routes/settings.js +276 -0
  41. package/agent-runtime-server/server/routes/taskmaster.js +1963 -0
  42. package/agent-runtime-server/server/routes/user.js +123 -0
  43. package/agent-runtime-server/server/services/notification-orchestrator.js +227 -0
  44. package/agent-runtime-server/server/services/vapid-keys.js +35 -0
  45. package/agent-runtime-server/server/sessionManager.js +226 -0
  46. package/agent-runtime-server/server/utils/commandParser.js +303 -0
  47. package/agent-runtime-server/server/utils/frontmatter.js +18 -0
  48. package/agent-runtime-server/server/utils/gitConfig.js +34 -0
  49. package/agent-runtime-server/server/utils/mcp-detector.js +198 -0
  50. package/agent-runtime-server/server/utils/plugin-loader.js +457 -0
  51. package/agent-runtime-server/server/utils/plugin-process-manager.js +184 -0
  52. package/agent-runtime-server/server/utils/taskmaster-websocket.js +129 -0
  53. package/agent-runtime-server/shared/modelConstants.js +12 -0
  54. package/agent-runtime-server/shared/modelConstants.test.js +34 -0
  55. package/agent-runtime-server/shared/networkHosts.js +22 -0
  56. package/agent-runtime-server/test_sdk.mjs +16 -0
  57. package/bin/bridges/darwin-x64/nocode-bridge +0 -0
  58. package/bin/{nocode-local.js → notioncode.js} +0 -0
  59. package/dist/assets/icon-CQtd7WEB.png +0 -0
  60. package/dist/assets/index-Ctr1ES45.js +1 -0
  61. package/dist/assets/index-DhCWie1Z.css +1 -0
  62. package/dist/assets/index-DzqxG7Z8.js +689 -0
  63. package/dist/index.html +46 -0
  64. package/dist/onboarding/step1_create.png +0 -0
  65. package/dist/onboarding/step2_capabilities.png +0 -0
  66. package/dist/onboarding/step2b_content_access.png +0 -0
  67. package/dist/onboarding/step2c_page_access.png +0 -0
  68. package/dist/onboarding/step3_token.png +0 -0
  69. package/dist/onboarding/step4_webhook.png +0 -0
  70. package/dist/onboarding/step6a_verify.png +0 -0
  71. package/dist/onboarding/step6b_copy_verify_token.png +0 -0
  72. package/dist/tinyfish-fish-only.png +0 -0
  73. package/lib/install.js +33 -2
  74. package/lib/start.js +157 -25
  75. package/package.json +7 -4
  76. package/src/shared/modelRegistry.d.ts +24 -0
  77. package/src/shared/modelRegistry.js +163 -0
@@ -0,0 +1,46 @@
1
+ <!doctype html>
2
+ <html lang="en" style="background-color: #1a1f2e;">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" type="image/png" href="/icon.png" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Notion Code</title>
9
+ <script>
10
+ (function () {
11
+ try {
12
+ const savedTheme = localStorage.getItem('app-theme-preference');
13
+ const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
14
+
15
+ // Determine if should be dark for later use, but ALWAYS start with splash color
16
+ const shouldBeDark = savedTheme === 'dark' || ((!savedTheme || savedTheme === 'system') && systemDark);
17
+
18
+ // Set background color IMMEDIATELY to SPLASH COLOR (seamless transition)
19
+ document.documentElement.style.backgroundColor = '#1a1f2e';
20
+
21
+ if (shouldBeDark) {
22
+ document.documentElement.classList.add('dark');
23
+ document.documentElement.setAttribute('data-theme', 'dark');
24
+ } else {
25
+ document.documentElement.classList.remove('dark');
26
+ document.documentElement.setAttribute('data-theme', 'light');
27
+ }
28
+ } catch (e) {
29
+ console.error('Failed to apply theme during boot:', e);
30
+ document.documentElement.style.backgroundColor = '#1a1f2e';
31
+ }
32
+ })();
33
+ </script>
34
+ <script type="module" crossorigin src="./assets/index-DzqxG7Z8.js"></script>
35
+ <link rel="stylesheet" crossorigin href="./assets/index-DhCWie1Z.css">
36
+ </head>
37
+
38
+ <body style="margin: 0; background-color: #1a1f2e;">
39
+
40
+
41
+
42
+
43
+ <div id="root"></div>
44
+ </body>
45
+
46
+ </html>
Binary file
Binary file
Binary file
package/lib/install.js CHANGED
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import { pipeline } from 'node:stream/promises';
5
5
  import { spawnSync } from 'node:child_process';
6
+ import { createRequire } from 'node:module';
6
7
  import { fileURLToPath } from 'node:url';
7
8
 
8
9
  import { commandForPlatform, defaultCacheDir, resolvePlatformTarget } from './platform.js';
@@ -10,6 +11,7 @@ import { commandForPlatform, defaultCacheDir, resolvePlatformTarget } from './pl
10
11
  const DEFAULT_ASSET_BASE =
11
12
  process.env.NOCODE_COMPANION_ASSET_BASE_URL ||
12
13
  'https://github.com/tadkt/nocode/releases/download';
14
+ const require = createRequire(import.meta.url);
13
15
 
14
16
  function packageVersion() {
15
17
  try {
@@ -40,6 +42,30 @@ function localBinaryName() {
40
42
  return process.platform === 'win32' ? 'nocode-bridge.exe' : 'nocode-bridge';
41
43
  }
42
44
 
45
+ export function bridgeAssetFileNameForTarget(target = resolvePlatformTarget()) {
46
+ return `nocode-bridge-${target}${process.platform === 'win32' ? '.exe' : ''}`;
47
+ }
48
+
49
+ export function resolveBundledNpmBridgePath(target = resolvePlatformTarget()) {
50
+ try {
51
+ const packageJsonPath = require.resolve('notioncode/package.json');
52
+ const packageDir = path.dirname(packageJsonPath);
53
+ const binaryPath = path.join(packageDir, 'bin', 'bridges', target, localBinaryName());
54
+
55
+ if (!existsSync(binaryPath)) {
56
+ return null;
57
+ }
58
+
59
+ if (process.platform !== 'win32') {
60
+ chmodSync(binaryPath, 0o755);
61
+ }
62
+
63
+ return binaryPath;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
43
69
  function canUseCargo() {
44
70
  const result = spawnSync(commandForPlatform('cargo'), ['--version'], {
45
71
  stdio: 'ignore',
@@ -66,6 +92,11 @@ export async function ensureBridgeBinary(version = DEFAULT_VERSION) {
66
92
  return envPath;
67
93
  }
68
94
 
95
+ const npmInstalled = resolveBundledNpmBridgePath();
96
+ if (npmInstalled) {
97
+ return npmInstalled;
98
+ }
99
+
69
100
  const installed = resolveInstalledBridgePath(version);
70
101
  if (existsSync(installed)) {
71
102
  return installed;
@@ -88,7 +119,7 @@ export async function ensureBridgeBinary(version = DEFAULT_VERSION) {
88
119
  export async function downloadBridgeBinary(version = DEFAULT_VERSION, destination = resolveInstalledBridgePath(version)) {
89
120
  mkdirSync(path.dirname(destination), { recursive: true });
90
121
  const target = resolvePlatformTarget();
91
- const assetName = `nocode-bridge-${target}${process.platform === 'win32' ? '.exe' : ''}`;
122
+ const assetName = bridgeAssetFileNameForTarget(target);
92
123
  const url = `${DEFAULT_ASSET_BASE}/notioncode-v${version}/${assetName}`;
93
124
 
94
125
  const response = await fetch(url);
@@ -123,7 +154,7 @@ export function buildLocalBridgeAsset(version = DEFAULT_VERSION) {
123
154
  const artifactsDir = path.join(repoRoot, 'artifacts', 'local-companion', version);
124
155
  mkdirSync(artifactsDir, { recursive: true });
125
156
  const target = resolvePlatformTarget();
126
- const dest = path.join(artifactsDir, `nocode-bridge-${target}${process.platform === 'win32' ? '.exe' : ''}`);
157
+ const dest = path.join(artifactsDir, bridgeAssetFileNameForTarget(target));
127
158
  copyFileSync(source, dest);
128
159
  if (process.platform !== 'win32') {
129
160
  chmodSync(dest, 0o755);
package/lib/start.js CHANGED
@@ -1,8 +1,8 @@
1
+ import { createReadStream, existsSync, readFileSync, statSync } from 'node:fs';
1
2
  import http from 'node:http';
2
3
  import https from 'node:https';
3
4
  import path from 'node:path';
4
5
  import { spawn, spawnSync } from 'node:child_process';
5
- import { existsSync } from 'node:fs';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  import { setTimeout as sleep } from 'node:timers/promises';
8
8
 
@@ -26,10 +26,18 @@ function detectRuntimeRoot() {
26
26
  return process.env.NOCODE_RUNTIME_ROOT;
27
27
  }
28
28
 
29
+ const packageRoot = packageRootDir();
30
+ if (
31
+ existsSync(path.join(packageRoot, 'agent-runtime-server', 'package.json')) ||
32
+ existsSync(path.join(packageRoot, 'dist', 'index.html'))
33
+ ) {
34
+ return packageRoot;
35
+ }
36
+
29
37
  const candidates = [
30
- path.resolve(packageRootDir(), '..', '..'),
38
+ path.resolve(packageRoot, '..', '..'),
31
39
  process.cwd(),
32
- path.resolve(packageRootDir(), '..'),
40
+ path.resolve(packageRoot, '..'),
33
41
  ];
34
42
 
35
43
  for (const candidate of candidates) {
@@ -204,6 +212,97 @@ function hasLocalUiRepo() {
204
212
  return existsSync(path.join(runtimeRoot, 'package.json')) && existsSync(path.join(runtimeRoot, 'node_modules', 'vite', 'bin', 'vite.js'));
205
213
  }
206
214
 
215
+ function hasBundledLocalUi(runtimeRoot) {
216
+ return existsSync(path.join(runtimeRoot, 'dist', 'index.html'));
217
+ }
218
+
219
+ function mimeTypeForPath(filePath) {
220
+ const ext = path.extname(filePath).toLowerCase();
221
+ switch (ext) {
222
+ case '.html':
223
+ return 'text/html; charset=utf-8';
224
+ case '.js':
225
+ return 'text/javascript; charset=utf-8';
226
+ case '.css':
227
+ return 'text/css; charset=utf-8';
228
+ case '.json':
229
+ return 'application/json; charset=utf-8';
230
+ case '.svg':
231
+ return 'image/svg+xml';
232
+ case '.png':
233
+ return 'image/png';
234
+ case '.jpg':
235
+ case '.jpeg':
236
+ return 'image/jpeg';
237
+ case '.webp':
238
+ return 'image/webp';
239
+ case '.woff2':
240
+ return 'font/woff2';
241
+ case '.txt':
242
+ return 'text/plain; charset=utf-8';
243
+ default:
244
+ return 'application/octet-stream';
245
+ }
246
+ }
247
+
248
+ function resolveStaticAssetPath(rootDir, requestUrl) {
249
+ const url = new URL(requestUrl, 'http://localhost');
250
+ const normalizedPath = decodeURIComponent(url.pathname);
251
+ const candidate = normalizedPath === '/'
252
+ ? path.join(rootDir, 'index.html')
253
+ : path.join(rootDir, normalizedPath.replace(/^\/+/, ''));
254
+
255
+ const normalizedRoot = `${path.resolve(rootDir)}${path.sep}`;
256
+ const resolvedCandidate = path.resolve(candidate);
257
+ if (resolvedCandidate !== path.resolve(rootDir, 'index.html') && !resolvedCandidate.startsWith(normalizedRoot)) {
258
+ return null;
259
+ }
260
+
261
+ if (existsSync(resolvedCandidate)) {
262
+ const stats = statSync(resolvedCandidate);
263
+ if (stats.isFile()) {
264
+ return resolvedCandidate;
265
+ }
266
+ }
267
+
268
+ if (!path.extname(normalizedPath)) {
269
+ return path.join(rootDir, 'index.html');
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ function startBundledLocalUiServer({ runtimeRoot, useHttpsLocalUi, localHttps, localUiHost }) {
276
+ const distRoot = path.join(runtimeRoot, 'dist');
277
+ const listener = (req, res) => {
278
+ const targetFile = resolveStaticAssetPath(distRoot, req.url || '/');
279
+ if (!targetFile || !existsSync(targetFile)) {
280
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
281
+ res.end('Not found');
282
+ return;
283
+ }
284
+
285
+ res.writeHead(200, {
286
+ 'Content-Type': mimeTypeForPath(targetFile),
287
+ 'Cache-Control': targetFile.endsWith('index.html') ? 'no-cache' : 'public, max-age=31536000, immutable',
288
+ });
289
+ createReadStream(targetFile).pipe(res);
290
+ };
291
+
292
+ const server = useHttpsLocalUi
293
+ ? https.createServer(
294
+ {
295
+ cert: readFileSync(localHttps.certFile),
296
+ key: readFileSync(localHttps.keyFile),
297
+ },
298
+ listener
299
+ )
300
+ : http.createServer(listener);
301
+
302
+ server.listen(1420, localUiHost);
303
+ return server;
304
+ }
305
+
207
306
  function hasCargoBridgeSource(runtimeRoot) {
208
307
  return existsSync(path.join(runtimeRoot, 'src-tauri', 'Cargo.toml'));
209
308
  }
@@ -215,15 +314,19 @@ function canUseCargo() {
215
314
  return result.status === 0;
216
315
  }
217
316
 
218
- function ensureSidecarInstallIfRepoPresent() {
219
- const runtimeRoot = detectRuntimeRoot();
220
- if (!existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'package.json'))) {
317
+ function ensureSidecarInstallIfPresent(runtimeRoot) {
318
+ const sidecarRoot = path.join(runtimeRoot, 'agent-runtime-server');
319
+ if (!existsSync(path.join(sidecarRoot, 'package.json'))) {
320
+ return;
321
+ }
322
+
323
+ if (existsSync(path.join(sidecarRoot, 'node_modules', '.package-lock.json'))) {
221
324
  return;
222
325
  }
223
326
 
224
327
  const npmCmd = commandForPlatform('npm');
225
- const result = spawnSync(npmCmd, ['--prefix', 'agent-runtime-server', 'install'], {
226
- cwd: runtimeRoot,
328
+ const result = spawnSync(npmCmd, ['install'], {
329
+ cwd: sidecarRoot,
227
330
  stdio: 'inherit',
228
331
  });
229
332
  if (result.status !== 0) {
@@ -231,22 +334,41 @@ function ensureSidecarInstallIfRepoPresent() {
231
334
  }
232
335
  }
233
336
 
337
+ function hasBundledSidecar(runtimeRoot) {
338
+ return existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'server', 'index.js'));
339
+ }
340
+
341
+ function ensureSidecarInstallIfRepoPresent() {
342
+ const runtimeRoot = detectRuntimeRoot();
343
+ if (!existsSync(path.join(runtimeRoot, 'agent-runtime-server', 'package.json'))) {
344
+ return;
345
+ }
346
+ ensureSidecarInstallIfPresent(runtimeRoot);
347
+ }
348
+
234
349
  export async function startLocalCompanion(options = {}) {
235
350
  const runtimeRoot = detectRuntimeRoot();
236
351
  const requestedLocalUi = options.withLocalUi !== false;
237
- const shouldStartLocalUi = requestedLocalUi && hasLocalUiRepo();
352
+ const shouldStartRepoLocalUi = requestedLocalUi && hasLocalUiRepo();
353
+ const shouldStartBundledLocalUi = requestedLocalUi && !shouldStartRepoLocalUi && hasBundledLocalUi(runtimeRoot);
354
+ const shouldStartLocalUi = shouldStartRepoLocalUi || shouldStartBundledLocalUi;
238
355
  const useCargoBridge = hasCargoBridgeSource(runtimeRoot) && canUseCargo();
239
356
  const useHttpsLocalUi =
240
357
  typeof options.localUiHttps === 'boolean' ? options.localUiHttps : shouldUseHttpsLocalUi();
241
358
  const localUiUrl = useHttpsLocalUi ? LOCAL_UI_HTTPS_URL : LOCAL_UI_URL;
242
359
  const localUiHost = useHttpsLocalUi ? 'localhost' : '127.0.0.1';
243
360
  let localHttps = null;
361
+ let bundledLocalUiServer = null;
244
362
 
245
363
  if (shouldStartLocalUi && useHttpsLocalUi) {
246
364
  localHttps = await ensureTrustedLocalhostCert();
247
365
  }
248
366
 
249
- ensureSidecarInstallIfRepoPresent();
367
+ if (hasBundledSidecar(runtimeRoot)) {
368
+ ensureSidecarInstallIfPresent(runtimeRoot);
369
+ } else {
370
+ ensureSidecarInstallIfRepoPresent();
371
+ }
250
372
 
251
373
  let vite = null;
252
374
  if (shouldStartLocalUi) {
@@ -254,20 +376,29 @@ export async function startLocalCompanion(options = {}) {
254
376
  console.log(`[notioncode] Reusing existing local UI on ${localUiUrl} .`);
255
377
  } else {
256
378
  console.log(`[notioncode] Starting local UI on ${localUiUrl} ...`);
257
- vite = spawnLogged(commandForPlatform('node'), ['node_modules/vite/bin/vite.js', '--host', localUiHost], {
258
- cwd: runtimeRoot,
259
- env: {
260
- VITE_SINGLE_USER_MODE: 'true',
261
- NOCODE_LOCAL_UI_USE_HTTPS: useHttpsLocalUi ? '1' : '0',
262
- NOCODE_LOCAL_UI_HOST: localUiHost,
263
- ...(localHttps
264
- ? {
265
- NOCODE_LOCAL_UI_CERT_FILE: localHttps.certFile,
266
- NOCODE_LOCAL_UI_KEY_FILE: localHttps.keyFile,
267
- }
268
- : {}),
269
- },
270
- });
379
+ if (shouldStartRepoLocalUi) {
380
+ vite = spawnLogged(commandForPlatform('node'), ['node_modules/vite/bin/vite.js', '--host', localUiHost], {
381
+ cwd: runtimeRoot,
382
+ env: {
383
+ VITE_SINGLE_USER_MODE: 'true',
384
+ NOCODE_LOCAL_UI_USE_HTTPS: useHttpsLocalUi ? '1' : '0',
385
+ NOCODE_LOCAL_UI_HOST: localUiHost,
386
+ ...(localHttps
387
+ ? {
388
+ NOCODE_LOCAL_UI_CERT_FILE: localHttps.certFile,
389
+ NOCODE_LOCAL_UI_KEY_FILE: localHttps.keyFile,
390
+ }
391
+ : {}),
392
+ },
393
+ });
394
+ } else if (shouldStartBundledLocalUi) {
395
+ bundledLocalUiServer = startBundledLocalUiServer({
396
+ runtimeRoot,
397
+ useHttpsLocalUi,
398
+ localHttps,
399
+ localUiHost,
400
+ });
401
+ }
271
402
  }
272
403
  }
273
404
 
@@ -297,6 +428,7 @@ export async function startLocalCompanion(options = {}) {
297
428
  const shutdown = (code = 0) => {
298
429
  vite?.kill('SIGTERM');
299
430
  bridge?.kill('SIGTERM');
431
+ bundledLocalUiServer?.close();
300
432
  setTimeout(() => process.exit(code), 250);
301
433
  };
302
434
 
@@ -321,7 +453,7 @@ export async function startLocalCompanion(options = {}) {
321
453
  await verifyTrustedBridge(localUiUrl);
322
454
  } catch (error) {
323
455
  throw new Error(
324
- `Safari-compatible local bridge could not be initialized.\n\nReason:\n ${error instanceof Error ? error.message : String(error)}\n\nFix:\n Install mkcert and trust the local certificate authority, then rerun:\n mkcert -install\n npx create notioncode`
456
+ `Safari-compatible local bridge could not be initialized.\n\nReason:\n ${error instanceof Error ? error.message : String(error)}\n\nFix:\n Install mkcert and trust the local certificate authority, then rerun:\n mkcert -install\n npx notioncode start`
325
457
  );
326
458
  }
327
459
  }
package/package.json CHANGED
@@ -1,19 +1,22 @@
1
1
  {
2
2
  "name": "notioncode",
3
- "version": "0.1.1",
4
- "description": "Local companion runtime used by the `npx create notioncode` setup flow.",
3
+ "version": "0.1.3",
4
+ "description": "Local companion runtime used by the `npx notioncode start` setup flow.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "dependencies": {
8
8
  "default-browser-id": "^5.0.1"
9
9
  },
10
10
  "bin": {
11
- "notioncode": "bin/nocode-local.js"
11
+ "notioncode": "bin/notioncode.js"
12
12
  },
13
13
  "files": [
14
+ "agent-runtime-server",
14
15
  "bin",
16
+ "dist",
15
17
  "lib",
16
- "README.md"
18
+ "README.md",
19
+ "src"
17
20
  ],
18
21
  "publishConfig": {
19
22
  "access": "public"
@@ -0,0 +1,24 @@
1
+ export type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
2
+
3
+ export interface ModelOption {
4
+ value: string;
5
+ label: string;
6
+ }
7
+
8
+ export interface ModelConfig {
9
+ OPTIONS: ModelOption[];
10
+ DEFAULT: string;
11
+ }
12
+
13
+ export const CLAUDE_MODELS: ModelConfig;
14
+ export const CURSOR_MODELS: ModelConfig;
15
+ export const CODEX_MODELS: ModelConfig;
16
+ export const GEMINI_MODELS: ModelConfig;
17
+
18
+ export const MODEL_CONFIGS: Record<AgentProvider, ModelConfig>;
19
+
20
+ export function getModelOptions(provider: AgentProvider): ModelOption[];
21
+ export function getDefaultModel(provider: AgentProvider): string;
22
+ export function isSupportedModel(provider: AgentProvider, value: string | null | undefined): boolean;
23
+ export function migrateLegacyModel(provider: AgentProvider, value: string | null | undefined): string | null;
24
+ export function normalizeModel(provider: AgentProvider, value: string | null | undefined): string;
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Shared model registry used by both the frontend workspace UI and the
3
+ * agent-runtime sidecar.
4
+ */
5
+
6
+ /** @typedef {'claude' | 'cursor' | 'codex' | 'gemini'} AgentProvider */
7
+
8
+ /**
9
+ * @typedef {{ value: string, label: string }} ModelOption
10
+ */
11
+
12
+ export const CLAUDE_MODELS = {
13
+ OPTIONS: [
14
+ { value: 'sonnet', label: 'Sonnet' },
15
+ { value: 'opus', label: 'Opus' },
16
+ { value: 'haiku', label: 'Haiku' },
17
+ { value: 'opusplan', label: 'Opus Plan' },
18
+ { value: 'sonnet[1m]', label: 'Sonnet [1M]' },
19
+ ],
20
+ DEFAULT: 'sonnet',
21
+ };
22
+
23
+ export const CURSOR_MODELS = {
24
+ OPTIONS: [
25
+ { value: 'opus-4.6-thinking', label: 'Claude 4.6 Opus (Thinking)' },
26
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3' },
27
+ { value: 'gpt-5.2-high', label: 'GPT-5.2 High' },
28
+ { value: 'gemini-3-pro', label: 'Gemini 3 Pro' },
29
+ { value: 'opus-4.5-thinking', label: 'Claude 4.5 Opus (Thinking)' },
30
+ { value: 'gpt-5.2', label: 'GPT-5.2' },
31
+ { value: 'gpt-5.1', label: 'GPT-5.1' },
32
+ { value: 'gpt-5.1-high', label: 'GPT-5.1 High' },
33
+ { value: 'composer-1', label: 'Composer 1' },
34
+ { value: 'auto', label: 'Auto' },
35
+ { value: 'sonnet-4.5', label: 'Claude 4.5 Sonnet' },
36
+ { value: 'sonnet-4.5-thinking', label: 'Claude 4.5 Sonnet (Thinking)' },
37
+ { value: 'opus-4.5', label: 'Claude 4.5 Opus' },
38
+ { value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
39
+ { value: 'gpt-5.1-codex-high', label: 'GPT-5.1 Codex High' },
40
+ { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
41
+ { value: 'gpt-5.1-codex-max-high', label: 'GPT-5.1 Codex Max High' },
42
+ { value: 'opus-4.1', label: 'Claude 4.1 Opus' },
43
+ { value: 'grok', label: 'Grok' },
44
+ ],
45
+ DEFAULT: 'gpt-5-3-codex',
46
+ };
47
+
48
+ export const CODEX_MODELS = {
49
+ OPTIONS: [
50
+ { value: 'gpt-5.4', label: 'GPT-5.4' },
51
+ { value: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
52
+ { value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
53
+ { value: 'gpt-5.2', label: 'GPT-5.2' },
54
+ { value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
55
+ { value: 'o3', label: 'O3' },
56
+ { value: 'o4-mini', label: 'O4-mini' },
57
+ ],
58
+ DEFAULT: 'gpt-5.4',
59
+ };
60
+
61
+ export const GEMINI_MODELS = {
62
+ OPTIONS: [
63
+ { value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' },
64
+ { value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
65
+ { value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
66
+ { value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
67
+ { value: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
68
+ { value: 'gemini-2.0-flash-lite', label: 'Gemini 2.0 Flash Lite' },
69
+ { value: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' },
70
+ { value: 'gemini-2.0-pro-exp', label: 'Gemini 2.0 Pro Experimental' },
71
+ { value: 'gemini-2.0-flash-thinking-exp', label: 'Gemini 2.0 Flash Thinking' },
72
+ ],
73
+ DEFAULT: 'gemini-2.5-flash',
74
+ };
75
+
76
+ export const MODEL_CONFIGS = {
77
+ claude: CLAUDE_MODELS,
78
+ cursor: CURSOR_MODELS,
79
+ codex: CODEX_MODELS,
80
+ gemini: GEMINI_MODELS,
81
+ };
82
+
83
+ const LEGACY_MODEL_MIGRATIONS = {
84
+ claude: {
85
+ sonnet: 'sonnet',
86
+ opus: 'opus',
87
+ haiku: 'haiku',
88
+ 'claude-3-5-sonnet-20241022': 'sonnet',
89
+ 'claude-3-7-sonnet-20250219': 'sonnet',
90
+ 'claude-3-opus-20240229': 'opus',
91
+ 'claude-3-5-haiku-20241022': 'haiku',
92
+ },
93
+ cursor: {},
94
+ codex: {},
95
+ gemini: {},
96
+ };
97
+
98
+ /**
99
+ * @param {AgentProvider} provider
100
+ * @returns {ModelOption[]}
101
+ */
102
+ export function getModelOptions(provider) {
103
+ return MODEL_CONFIGS[provider]?.OPTIONS ?? [];
104
+ }
105
+
106
+ /**
107
+ * @param {AgentProvider} provider
108
+ * @returns {string}
109
+ */
110
+ export function getDefaultModel(provider) {
111
+ return MODEL_CONFIGS[provider]?.DEFAULT ?? CODEX_MODELS.DEFAULT;
112
+ }
113
+
114
+ /**
115
+ * @param {AgentProvider} provider
116
+ * @param {string | null | undefined} value
117
+ * @returns {boolean}
118
+ */
119
+ export function isSupportedModel(provider, value) {
120
+ if (!value) {
121
+ return false;
122
+ }
123
+
124
+ return getModelOptions(provider).some((option) => option.value === value);
125
+ }
126
+
127
+ /**
128
+ * @param {AgentProvider} provider
129
+ * @param {string | null | undefined} value
130
+ * @returns {string | null}
131
+ */
132
+ export function migrateLegacyModel(provider, value) {
133
+ if (!value) {
134
+ return null;
135
+ }
136
+
137
+ const directMigration = LEGACY_MODEL_MIGRATIONS[provider]?.[value];
138
+ if (directMigration) {
139
+ return directMigration;
140
+ }
141
+
142
+ if (provider === 'claude' && value.startsWith('claude-')) {
143
+ return getDefaultModel('claude');
144
+ }
145
+
146
+ return value;
147
+ }
148
+
149
+ /**
150
+ * Normalize a model selection to a canonical supported value for a provider.
151
+ *
152
+ * @param {AgentProvider} provider
153
+ * @param {string | null | undefined} value
154
+ * @returns {string}
155
+ */
156
+ export function normalizeModel(provider, value) {
157
+ const migrated = migrateLegacyModel(provider, value);
158
+ if (migrated && isSupportedModel(provider, migrated)) {
159
+ return migrated;
160
+ }
161
+
162
+ return getDefaultModel(provider);
163
+ }