@jacobbd/relay-ai 0.12.9 → 0.12.11

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.
@@ -7,7 +7,7 @@ import { join } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@jacobbd/relay-ai",
10
- version: "0.12.9",
10
+ version: "0.12.11",
11
11
  publishConfig: {
12
12
  access: "public"
13
13
  },
@@ -68,7 +68,6 @@ var package_default = {
68
68
  "@ai-sdk/openai-compatible": "^3.0.44",
69
69
  "@ai-sdk/perplexity": "^4.0.39",
70
70
  "@ai-sdk/togetherai": "^3.0.45",
71
- "@ai-sdk/vercel": "^3.0.30",
72
71
  "@ai-sdk/xai": "^4.0.54",
73
72
  "@clack/prompts": "^0.9.1",
74
73
  "@openrouter/ai-sdk-provider": "^3.0.0",
@@ -126,7 +125,7 @@ var BACKENDS = {
126
125
  }
127
126
  };
128
127
  var CODEX_RESPONSES_LITE_WS_URL = "wss://chatgpt.com/backend-api/codex/responses";
129
- var CODEX_RESPONSES_LITE_VERSION = "0.153.4";
128
+ var CODEX_RESPONSES_LITE_VERSION = "0.155.1";
130
129
  var CODEX_RESPONSES_WEBSOCKETS_BETA = "responses_websockets=2026-02-06";
