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