131
130
  var CONFLICTING_ENV_VARS = [
132
131
  "CLAUDE_CODE_USE_VERTEX",
@@ -219,8 +218,271 @@ function getLegacyConfPath(env = process.env, platform = process.platform) {
219
218
  return join2(env.XDG_CONFIG_HOME ?? join2(home, ".config"), appName, "config.json");
220
219
  }
221
220
 
221
+ // src/config.ts
222
+ import { dirname, join as join3 } from "path";
223
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
224
+ function readJsonFile(path) {
225
+ try {
226
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
227
+ return parsed && typeof parsed === "object" ? parsed : null;
228
+ } catch {
229
+ return null;
230
+ }
231
+ }
232
+ function ensureAppHomeMigrated() {
233
+ const configPath = getConfigPath();
234
+ if (existsSync(configPath)) return;
235
+ const legacyConfig = join3(getLegacyAppHome(), "config.json");
236
+ if (!existsSync(legacyConfig)) return;
237
+ mkdirSync(getAppHome(), { recursive: true, mode: 448 });
238
+ copyFileSync(legacyConfig, configPath);
239
+ const legacyVertex = join3(getLegacyAppHome(), "vertex-models.json");
240
+ const vertexPath = join3(getAppHome(), "vertex-models.json");
241
+ if (existsSync(legacyVertex) && !existsSync(vertexPath)) {
242
+ copyFileSync(legacyVertex, vertexPath);
243
+ }
244
+ }
245
+ function ensureConfigMigrated() {
246
+ ensureAppHomeMigrated();
247
+ const configPath = getConfigPath();
248
+ if (existsSync(configPath)) return;
249
+ const legacyPath = getLegacyConfPath();
250
+ if (!existsSync(legacyPath)) return;
251
+ const legacy = readJsonFile(legacyPath);
252
+ if (!legacy) return;
253
+ mkdirSync(dirname(configPath), { recursive: true, mode: 448 });
254
+ writeFileSync(configPath, `${JSON.stringify(legacy, null, 2)}
255
+ `, { encoding: "utf8", mode: 384 });
256
+ try {
257
+ renameSync(legacyPath, `${legacyPath}.migrated`);
258
+ } catch {
259
+ }
260
+ }
261
+ function readConfig() {
262
+ ensureConfigMigrated();
263
+ return readJsonFile(getConfigPath()) ?? {};
264
+ }
265
+ function writeConfig(config) {
266
+ const configPath = getConfigPath();
267
+ mkdirSync(dirname(configPath), { recursive: true, mode: 448 });
268
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
269
+ `, { encoding: "utf8", mode: 384 });
270
+ }
271
+ function loadPreferences() {
272
+ const config = readConfig();
273
+ const lastProvider = config.lastProvider === "opencode" ? "zen" : config.lastProvider;
274
+ return {
275
+ lastBackend: config.lastBackend,
276
+ lastModel: config.lastModel,
277
+ lastProvider,
278
+ lastCodexProvider: config.lastCodexProvider,
279
+ lastCodexModel: config.lastCodexModel,
280
+ lastGeminiProvider: config.lastGeminiProvider,
281
+ lastGeminiModel: config.lastGeminiModel,
282
+ lastAntigravityProvider: config.lastAntigravityProvider,
283
+ lastAntigravityModel: config.lastAntigravityModel,
284
+ lastClaudeTransparentMode: config.lastClaudeTransparentMode,
285
+ recentModelsByProvider: config.recentModelsByProvider,
286
+ favoriteModels: config.favoriteModels,
287
+ codexSubagentModels: Array.isArray(config.codexSubagentModels) ? config.codexSubagentModels.slice(0, CODEX_SUBAGENT_MODEL_CAP) : void 0,
288
+ antigravityCliFavoriteModels: config.antigravityCliFavoriteModels,
289
+ antigravityCliFavoritesHintShown: config.antigravityCliFavoritesHintShown,
290
+ appPathOverrides: config.appPathOverrides,
291
+ recentLaunchFolders: config.recentLaunchFolders,
292
+ server: config.server
293
+ };
294
+ }
295
+ function savePreferences(prefs) {
296
+ const config = readConfig();
297
+ if (prefs.lastBackend !== void 0) config.lastBackend = prefs.lastBackend;
298
+ if (prefs.lastModel !== void 0) config.lastModel = prefs.lastModel;
299
+ if (prefs.lastProvider !== void 0) config.lastProvider = prefs.lastProvider;
300
+ if (prefs.lastCodexProvider !== void 0) config.lastCodexProvider = prefs.lastCodexProvider;
301
+ if (prefs.lastCodexModel !== void 0) config.lastCodexModel = prefs.lastCodexModel;
302
+ if (prefs.lastGeminiProvider !== void 0) config.lastGeminiProvider = prefs.lastGeminiProvider;
303
+ if (prefs.lastGeminiModel !== void 0) config.lastGeminiModel = prefs.lastGeminiModel;
304
+ if (prefs.lastAntigravityProvider !== void 0) config.lastAntigravityProvider = prefs.lastAntigravityProvider;
305
+ if (prefs.lastAntigravityModel !== void 0) config.lastAntigravityModel = prefs.lastAntigravityModel;
306
+ if (prefs.lastClaudeTransparentMode !== void 0) config.lastClaudeTransparentMode = prefs.lastClaudeTransparentMode;
307
+ if (prefs.recentModelsByProvider !== void 0) config.recentModelsByProvider = prefs.recentModelsByProvider;
308
+ if (prefs.favoriteModels !== void 0) config.favoriteModels = prefs.favoriteModels;
309
+ if (prefs.codexSubagentModels !== void 0) {
310
+ config.codexSubagentModels = prefs.codexSubagentModels.slice(0, CODEX_SUBAGENT_MODEL_CAP);
311
+ }
312
+ if (prefs.antigravityCliFavoriteModels !== void 0) config.antigravityCliFavoriteModels = prefs.antigravityCliFavoriteModels;
313
+ if (prefs.antigravityCliFavoritesHintShown !== void 0) config.antigravityCliFavoritesHintShown = prefs.antigravityCliFavoritesHintShown;
314
+ if (prefs.appPathOverrides !== void 0) config.appPathOverrides = prefs.appPathOverrides;
315
+ if (prefs.recentLaunchFolders !== void 0) config.recentLaunchFolders = prefs.recentLaunchFolders;
316
+ writeConfig(config);
317
+ }
318
+ function getAppPathOverride(appId) {
319
+ const value = loadPreferences().appPathOverrides?.[appId];
320
+ return typeof value === "string" && value.trim() ? value : void 0;
321
+ }
322
+ function setAppPathOverride(appId, path) {
323
+ const config = readConfig();
324
+ const next = { ...config.appPathOverrides ?? {} };
325
+ const trimmed = path?.trim() ?? "";
326
+ if (trimmed) next[appId] = trimmed;
327
+ else delete next[appId];
328
+ config.appPathOverrides = next;
329
+ if (Object.keys(next).length === 0) delete config.appPathOverrides;
330
+ writeConfig(config);
331
+ return next;
332
+ }
333
+ var MAX_RECENT_MODELS = 3;
334
+ var MAX_RECENT_LAUNCH_FOLDERS = 6;
335
+ function recordLaunchFolder(folder) {
336
+ const trimmed = folder.trim();
337
+ if (!trimmed) return loadPreferences().recentLaunchFolders ?? [];
338
+ const config = readConfig();
339
+ const prev = config.recentLaunchFolders ?? [];
340
+ const next = [trimmed, ...prev.filter((path) => path !== trimmed)].slice(0, MAX_RECENT_LAUNCH_FOLDERS);
341
+ config.recentLaunchFolders = next;
342
+ writeConfig(config);
343
+ return next;
344
+ }
345
+ function recordLaunchSelection(agent, providerId, modelId, prefs) {
346
+ const prevRecent = prefs.recentModelsByProvider?.[providerId] ?? [];
347
+ const updatedRecent = [modelId, ...prevRecent.filter((id) => id !== modelId)].slice(0, MAX_RECENT_MODELS);
348
+ savePreferences({
349
+ ...agent === "claude" ? { lastProvider: providerId, lastModel: modelId } : agent === "codex" ? { lastCodexProvider: providerId, lastCodexModel: modelId } : { lastGeminiProvider: providerId, lastGeminiModel: modelId },
350
+ recentModelsByProvider: { ...prefs.recentModelsByProvider, [providerId]: updatedRecent }
351
+ });
352
+ }
353
+ var SERVER_PASSWORD_SERVICE = "relay-ai-server-password";
354
+ var SERVER_PASSWORD_ACCOUNT = "server-password";
355
+ async function getServerPasswordKeyring() {
356
+ try {
357
+ const { Entry } = await import("@napi-rs/keyring");
358
+ return new Entry(SERVER_PASSWORD_SERVICE, SERVER_PASSWORD_ACCOUNT);
359
+ } catch {
360
+ return null;
361
+ }
362
+ }
363
+ async function getSavedServerPassword() {
364
+ const config = readConfig();
365
+ if (config.server?.savedPassword) {
366
+ const pwd = config.server.savedPassword;
367
+ const keyring2 = await getServerPasswordKeyring();
368
+ if (keyring2) {
369
+ try {
370
+ await keyring2.setPassword(pwd);
371
+ delete config.server.savedPassword;
372
+ if (Object.keys(config.server).length === 0) delete config.server;
373
+ writeConfig(config);
374
+ } catch {
375
+ }
376
+ }
377
+ return pwd;
378
+ }
379
+ const keyring = await getServerPasswordKeyring();
380
+ if (keyring) {
381
+ try {
382
+ return await keyring.getPassword();
383
+ } catch {
384
+ return null;
385
+ }
386
+ }
387
+ return null;
388
+ }
389
+ function getEnvServerPassword() {
390
+ const value = process.env["RELAY_AI_SERVER_PASSWORD"]?.trim();
391
+ return value || null;
392
+ }
393
+ async function setSavedServerPassword(password) {
394
+ const keyring = await getServerPasswordKeyring();
395
+ if (keyring) {
396
+ try {
397
+ await keyring.setPassword(password);
398
+ return;
399
+ } catch {
400
+ }
401
+ }
402
+ const config = readConfig();
403
+ config.server = {
404
+ ...config.server ?? {},
405
+ savedPassword: password
406
+ };
407
+ writeConfig(config);
408
+ }
409
+ function getServerExposedProviders() {
410
+ const list = readConfig().server?.exposedProviders;
411
+ return list && list.length > 0 ? list : null;
412
+ }
413
+ function setServerExposedProviders(providerIds) {
414
+ const config = readConfig();
415
+ config.server = {
416
+ ...config.server ?? {},
417
+ exposedProviders: providerIds
418
+ };
419
+ writeConfig(config);
420
+ }
421
+ function getServerMaskGatewayIds() {
422
+ return readConfig().server?.maskGatewayIds ?? true;
423
+ }
424
+ function setServerMaskGatewayIds(mask) {
425
+ const config = readConfig();
426
+ config.server = {
427
+ ...config.server ?? {},
428
+ maskGatewayIds: mask
429
+ };
430
+ writeConfig(config);
431
+ }
432
+ function getServerFavoritesOnly() {
433
+ return readConfig().server?.favoritesOnly ?? false;
434
+ }
435
+ function setServerFavoritesOnly(favoritesOnly) {
436
+ const config = readConfig();
437
+ config.server = {
438
+ ...config.server ?? {},
439
+ favoritesOnly
440
+ };
441
+ writeConfig(config);
442
+ }
443
+ function getServerFreeModelsOnly() {
444
+ return readConfig().server?.freeModelsOnly ?? false;
445
+ }
446
+ function setServerFreeModelsOnly(freeModelsOnly) {
447
+ const config = readConfig();
448
+ config.server = {
449
+ ...config.server ?? {},
450
+ freeModelsOnly
451
+ };
452
+ writeConfig(config);
453
+ }
454
+ function getServerListenMode() {
455
+ return readConfig().server?.listenMode === "network" ? "network" : "local";
456
+ }
457
+ function setServerListenMode(listenMode) {
458
+ const config = readConfig();
459
+ config.server = {
460
+ ...config.server ?? {},
461
+ listenMode
462
+ };
463
+ writeConfig(config);
464
+ }
465
+ function getServerAutostart() {
466
+ return readConfig().server?.autostart ?? false;
467
+ }
468
+ function setServerAutostart(autostart) {
469
+ const config = readConfig();
470
+ config.server = {
471
+ ...config.server ?? {},
472
+ autostart
473
+ };
474
+ writeConfig(config);
475
+ }
476
+ function resolveServerAutostart(env = process.env) {
477
+ const envVal = env["RELAY_AI_SERVER_AUTOSTART"]?.trim().toLowerCase();
478
+ if (envVal !== void 0 && envVal !== "") {
479
+ return ["1", "true", "yes", "on"].includes(envVal);
480
+ }
481
+ return getServerAutostart();
482
+ }
483
+
222
484
  // src/context-window.ts
223
- import { readFileSync } from "fs";
485
+ import { readFileSync as readFileSync2 } from "fs";
224
486
  var DEFAULT_CONTEXT_WINDOW = 2e5;
225
487
  var CACHE_PROVIDER_PRIORITY = /* @__PURE__ */ new Set(["opencode", "opencode-go"]);
226
488
  var HEURISTIC_RULES = [
@@ -258,7 +520,7 @@ var heuristicCache = /* @__PURE__ */ new Map();
258
520
  function loadOpencodeCache() {
259
521
  if (parsedCache === void 0) {
260
522
  try {
261
- parsedCache = JSON.parse(readFileSync(OPENCODE_CACHE_PATH, "utf8"));
523
+ parsedCache = JSON.parse(readFileSync2(OPENCODE_CACHE_PATH, "utf8"));
262
524
  } catch {
263
525
  parsedCache = null;
264
526
  }
@@ -297,8 +559,8 @@ function getCacheIndex() {
297
559
  return cacheIndex;
298
560
  }
299
561
  function contextWindowFromHeuristics(modelId) {
300
- const cached = heuristicCache.get(modelId);
301
- if (cached !== void 0) return cached;
562
+ const cached2 = heuristicCache.get(modelId);
563
+ if (cached2 !== void 0) return cached2;
302
564
  for (const [pattern, size] of HEURISTIC_RULES) {
303
565
  if (pattern.test(modelId)) {
304
566
  heuristicCache.set(modelId, size);
@@ -345,7 +607,7 @@ function routeLookupIds(id) {
345
607
 
346
608
  // src/oauth/antigravity-oauth.ts
347
609
  import open from "open";
348
- import { readFileSync as readFileSync2 } from "fs";
610
+ import { readFileSync as readFileSync3 } from "fs";
349
611
  import { homedir as homedir3 } from "os";
350
612
  import { join as pathJoin } from "path";
351
613
 
@@ -634,7 +896,7 @@ async function onboardUser(accessToken, tierId, maxAttempts = 10) {
634
896
  function readAgyProjectId() {
635
897
  try {
636
898
  const cache = pathJoin(homedir3(), ".gemini", "antigravity-cli", "cache", "projects.json");
637
- const data = JSON.parse(readFileSync2(cache, "utf8"));
899
+ const data = JSON.parse(readFileSync3(cache, "utf8"));
638
900
  return data[homedir3()] ?? Object.values(data)[0] ?? "";
639
901
  } catch {
640
902
  return "";
@@ -680,15 +942,15 @@ async function completeAntigravityExchange(code, codeVerifier, redirectUri) {
680
942
  }
681
943
 
682
944
  // src/registry/opencode-auth.ts
683
- import { existsSync, readFileSync as readFileSync3, statSync } from "fs";
945
+ import { existsSync as existsSync2, readFileSync as readFileSync4, statSync } from "fs";
684
946
  import { homedir as homedir4 } from "os";
685
- import { join as join3 } from "path";
947
+ import { join as join4 } from "path";
686
948
  function resolveOpencodeAuthPath(env = process.env) {
687
- const dataHome = env["XDG_DATA_HOME"] ?? join3(homedir4(), ".local", "share");
949
+ const dataHome = env["XDG_DATA_HOME"] ?? join4(homedir4(), ".local", "share");
688
950
  if (process.platform === "win32") {
689
- return join3(env["APPDATA"] ?? join3(homedir4(), "AppData", "Roaming"), "opencode", "auth.json");
951
+ return join4(env["APPDATA"] ?? join4(homedir4(), "AppData", "Roaming"), "opencode", "auth.json");
690
952
  }
691
- return join3(dataHome, "opencode", "auth.json");
953
+ return join4(dataHome, "opencode", "auth.json");
692
954
  }
693
955
  function decodeAuthEntry(value) {
694
956
  if (typeof value === "string" && value.trim()) return value.trim();
@@ -718,7 +980,7 @@ function decodeAuthEntry(value) {
718
980
  return null;
719
981
  }
720
982
  function authFilePermissionWarning(path) {
721
- if (!existsSync(path)) return void 0;
983
+ if (!existsSync2(path)) return void 0;
722
984
  if (process.platform === "win32") return void 0;
723
985
  try {
724
986
  const mode = statSync(path).mode & 511;
@@ -731,10 +993,10 @@ function authFilePermissionWarning(path) {
731
993
  }
732
994
  function readOpencodeAuthFile(env = process.env) {
733
995
  const path = resolveOpencodeAuthPath(env);
734
- if (!existsSync(path)) return null;
996
+ if (!existsSync2(path)) return null;
735
997
  let parsed;
736
998
  try {
737
- parsed = JSON.parse(readFileSync3(path, "utf8"));
999
+ parsed = JSON.parse(readFileSync4(path, "utf8"));
738
1000
  } catch {
739
1001
  return { path, entries: {}, permissionWarning: authFilePermissionWarning(path) };
740
1002
  }
@@ -1539,7 +1801,7 @@ async function refreshStoredOAuthCredential(providerId, cred) {
1539
1801
  }
1540
1802
 
1541
1803
  // src/secrets-file.ts
1542
- import { chmodSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
1804
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
1543
1805
  var DIR_MODE = 448;
1544
1806
  var FILE_MODE = 384;
1545
1807
  function emptySecrets() {
@@ -1547,9 +1809,9 @@ function emptySecrets() {
1547
1809
  }
1548
1810
  function readSecretsFile(env = process.env) {
1549
1811
  const path = getSecretsPath(env);
1550
- if (!existsSync2(path)) return emptySecrets();
1812
+ if (!existsSync3(path)) return emptySecrets();
1551
1813
  try {
1552
- const raw = JSON.parse(readFileSync4(path, "utf8"));
1814
+ const raw = JSON.parse(readFileSync5(path, "utf8"));
1553
1815
  if (raw?.version !== 1 || !raw.accounts || typeof raw.accounts !== "object") {
1554
1816
  return emptySecrets();
1555
1817
  }
@@ -1564,13 +1826,13 @@ function readSecretsFile(env = process.env) {
1564
1826
  }
1565
1827
  function writeSecretsFile(data, env = process.env) {
1566
1828
  const home = getAppHome(env);
1567
- mkdirSync(home, { recursive: true, mode: DIR_MODE });
1829
+ mkdirSync2(home, { recursive: true, mode: DIR_MODE });
1568
1830
  try {
1569
1831
  chmodSync(home, DIR_MODE);
1570
1832
  } catch {
1571
1833
  }
1572
1834
  const path = getSecretsPath(env);
1573
- writeFileSync(path, `${JSON.stringify(data, null, 2)}
1835
+ writeFileSync2(path, `${JSON.stringify(data, null, 2)}
1574
1836
  `, { encoding: "utf8", mode: FILE_MODE });
1575
1837
  try {
1576
1838
  chmodSync(path, FILE_MODE);
@@ -2021,16 +2283,16 @@ function parseManualModel(raw) {
2021
2283
  // src/registry/io.ts
2022
2284
  import {
2023
2285
  chmodSync as chmodSync2,
2024
- copyFileSync,
2025
- existsSync as existsSync3,
2026
- mkdirSync as mkdirSync2,
2286
+ copyFileSync as copyFileSync2,
2287
+ existsSync as existsSync4,
2288
+ mkdirSync as mkdirSync3,
2027
2289
  openSync,
2028
- readFileSync as readFileSync5,
2029
- renameSync,
2290
+ readFileSync as readFileSync6,
2291
+ renameSync as renameSync2,
2030
2292
  writeSync,
2031
2293
  closeSync
2032
2294
  } from "fs";
2033
- import { dirname } from "path";
2295
+ import { dirname as dirname2 } from "path";
2034
2296
 
2035
2297
  // src/registry/types.ts
2036
2298
  var REGISTRY_SCHEMA_VERSION = 1;
@@ -2121,7 +2383,7 @@ var DIR_MODE2 = 448;
2121
2383
  var FILE_MODE2 = 384;
2122
2384
  function ensureSecureAppHome() {
2123
2385
  const home = getAppHome();
2124
- mkdirSync2(home, { recursive: true, mode: DIR_MODE2 });
2386
+ mkdirSync3(home, { recursive: true, mode: DIR_MODE2 });
2125
2387
  try {
2126
2388
  chmodSync2(home, DIR_MODE2);
2127
2389
  } catch {
@@ -2129,7 +2391,7 @@ function ensureSecureAppHome() {
2129
2391
  }
2130
2392
  function writeSecureFile(path, content) {
2131
2393
  ensureSecureAppHome();
2132
- mkdirSync2(dirname(path), { recursive: true, mode: DIR_MODE2 });
2394
+ mkdirSync3(dirname2(path), { recursive: true, mode: DIR_MODE2 });
2133
2395
  const fd = openSync(path, "w", FILE_MODE2);
2134
2396
  try {
2135
2397
  writeSync(fd, content);
@@ -2205,11 +2467,11 @@ function parseRegistry(raw) {
2205
2467
  return registry;
2206
2468
  }
2207
2469
  function loadRegistry(path = getProvidersPath(), { persist = true } = {}) {
2208
- if (!existsSync3(path)) {
2470
+ if (!existsSync4(path)) {
2209
2471
  return { schemaVersion: REGISTRY_SCHEMA_VERSION, providers: [] };
2210
2472
  }
2211
2473
  try {
2212
- const raw = JSON.parse(readFileSync5(path, "utf8"));
2474
+ const raw = JSON.parse(readFileSync6(path, "utf8"));
2213
2475
  const registry = parseRegistry(raw);
2214
2476
  let migrated = migrateLegacyCloudProviders(registry);
2215
2477
  if (migrateOAuthOpenAiProvider(registry)) migrated = true;
@@ -2230,15 +2492,15 @@ function saveRegistry(registry, path = getProvidersPath()) {
2230
2492
  const payload = `${JSON.stringify(registry, null, 2)}
2231
2493
  `;
2232
2494
  const backup = `${path}.bak`;
2233
- if (existsSync3(path)) {
2495
+ if (existsSync4(path)) {
2234
2496
  try {
2235
- copyFileSync(path, backup);
2497
+ copyFileSync2(path, backup);
2236
2498
  } catch {
2237
2499
  }
2238
2500
  }
2239
2501
  const tmp = `${path}.tmp`;
2240
2502
  writeSecureFile(tmp, payload);
2241
- renameSync(tmp, path);
2503
+ renameSync2(tmp, path);
2242
2504
  }
2243
2505
 
2244
2506
  // src/registry/url-security.ts
@@ -2353,6 +2615,81 @@ async function validateCustomEndpointUrl(rawUrl, opts = {}) {
2353
2615
  return { ok: true, normalizedUrl };
2354
2616
  }
2355
2617
 
2618
+ // src/codex/process.ts
2619
+ import spawn from "cross-spawn";
2620
+ function commandError(binaryPath, args, stdout, stderr, detail) {
2621
+ const error = new Error(`Command failed: ${binaryPath} ${args.join(" ")} (${detail})`);
2622
+ return Object.assign(error, { stdout, stderr });
2623
+ }
2624
+ function runCodexCommandSync(binaryPath, args, options = {}) {
2625
+ const result = spawn.sync(binaryPath, args, {
2626
+ encoding: "utf8",
2627
+ stdio: ["ignore", "pipe", "pipe"],
2628
+ env: options.env,
2629
+ timeout: options.timeout,
2630
+ maxBuffer: options.maxBuffer
2631
+ });
2632
+ const stdout = result.stdout ?? "";
2633
+ const stderr = result.stderr ?? "";
2634
+ if (result.error) {
2635
+ throw Object.assign(result.error, { stdout, stderr });
2636
+ }
2637
+ if (result.status !== 0) {
2638
+ throw commandError(binaryPath, args, stdout, stderr, `exit ${result.status ?? result.signal ?? "unknown"}`);
2639
+ }
2640
+ return { stdout, stderr };
2641
+ }
2642
+ function runCodexCommand(binaryPath, args, options = {}) {
2643
+ return new Promise((resolve, reject) => {
2644
+ const child = spawn(binaryPath, args, {
2645
+ stdio: ["ignore", "pipe", "pipe"],
2646
+ env: options.env
2647
+ });
2648
+ const stdoutChunks = [];
2649
+ const stderrChunks = [];
2650
+ let outputBytes = 0;
2651
+ let settled = false;
2652
+ const finishError = (error) => {
2653
+ if (settled) return;
2654
+ settled = true;
2655
+ reject(Object.assign(error, {
2656
+ stdout: Buffer.concat(stdoutChunks).toString("utf8"),
2657
+ stderr: Buffer.concat(stderrChunks).toString("utf8")
2658
+ }));
2659
+ };
2660
+ const collect = (target, chunk) => {
2661
+ target.push(chunk);
2662
+ outputBytes += chunk.length;
2663
+ if (options.maxBuffer !== void 0 && outputBytes > options.maxBuffer) {
2664
+ child.kill();
2665
+ finishError(commandError(binaryPath, args, "", "", `output exceeded ${options.maxBuffer} bytes`));
2666
+ }
2667
+ };
2668
+ child.stdout?.on("data", (chunk) => collect(stdoutChunks, chunk));
2669
+ child.stderr?.on("data", (chunk) => collect(stderrChunks, chunk));
2670
+ child.on("error", finishError);
2671
+ let timer;
2672
+ if (options.timeout !== void 0) {
2673
+ timer = setTimeout(() => {
2674
+ child.kill();
2675
+ finishError(commandError(binaryPath, args, "", "", `timed out after ${options.timeout}ms`));
2676
+ }, options.timeout);
2677
+ }
2678
+ child.on("close", (code, signal) => {
2679
+ if (timer) clearTimeout(timer);
2680
+ if (settled) return;
2681
+ const stdout = Buffer.concat(stdoutChunks).toString("utf8");
2682
+ const stderr = Buffer.concat(stderrChunks).toString("utf8");
2683
+ if (code !== 0) {
2684
+ finishError(commandError(binaryPath, args, stdout, stderr, `exit ${code ?? signal ?? "unknown"}`));
2685
+ return;
2686
+ }
2687
+ settled = true;
2688
+ resolve({ stdout, stderr });
2689
+ });
2690
+ });
2691
+ }
2692
+
2356
2693
  // src/oauth/claude-identity.ts
2357
2694
  import { createHash, randomUUID } from "crypto";
2358
2695
  var CLAUDE_CODE_CLI_VERSION2 = "2.1.195";
@@ -2470,6 +2807,125 @@ function injectClaudeIdentity(body, providerData, seed) {
2470
2807
  // src/provider-factory.ts
2471
2808
  import { wrapLanguageModel, extractReasoningMiddleware } from "ai";
2472
2809
 
2810
+ // src/codex/version.ts
2811
+ import { execSync } from "child_process";
2812
+ import { existsSync as existsSync5 } from "fs";
2813
+ import { homedir as homedir5 } from "os";
2814
+ import { join as join5 } from "path";
2815
+ var isWindows = process.platform === "win32";
2816
+ var CODEX_NPM_LATEST_URL = "https://registry.npmjs.org/@openai/codex/latest";
2817
+ var CODEX_VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2818
+ var NPM_FETCH_TIMEOUT_MS = 3e3;
2819
+ var cached = null;
2820
+ var inflight = null;
2821
+ function parseCodexVersion(input) {
2822
+ const match = input.match(/(\d+)\.(\d+)\.(\d+)/);
2823
+ return match ? `${match[1]}.${match[2]}.${match[3]}` : null;
2824
+ }
2825
+ function versionTuple(version) {
2826
+ const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
2827
+ if (!match) return null;
2828
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
2829
+ }
2830
+ function maxCodexVersion(candidates) {
2831
+ let best = null;
2832
+ let bestTuple = null;
2833
+ for (const candidate of candidates) {
2834
+ if (!candidate) continue;
2835
+ const tuple = versionTuple(candidate);
2836
+ if (!tuple) continue;
2837
+ const greater = !bestTuple || (tuple[0] !== bestTuple[0] ? tuple[0] > bestTuple[0] : tuple[1] !== bestTuple[1] ? tuple[1] > bestTuple[1] : tuple[2] > bestTuple[2]);
2838
+ if (greater) {
2839
+ best = candidate;
2840
+ bestTuple = tuple;
2841
+ }
2842
+ }
2843
+ return best ?? candidates.find((c) => !!c) ?? CODEX_RESPONSES_LITE_VERSION;
2844
+ }
2845
+ async function fetchNpmCodexVersion(fetchImpl = fetch, timeoutMs = NPM_FETCH_TIMEOUT_MS) {
2846
+ try {
2847
+ const controller = new AbortController();
2848
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2849
+ try {
2850
+ const res = await fetchImpl(CODEX_NPM_LATEST_URL, {
2851
+ headers: { Accept: "application/json" },
2852
+ signal: controller.signal
2853
+ });
2854
+ if (!res.ok) return null;
2855
+ const body = await res.json();
2856
+ return typeof body.version === "string" ? parseCodexVersion(body.version) : null;
2857
+ } finally {
2858
+ clearTimeout(timer);
2859
+ }
2860
+ } catch {
2861
+ return null;
2862
+ }
2863
+ }
2864
+ var CODEX_VERSION_FALLBACK_PATHS = isWindows ? [
2865
+ join5(process.env["APPDATA"] ?? homedir5(), "npm", "codex.cmd"),
2866
+ join5(process.env["APPDATA"] ?? homedir5(), "npm", "codex")
2867
+ ] : [
2868
+ join5(homedir5(), ".local", "bin", "codex"),
2869
+ join5(homedir5(), ".npm", "bin", "codex"),
2870
+ "/usr/local/bin/codex",
2871
+ "/opt/homebrew/bin/codex"
2872
+ ];
2873
+ function codexBinaryCandidates() {
2874
+ const override = getAppPathOverride("codex");
2875
+ if (override) return [override];
2876
+ const candidates = [];
2877
+ try {
2878
+ const result = execSync(isWindows ? "where.exe codex" : "which codex", {
2879
+ encoding: "utf8",
2880
+ stdio: ["pipe", "pipe", "pipe"]
2881
+ });
2882
+ candidates.push(...result.trim().split("\n").map((l) => l.trim()).filter(Boolean));
2883
+ } catch {
2884
+ }
2885
+ candidates.push(...CODEX_VERSION_FALLBACK_PATHS);
2886
+ return [...new Set(candidates)];
2887
+ }
2888
+ function installedCodexVersion() {
2889
+ for (const binary of codexBinaryCandidates()) {
2890
+ try {
2891
+ if (!existsSync5(binary)) continue;
2892
+ const { stdout } = runCodexCommandSync(binary, ["--version"], { timeout: 5e3 });
2893
+ const parsed = parseCodexVersion(stdout);
2894
+ if (parsed) return parsed;
2895
+ } catch {
2896
+ }
2897
+ }
2898
+ return null;
2899
+ }
2900
+ async function resolveCodexClientVersion(opts = {}) {
2901
+ const override = process.env["RELAY_AI_CODEX_VERSION"]?.trim();
2902
+ if (override) return parseCodexVersion(override) ?? CODEX_RESPONSES_LITE_VERSION;
2903
+ if (process.env["VITEST"] || process.env["VITEST_WORKER_ID"]) {
2904
+ return cached?.version ?? CODEX_RESPONSES_LITE_VERSION;
2905
+ }
2906
+ const now = Date.now();
2907
+ if (!opts.forceRefresh && cached && now - cached.fetchedAt < CODEX_VERSION_CACHE_TTL_MS) {
2908
+ return cached.version;
2909
+ }
2910
+ if (inflight && !opts.forceRefresh) return inflight;
2911
+ inflight = (async () => {
2912
+ try {
2913
+ const [npm, local] = await Promise.all([
2914
+ fetchNpmCodexVersion(opts.fetchImpl),
2915
+ Promise.resolve().then(() => installedCodexVersion())
2916
+ ]);
2917
+ const best = maxCodexVersion([npm, local, CODEX_RESPONSES_LITE_VERSION]);
2918
+ cached = { version: best, fetchedAt: Date.now() };
2919
+ return best;
2920
+ } catch {
2921
+ return cached?.version ?? CODEX_RESPONSES_LITE_VERSION;
2922
+ } finally {
2923
+ inflight = null;
2924
+ }
2925
+ })();
2926
+ return inflight;
2927
+ }
2928
+
2473
2929
  // src/oauth/responses-websocket.ts
2474
2930
  var RESPONSES_LITE_HEADER = "x-openai-internal-codex-responses-lite";
2475
2931
  var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set(["response.completed", "response.failed", "response.incomplete", "error"]);
@@ -2946,8 +3402,8 @@ function createResponsesWebSocketFetch(wsUrl, log) {
2946
3402
 
2947
3403
  // src/gateway-protocol.ts
2948
3404
  import { createHash as createHash2 } from "crypto";
2949
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
2950
- import { dirname as dirname2, join as join4 } from "path";
3405
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
3406
+ import { dirname as dirname3, join as join6 } from "path";
2951
3407
  var DUAL_PROVIDER_IDS = /* @__PURE__ */ new Set([
2952
3408
  "zen",
2953
3409
  "go",
@@ -3047,7 +3503,7 @@ var PREFERENCE_TTL_MS = 24 * 60 * 60 * 1e3;
3047
3503
  var FAILURE_COOLDOWN_MS = 5 * 60 * 1e3;
3048
3504
  var loadedPersistentPath = null;
3049
3505
  function persistentPath() {
3050
- return join4(getAppHome(), "protocol-cache.json");
3506
+ return join6(getAppHome(), "protocol-cache.json");
3051
3507
  }
3052
3508
  function loadPersistent(now = Date.now()) {
3053
3509
  const path = persistentPath();
@@ -3055,8 +3511,8 @@ function loadPersistent(now = Date.now()) {
3055
3511
  remembered.clear();
3056
3512
  loadedPersistentPath = path;
3057
3513
  try {
3058
- if (!existsSync4(path)) return;
3059
- const raw = JSON.parse(readFileSync6(path, "utf8"));
3514
+ if (!existsSync6(path)) return;
3515
+ const raw = JSON.parse(readFileSync7(path, "utf8"));
3060
3516
  for (const [key, entry] of Object.entries(raw)) {
3061
3517
  if (!entry || entry.protocol !== void 0 && entry.protocol !== "openai" && entry.protocol !== "anthropic") continue;
3062
3518
  if (typeof entry.expiresAt !== "number" || entry.expiresAt <= now) continue;
@@ -3069,16 +3525,16 @@ function loadPersistent(now = Date.now()) {
3069
3525
  function savePersistent() {
3070
3526
  const path = persistentPath();
3071
3527
  try {
3072
- mkdirSync3(dirname2(path), { recursive: true, mode: 448 });
3528
+ mkdirSync4(dirname3(path), { recursive: true, mode: 448 });
3073
3529
  const live = {};
3074
3530
  const now = Date.now();
3075
3531
  for (const [key, entry] of remembered) {
3076
3532
  if (entry.expiresAt > now) live[key] = entry;
3077
3533
  }
3078
3534
  const tempPath = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
3079
- writeFileSync2(tempPath, `${JSON.stringify(live)}
3535
+ writeFileSync3(tempPath, `${JSON.stringify(live)}
3080
3536
  `, { mode: 384 });
3081
- renameSync2(tempPath, path);
3537
+ renameSync3(tempPath, path);
3082
3538
  } catch {
3083
3539
  }
3084
3540
  }
@@ -3203,9 +3659,9 @@ function findCreateFactory(mod) {
3203
3659
  throw new Error("No create* factory export found in provider package");
3204
3660
  }
3205
3661
  async function loadSdkProviderFactory(npm) {
3206
- let cached = factoryCache.get(npm);
3207
- if (!cached) {
3208
- cached = (async () => {
3662
+ let cached2 = factoryCache.get(npm);
3663
+ if (!cached2) {
3664
+ cached2 = (async () => {
3209
3665
  try {
3210
3666
  const mod = await import(npm);
3211
3667
  return findCreateFactory(mod);
@@ -3217,10 +3673,10 @@ async function loadSdkProviderFactory(npm) {
3217
3673
  throw err;
3218
3674
  }
3219
3675
  })();
3220
- factoryCache.set(npm, cached);
3221
- cached.catch(() => factoryCache.delete(npm));
3676
+ factoryCache.set(npm, cached2);
3677
+ cached2.catch(() => factoryCache.delete(npm));
3222
3678
  }
3223
- return cached;
3679
+ return cached2;
3224
3680
  }
3225
3681
  async function createLanguageModelSingle(spec) {
3226
3682
  const npm = resolveProviderNpm(spec.npm);
@@ -3246,8 +3702,10 @@ async function createLanguageModelSingle(spec) {
3246
3702
  ...accountId ? { "ChatGPT-Account-Id": accountId } : {},
3247
3703
  originator: "relay-ai",
3248
3704
  // Responses-Lite models (backend prefer_websockets/use_responses_lite,
3249
- // e.g. gpt-5.6-luna) require these on the request.
3250
- ...spec.useResponsesLite ? { version: CODEX_RESPONSES_LITE_VERSION, "x-openai-internal-codex-responses-lite": "true" } : {}
3705
+ // e.g. gpt-5.6-luna) require these on the request. The version is
3706
+ // resolved at runtime (npm latest → installed CLI → bundled
3707
+ // fallback) so new model gates don't need a Relay release.
3708
+ ...spec.useResponsesLite ? { version: await resolveCodexClientVersion(), "x-openai-internal-codex-responses-lite": "true" } : {}
3251
3709
  },
3252
3710
  // Models the backend flags with prefer_websockets are only served over
3253
3711
  // the WebSocket Responses transport, not HTTP.
@@ -3668,14 +4126,14 @@ function xaiDefaultReasoningEffort(modelId) {
3668
4126
  if (lower === "grok-4.5" || lower.startsWith("grok-4.5-")) return "high";
3669
4127
  return "low";
3670
4128
  }
3671
- var DEEPSEEK_V4_REASONING_ID = /^deepseek-v4(?:\.\d+)?-(?:flash|pro)(?:-|$)/;
4129
+ var DEEPSEEK_V4_REASONING_ID = /^(?:[a-z0-9-]+\/)?deepseek-v4(?:\.\d+)?-(?:flash|pro)(?:-|$)/;
3672
4130
  function isDeepSeekReasoningModel(modelId) {
3673
4131
  const lower = modelId.toLowerCase();
3674
4132
  return DEEPSEEK_V4_REASONING_ID.test(lower) || lower === "deepseek-reasoner" || lower === "deepseek-chat";
3675
4133
  }
4134
+ var KIMI_REASONING_ID = /^(?:[a-z0-9-]+\/)?kimi-/;
3676
4135
  function isKimiReasoningModel(modelId) {
3677
- const lower = modelId.toLowerCase();
3678
- return lower.startsWith("kimi-");
4136
+ return KIMI_REASONING_ID.test(modelId.toLowerCase().trim());
3679
4137
  }
3680
4138
  function isGlm52ReasoningModel(modelId) {
3681
4139
  const lower = modelId.toLowerCase();
@@ -3725,10 +4183,11 @@ function openRouterReasoningCapabilities(metadata) {
3725
4183
  }
3726
4184
  function deepSeekAcceptsNativeEfforts(metadata) {
3727
4185
  const providerId = metadata?.providerId?.toLowerCase();
3728
- if (providerId === "go" || providerId === "zen" || providerId === "opencode-go" || providerId === "opencode") {
4186
+ if (providerId === "go" || providerId === "zen" || providerId === "opencode-go" || providerId === "opencode" || providerId === "commandcode") {
3729
4187
  return true;
3730
4188
  }
3731
- return metadata?.apiBaseUrl?.includes("opencode.ai") === true;
4189
+ const baseUrl = metadata?.apiBaseUrl;
4190
+ return baseUrl?.includes("opencode.ai") === true || baseUrl?.includes("commandcode.ai") === true;
3732
4191
  }
3733
4192
  function mapCodexEffortToDeepSeek(effort, nativeEfforts) {
3734
4193
  switch (effort) {
@@ -4724,9 +5183,54 @@ function collapseSchemaUnionTypes(value) {
4724
5183
  }
4725
5184
  return out;
4726
5185
  }
5186
+ function isDigit(ch) {
5187
+ return ch !== void 0 && ch >= "0" && ch <= "9";
5188
+ }
5189
+ function rewriteNulEscapesInPattern(pattern) {
5190
+ let out = "";
5191
+ let escaped = false;
5192
+ let changed = false;
5193
+ for (let i = 0; i < pattern.length; i += 1) {
5194
+ const ch = pattern[i];
5195
+ if (escaped) {
5196
+ escaped = false;
5197
+ if (ch === "0" && !isDigit(pattern[i + 1])) {
5198
+ out += "x00";
5199
+ changed = true;
5200
+ } else {
5201
+ out += ch;
5202
+ }
5203
+ continue;
5204
+ }
5205
+ if (ch === "\\") escaped = true;
5206
+ out += ch;
5207
+ }
5208
+ return changed ? out : null;
5209
+ }
5210
+ function rewriteNulPatternEscapes(value) {
5211
+ if (Array.isArray(value)) {
5212
+ let changed2 = false;
5213
+ const out2 = value.map((entry) => {
5214
+ const next = rewriteNulPatternEscapes(entry);
5215
+ if (next !== entry) changed2 = true;
5216
+ return next;
5217
+ });
5218
+ return changed2 ? out2 : value;
5219
+ }
5220
+ if (!value || typeof value !== "object") return value;
5221
+ let changed = false;
5222
+ const out = {};
5223
+ for (const [key, child] of Object.entries(value)) {
5224
+ const next = key === "pattern" && typeof child === "string" ? rewriteNulEscapesInPattern(child) ?? child : rewriteNulPatternEscapes(child);
5225
+ if (next !== child) changed = true;
5226
+ out[key] = next;
5227
+ }
5228
+ return changed ? out : value;
5229
+ }
4727
5230
  function normalizeToolSchemaForNpm(schema, npm) {
4728
- if (!npm || !GOOGLE_NPM.has(npm)) return schema;
4729
- return collapseSchemaUnionTypes(schema);
5231
+ const portable = rewriteNulPatternEscapes(schema);
5232
+ if (!npm || !GOOGLE_NPM.has(npm)) return portable;
5233
+ return collapseSchemaUnionTypes(portable);
4730
5234
  }
4731
5235
 
4732
5236
  // src/sdk-adapter.ts
@@ -5211,6 +5715,35 @@ export {
5211
5715
  VERTEX_ANTHROPIC_NPM,
5212
5716
  classifyModelFormat,
5213
5717
  VERSION,
5718
+ getAppHome,
5719
+ getConfigPath,
5720
+ getProvidersPath,
5721
+ getLogsPath,
5722
+ getVertexModelsPath,
5723
+ loadPreferences,
5724
+ savePreferences,
5725
+ getAppPathOverride,
5726
+ setAppPathOverride,
5727
+ recordLaunchFolder,
5728
+ recordLaunchSelection,
5729
+ getSavedServerPassword,
5730
+ getEnvServerPassword,
5731
+ setSavedServerPassword,
5732
+ getServerExposedProviders,
5733
+ setServerExposedProviders,
5734
+ getServerMaskGatewayIds,
5735
+ setServerMaskGatewayIds,
5736
+ getServerFavoritesOnly,
5737
+ setServerFavoritesOnly,
5738
+ getServerFreeModelsOnly,
5739
+ setServerFreeModelsOnly,
5740
+ getServerListenMode,
5741
+ setServerListenMode,
5742
+ setServerAutostart,
5743
+ resolveServerAutostart,
5744
+ runCodexCommandSync,
5745
+ runCodexCommand,
5746
+ resolveCodexClientVersion,
5214
5747
  requestOpenAiDeviceCode,
5215
5748
  openAiDeviceCodeUrl,
5216
5749
  pollOpenAiDeviceCodeToken,
@@ -5224,13 +5757,6 @@ export {
5224
5757
  CLINE_PASS_CATALOG_URL,
5225
5758
  CLINE_PASS_VALIDATION_URL,
5226
5759
  CLINE_PASS_LEGACY_DEFAULT_CONTEXT_WINDOW,
5227
- getAppHome,
5228
- getLegacyAppHome,
5229
- getConfigPath,
5230
- getProvidersPath,
5231
- getLogsPath,
5232
- getVertexModelsPath,
5233
- getLegacyConfPath,
5234
5760
  modelPrefersResponsesApi,
5235
5761
  isSdkMigratedNpm,
5236
5762
  maxToolsForNpm,
@@ -5321,4 +5847,4 @@ export {
5321
5847
  streamAnthropicResponse,
5322
5848
  generateAnthropicResponse
5323
5849
  };
5324
- //# sourceMappingURL=chunk-3KCKQRRR.js.map
5850
+ //# sourceMappingURL=chunk-FRTO6LVI.js.map