@jacobbd/relay-ai 0.11.0 → 0.12.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.
package/dist/cli.js CHANGED
@@ -1,20 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- ANTIGRAVITY_BASE_URLS,
4
- BACKENDS,
3
+ addManualModel,
4
+ removeManualModel
5
+ } from "./chunk-FD5EDSBM.js";
6
+ import {
5
7
  CODEX_APP_AUTO_COMPACT_RATIO,
6
8
  CODEX_APP_PROVIDER_ID,
7
- CODEX_RESPONSES_LITE_VERSION,
8
- CODEX_RESPONSES_LITE_WS_URL,
9
- CODEX_RESPONSES_WEBSOCKETS_BETA,
10
- CODEX_SUBAGENT_MODEL_CAP,
11
- CONFLICTING_ENV_VARS,
12
- GLOBAL_OPENCODE_KEYRING_ACCOUNT,
13
- MAX_MODEL_CATALOG,
14
9
  OPENCODE_SESSION_HEADER,
15
10
  PREVIEW_PROXY_PORT,
16
- VERSION,
17
- VERTEX_ANTHROPIC_NPM,
18
11
  addCustomEndpointProvider,
19
12
  addFavorite,
20
13
  addOpencodeCloudFromApiKey,
@@ -24,13 +17,10 @@ import {
24
17
  anthropicModelsEndpoint,
25
18
  appendCodexBodyDump,
26
19
  authenticateProvider,
27
- buildAntigravityChildEnv,
28
20
  buildAntigravityRoutes,
29
21
  buildAppCatalogFile,
30
22
  buildCatalogFile,
31
23
  buildCatalogRoutes,
32
- buildChildEnv,
33
- buildClaudeCodeBillingSystemLine,
34
24
  buildCodexAppRootConfig,
35
25
  buildHttpProxyRoutes,
36
26
  buildImportProviderList,
@@ -41,19 +31,13 @@ import {
41
31
  catalogEntryFromModel,
42
32
  checkForUpdates,
43
33
  claudeAppSupported,
44
- claudeCodeClientModelId,
45
34
  codexAppInstallHint,
46
35
  codexAppModelSlug,
47
36
  codexAppSupported,
48
37
  confirmLaunchMessage,
49
38
  createGatewayModelCatalog,
50
- createLanguageModel,
51
39
  customEndpointKind,
52
- deepMergeProviderOptions,
53
- detectConflicts,
54
40
  effectiveProviderBaseUrl,
55
- effortProviderOptions,
56
- encodeToolUseId,
57
41
  estimateAnthropicInputTokens,
58
42
  evaluateAgySwitchCompatibility,
59
43
  extractApiKey,
@@ -72,28 +56,20 @@ import {
72
56
  fmtProvider,
73
57
  fmtProviderBracket,
74
58
  fmtUrl,
59
+ forceQuitCodexApp,
75
60
  formatAnthropicModelEntry,
76
61
  formatAnthropicModelList,
77
62
  formatCodexModelLabel,
78
63
  formatRegistryAuthLabel,
79
64
  formatUpdateNotification,
80
- formatUpstreamError,
81
- formatUpstreamErrorTrace,
82
65
  getAntigravityDebugLogPath,
83
- getAppHome,
84
66
  getAppPathOverride,
85
67
  getClaudeDebugLogPath,
86
68
  getCodexProxyDebugLogPath,
87
- getConfigPath,
88
69
  getGeminiProxyDebugLogPath,
89
- getLogsPath,
90
- getProvidersPath,
91
70
  getProxyDebugLogPath,
92
- getReasoningCapabilities,
93
- grabRoundTripSignature,
94
71
  hasApplicationDefaultCredentials,
95
72
  httpProxyModelId,
96
- injectClaudeIdentity,
97
73
  injectRelayModels,
98
74
  isClaudeAppRunning,
99
75
  isCodexAppRunning,
@@ -101,32 +77,22 @@ import {
101
77
  isFreeStatus,
102
78
  isLikelyPlaceholderKey,
103
79
  isOAuthImportProvider,
104
- isSecretServiceAvailable,
105
- isValidProviderId,
106
80
  launchClaude,
107
81
  launchOrRestartClaudeApp,
108
82
  launchOrRestartCodexApp,
109
83
  listCredentialSkippedProviders,
110
84
  loadPreferences,
111
- loadRegistry,
112
85
  logActiveModel,
113
86
  logConnected,
114
87
  logProxy,
115
88
  makeRouteResolver,
116
89
  makeTraceLogger,
117
- maxToolsForNpm,
118
90
  meetsContextFloor,
119
- migrateGlobalOpencodeCredential,
120
- migrateLegacyCloudProviders,
121
91
  modelSelectOption,
122
92
  navOption,
123
93
  oauthAuthRef,
124
- oauthCredentialToKeychainJson,
125
94
  openCodeGoHeaders,
126
95
  parseCodexAppModelSlug,
127
- parseDsmlToolCalls,
128
- parseToolArguments,
129
- preferredRelayCredentialAuthRef,
130
96
  prepareClaudeTraceLog,
131
97
  prepareProviderTraceLog,
132
98
  printApiKeyPanel,
@@ -147,10 +113,6 @@ import {
147
113
  quitClaudeAppGracefully,
148
114
  quitCodexAppGracefully,
149
115
  readBody,
150
- readFromCredentialStore,
151
- readGlobalOpencodeCredential,
152
- readOpencodeAuthFile,
153
- readStoredProviderCredential,
154
116
  recordLaunchSelection,
155
117
  refreshAllProviderModels,
156
118
  refreshModelsDevCacheAsync,
@@ -161,48 +123,32 @@ import {
161
123
  removeProviderFromRegistry,
162
124
  renderMultiAgentV2Feature,
163
125
  resetCodexBodyDumpLog,
164
- resolveApiKey,
165
- resolveContextWindow,
166
126
  resolveLocalProviderApiKey,
167
127
  resolveModelSource,
168
- resolveProviderCredential,
169
128
  resolveProviderTemplate,
170
129
  resolveProvidersForDisplay,
171
130
  resolveRefreshCredential,
172
131
  resolveRelayCatalogSlots,
173
132
  routableModelsForTarget,
174
- routeLookupIds,
175
133
  runCodexCommand,
176
134
  runCodexCommandSync,
177
135
  runServerCommand,
178
136
  savePreferences,
179
- saveProviderCredential,
180
- saveRegistry,
181
- saveToCredentialStore,
182
- selectBetaFlags,
183
137
  sendJson,
184
138
  serializeCatalog,
185
- serializeToolResultContent,
186
139
  shouldHideModel,
187
- silenceSdkWarnings,
188
- splitToolUseId,
189
- sseChunk,
190
140
  startProxy,
191
141
  startProxyCatalog,
192
142
  startServer,
193
143
  supportsClaudeTransparentMode,
194
144
  supportsMultiAgentV2,
195
- supportsNativeOAuth,
196
145
  syntheticTemplate,
197
- thinkingProviderOptions,
198
146
  toggleProviderEnabled,
199
147
  updateCustomEndpointProvider,
200
- upstreamHttpStatus,
201
- validateCustomEndpointUrl,
202
148
  waitForCodexAppQuit,
203
149
  writeSecureLogLine,
204
150
  zenRegistryStub
205
- } from "./chunk-G6QWYYAY.js";
151
+ } from "./chunk-HL3EZ4OU.js";
206
152
  import {
207
153
  filterTemplates,
208
154
  getTemplateById,
@@ -210,11 +156,78 @@ import {
210
156
  listAddableTemplates,
211
157
  listSupportedTemplates,
212
158
  listVisibleOAuthTemplates
213
- } from "./chunk-SFN33VAE.js";
159
+ } from "./chunk-3R25QO5X.js";
160
+ import {
161
+ ANTIGRAVITY_BASE_URLS,
162
+ BACKENDS,
163
+ CODEX_RESPONSES_LITE_VERSION,
164
+ CODEX_RESPONSES_LITE_WS_URL,
165
+ CODEX_RESPONSES_WEBSOCKETS_BETA,
166
+ CODEX_SUBAGENT_MODEL_CAP,
167
+ CONFLICTING_ENV_VARS,
168
+ GLOBAL_OPENCODE_KEYRING_ACCOUNT,
169
+ MAX_MODEL_CATALOG,
170
+ VERSION,
171
+ VERTEX_ANTHROPIC_NPM,
172
+ buildAntigravityChildEnv,
173
+ buildChildEnv,
174
+ buildClaudeCodeBillingSystemLine,
175
+ claudeCodeClientModelId,
176
+ contextWindowError,
177
+ createLanguageModel,
178
+ deepMergeProviderOptions,
179
+ detectConflicts,
180
+ effortProviderOptions,
181
+ encodeToolUseId,
182
+ formatUpstreamError,
183
+ formatUpstreamErrorTrace,
184
+ getAppHome,
185
+ getConfigPath,
186
+ getLogsPath,
187
+ getProviderModels,
188
+ getProvidersPath,
189
+ getReasoningCapabilities,
190
+ grabRoundTripSignature,
191
+ injectClaudeIdentity,
192
+ isSecretServiceAvailable,
193
+ isValidProviderId,
194
+ loadRegistry,
195
+ maxToolsForNpm,
196
+ migrateGlobalOpencodeCredential,
197
+ migrateLegacyCloudProviders,
198
+ modelIdError,
199
+ normalizeToolSchemaForNpm,
200
+ oauthCredentialToKeychainJson,
201
+ parseDsmlToolCalls,
202
+ parseToolArguments,
203
+ preferredRelayCredentialAuthRef,
204
+ readFromCredentialStore,
205
+ readGlobalOpencodeCredential,
206
+ readOpencodeAuthFile,
207
+ readStoredProviderCredential,
208
+ resolveApiKey,
209
+ resolveContextWindow,
210
+ resolveProviderCredential,
211
+ routeLookupIds,
212
+ saveProviderCredential,
213
+ saveRegistry,
214
+ saveToCredentialStore,
215
+ selectBetaFlags,
216
+ serializeToolResultContent,
217
+ silenceSdkWarnings,
218
+ splitToolUseId,
219
+ sseChunk,
220
+ supportsManualModels,
221
+ supportsNativeOAuth,
222
+ thinkingProviderOptions,
223
+ upstreamHttpStatus,
224
+ validateCustomEndpointUrl
225
+ } from "./chunk-Z5GL4ALA.js";
226
+ import "./chunk-JIDIH7DS.js";
214
227
 
215
228
  // src/cli.ts
216
229
  import pc12 from "picocolors";
217
- import * as p14 from "@clack/prompts";
230
+ import * as p15 from "@clack/prompts";
218
231
  import { realpathSync } from "fs";
219
232
  import { fileURLToPath } from "url";
220
233
 
@@ -507,7 +520,7 @@ async function importFromOpencode(options = {}) {
507
520
  }
508
521
  continue;
509
522
  }
510
- const existingIdx = registry.providers.findIndex((p15) => p15.id === entry.id);
523
+ const existingIdx = registry.providers.findIndex((p16) => p16.id === entry.id);
511
524
  const existing = existingIdx >= 0 ? registry.providers[existingIdx] : void 0;
512
525
  if (existing && options.resolveConflict) {
513
526
  const choice = await options.resolveConflict({
@@ -535,7 +548,7 @@ async function importFromOpencode(options = {}) {
535
548
  continue;
536
549
  }
537
550
  if (existingIdx >= 0) {
538
- registry.providers[existingIdx] = { ...entry, addedAt: registry.providers[existingIdx].addedAt };
551
+ registry.providers[existingIdx] = { ...entry, addedAt: registry.providers[existingIdx].addedAt, manualModels: registry.providers[existingIdx].manualModels };
539
552
  } else {
540
553
  registry.providers.push(entry);
541
554
  }
@@ -545,7 +558,7 @@ async function importFromOpencode(options = {}) {
545
558
  if (isOAuth) oauthImported += 1;
546
559
  }
547
560
  const alreadyReportedIds = new Set(skipped.map((s) => s.id));
548
- const registryProviderIds = new Set(registry.providers.map((p15) => p15.id));
561
+ const registryProviderIds = new Set(registry.providers.map((p16) => p16.id));
549
562
  for (const provider of listCredentialSkippedProviders(
550
563
  raw,
551
564
  authEntries,
@@ -800,10 +813,10 @@ async function runFirstRunWizard(trace = false) {
800
813
  p2.log.error("OpenCode CLI not found. Install from https://opencode.ai \u2014 or use Quick start / providers add instead.");
801
814
  return runFirstRunWizard(trace);
802
815
  }
803
- const spinner9 = p2.spinner();
804
- spinner9.start("Importing from OpenCode CLI...");
816
+ const spinner10 = p2.spinner();
817
+ spinner10.start("Importing from OpenCode CLI...");
805
818
  const result = await importFromOpencode();
806
- spinner9.stop("");
819
+ spinner10.stop("");
807
820
  if (result.error) {
808
821
  p2.log.error(result.error);
809
822
  return runFirstRunWizard(trace);
@@ -1236,8 +1249,62 @@ async function pickGlobalFavoriteModel(providers, favorites, opts) {
1236
1249
 
1237
1250
  // src/providers-command.ts
1238
1251
  import pc4 from "picocolors";
1239
- import * as p5 from "@clack/prompts";
1252
+ import * as p6 from "@clack/prompts";
1240
1253
  init_provider_templates();
1254
+
1255
+ // src/manual-model-wizard.ts
1256
+ import * as p5 from "@clack/prompts";
1257
+ async function runManualModelAddFlow(provider) {
1258
+ const modelId = await p5.text({ message: "Exact model ID from your provider", placeholder: "e.g. deepseek-v4.1-flash-expires-on-0910", validate: modelIdError });
1259
+ if (p5.isCancel(modelId)) return 0;
1260
+ const displayName = await p5.text({
1261
+ message: "Display name (optional)",
1262
+ defaultValue: modelId,
1263
+ validate: (value) => value && (value.length > 200 || /[\u0000-\u001f\u007f]/u.test(value)) ? "Use at most 200 characters without control characters." : void 0
1264
+ });
1265
+ if (p5.isCancel(displayName)) return 0;
1266
+ const context = await p5.text({
1267
+ message: "Context size in tokens (optional \u2014 leave blank if unknown)",
1268
+ validate: (value) => contextWindowError(value?.trim() ? Number(value) : void 0)
1269
+ });
1270
+ if (p5.isCancel(context)) return 0;
1271
+ const contextText = typeof context === "string" ? context.trim() : "";
1272
+ const confirmed = await p5.confirm({ message: "Test & add? Sends three small requests to this provider; API charges may apply.", initialValue: true });
1273
+ if (p5.isCancel(confirmed) || !confirmed) return 0;
1274
+ const spinner10 = p5.spinner();
1275
+ spinner10.start("Testing generation, streaming and tool round-trip (up to 90 seconds)\u2026");
1276
+ const result = await addManualModel({
1277
+ providerId: provider.id,
1278
+ modelId: String(modelId),
1279
+ displayName: String(displayName),
1280
+ ...contextText ? { contextWindow: Number(contextText) } : {}
1281
+ });
1282
+ spinner10.stop(result.ok ? "Validation passed" : "Validation failed");
1283
+ if (!result.ok) {
1284
+ p5.log.error(result.error ?? "Model was not added.");
1285
+ return 1;
1286
+ }
1287
+ p5.log.success(`${result.model.name} added. Manual models are preserved when you refresh the catalog.`);
1288
+ p5.log.info("Generation, streaming and tools passed. Pricing and vision support are unknown; context size is user-supplied if set.");
1289
+ return 0;
1290
+ }
1291
+ async function runManualModelRemoveFlow(provider) {
1292
+ const models = provider.manualModels ?? [];
1293
+ if (!models.length) return 0;
1294
+ const id = await p5.select({ message: "Remove which manual model?", options: models.map((m) => ({ value: m.id, label: m.name, hint: m.id })) });
1295
+ if (p5.isCancel(id)) return 0;
1296
+ const confirmed = await p5.confirm({ message: `Remove the manual entry for ${id}?`, initialValue: false });
1297
+ if (p5.isCancel(confirmed) || !confirmed) return 0;
1298
+ const result = removeManualModel(provider.id, String(id));
1299
+ if (!result.ok) {
1300
+ p5.log.error(result.error ?? "Could not remove model.");
1301
+ return 1;
1302
+ }
1303
+ p5.log.success("Manual entry removed. If the provider now lists this ID, its discovered entry remains available.");
1304
+ return 0;
1305
+ }
1306
+
1307
+ // src/providers-command.ts
1241
1308
  function parseProvidersArgs(args) {
1242
1309
  if (args.length === 0) return { subcommand: "hub", showHelp: false };
1243
1310
  const [first, ...rest] = args;
@@ -1313,7 +1380,7 @@ async function runProvidersImport() {
1313
1380
  const hasExisting = registry.providers.length > 0;
1314
1381
  const resolveConflict = hasExisting ? async (ctx) => {
1315
1382
  printImportConflictPanel(ctx.existing.name, ctx.existingKeyHint, ctx.incomingKeyHint);
1316
- const choice = await p5.select({
1383
+ const choice = await p6.select({
1317
1384
  message: "Which configuration should we keep?",
1318
1385
  options: [
1319
1386
  { value: "keep", label: pc4.cyan("Keep mine"), hint: "Leave your current relay-ai config unchanged" },
@@ -1321,46 +1388,46 @@ async function runProvidersImport() {
1321
1388
  { value: "skip", label: pc4.dim("Skip this provider"), hint: "" }
1322
1389
  ]
1323
1390
  });
1324
- if (p5.isCancel(choice)) return "skip";
1391
+ if (p6.isCancel(choice)) return "skip";
1325
1392
  return choice;
1326
1393
  } : void 0;
1327
- const spinner9 = p5.spinner();
1328
- spinner9.start("Importing from OpenCode...");
1394
+ const spinner10 = p6.spinner();
1395
+ spinner10.start("Importing from OpenCode...");
1329
1396
  const result = await importFromOpencode({ resolveConflict });
1330
- spinner9.stop("");
1397
+ spinner10.stop("");
1331
1398
  if (result.error) {
1332
- p5.log.error(result.error);
1399
+ p6.log.error(result.error);
1333
1400
  return 1;
1334
1401
  }
1335
1402
  if (result.imported.length === 0 && result.skipped.length === 0) {
1336
- p5.log.warn("No configured providers found in OpenCode.");
1337
- p5.log.info("Add providers in OpenCode first, or use relay-ai providers add.");
1403
+ p6.log.warn("No configured providers found in OpenCode.");
1404
+ p6.log.info("Add providers in OpenCode first, or use relay-ai providers add.");
1338
1405
  return 0;
1339
1406
  }
1340
1407
  if (result.authFileWarning) {
1341
- p5.log.warn(result.authFileWarning);
1408
+ p6.log.warn(result.authFileWarning);
1342
1409
  }
1343
1410
  const importedNames = result.imported.map((pr) => pr.name).join(", ");
1344
1411
  const modelTotal = result.imported.reduce((n, pr) => n + (pr.modelsCache?.models.length ?? 0), 0);
1345
1412
  const credNote = result.oauthImported > 0 ? ` (${result.oauthImported} via OAuth)` : "";
1346
- p5.log.success(
1413
+ p6.log.success(
1347
1414
  `Imported ${importedNames} \u2014 ${modelTotal} model${modelTotal === 1 ? "" : "s"}, ${result.keysSaved} credential${result.keysSaved === 1 ? "" : "s"} saved to Keychain${credNote}.`
1348
1415
  );
1349
1416
  if (result.skipped.length > 0) {
1350
1417
  for (const s of result.skipped) {
1351
1418
  const reason = s.reason === "user-skipped" ? "skipped by you" : s.reason === "conflict-kept" ? "kept your existing config" : s.reason === "oauth-no-token" ? "OAuth provider in OpenCode but not signed in \u2014 run relay-ai providers auth" : s.reason === "no-api-key" ? "no API key in OpenCode \u2014 add key there or use relay-ai providers add" : s.reason === "manual-only" ? "uses gcloud/AWS credentials \u2014 not importable via API key" : s.reason === "placeholder-key" ? "placeholder API key \u2014 provider not imported" : s.reason === "invalid-key" ? "API key failed verification \u2014 provider not imported" : s.reason === "credential-save-failed" ? "could not save credential \u2014 provider not imported" : s.reason;
1352
- p5.log.warn(`Skipped ${s.name} (${s.id}): ${reason}`);
1419
+ p6.log.warn(`Skipped ${s.name} (${s.id}): ${reason}`);
1353
1420
  }
1354
1421
  }
1355
1422
  if (result.keysSkipped.length > 0) {
1356
1423
  for (const k of result.keysSkipped) {
1357
1424
  if (k.detail) {
1358
- p5.log.info(`${k.name} (${k.id}): ${k.detail}`);
1425
+ p6.log.info(`${k.name} (${k.id}): ${k.detail}`);
1359
1426
  }
1360
1427
  }
1361
1428
  }
1362
1429
  if (result.imported.length > 0) {
1363
- const refreshSpinner = p5.spinner();
1430
+ const refreshSpinner = p6.spinner();
1364
1431
  refreshSpinner.start("Fetching model capabilities from providers...");
1365
1432
  const registry2 = loadRegistry();
1366
1433
  for (const provider of result.imported) {
@@ -1377,14 +1444,14 @@ async function runProvidersImport() {
1377
1444
  async function runProvidersAuth(providerId, method) {
1378
1445
  try {
1379
1446
  const result = await authenticateProvider(providerId, { method });
1380
- p5.log.success(`Signed in to ${result.registryProvider.name} \u2014 credential saved to Keychain.`);
1447
+ p6.log.success(`Signed in to ${result.registryProvider.name} \u2014 credential saved to Keychain.`);
1381
1448
  return 0;
1382
1449
  } catch (err) {
1383
1450
  if (err instanceof Error && err.message === "Cancelled") {
1384
- p5.cancel("Cancelled.");
1451
+ p6.cancel("Cancelled.");
1385
1452
  return 0;
1386
1453
  }
1387
- p5.log.error(err instanceof Error ? err.message : String(err));
1454
+ p6.log.error(err instanceof Error ? err.message : String(err));
1388
1455
  return 1;
1389
1456
  }
1390
1457
  }
@@ -1392,67 +1459,67 @@ async function runProvidersRefreshModels(providerId) {
1392
1459
  const resolveKey = async (provider) => resolveProviderCredential(provider.id, provider.authRef);
1393
1460
  if (providerId) {
1394
1461
  const registry = loadRegistry();
1395
- const provider = registry.providers.find((p15) => p15.id === providerId);
1462
+ const provider = registry.providers.find((p16) => p16.id === providerId);
1396
1463
  if (!provider) {
1397
- p5.log.error(`Provider not found: ${providerId}`);
1464
+ p6.log.error(`Provider not found: ${providerId}`);
1398
1465
  return 1;
1399
1466
  }
1400
- const spinner10 = p5.spinner();
1401
- spinner10.start(`Refreshing ${provider.name}...`);
1467
+ const spinner11 = p6.spinner();
1468
+ spinner11.start(`Refreshing ${provider.name}...`);
1402
1469
  const key = await resolveRefreshCredential(
1403
1470
  provider,
1404
- async (p15) => resolveProviderCredential(p15.id, p15.authRef)
1471
+ async (p16) => resolveProviderCredential(p16.id, p16.authRef)
1405
1472
  );
1406
1473
  const result = await refreshProviderModels(providerId, key);
1407
- spinner10.stop("");
1474
+ spinner11.stop("");
1408
1475
  if (result.skipped) {
1409
1476
  const countNote = result.modelCount ? ` (${result.modelCount} cached models kept)` : "";
1410
- p5.log.warn(`${result.name}: ${result.reason}${countNote}`);
1477
+ p6.log.warn(`${result.name}: ${result.reason}${countNote}`);
1411
1478
  return 0;
1412
1479
  }
1413
1480
  if (!result.ok) {
1414
- p5.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
1481
+ p6.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
1415
1482
  return 1;
1416
1483
  }
1417
1484
  const diff = result.previousModelCount === void 0 ? 0 : (result.modelCount ?? 0) - result.previousModelCount;
1418
1485
  const diffStr = result.previousModelCount === void 0 ? "" : diff > 0 ? ` (+${diff})` : diff < 0 ? ` (${diff})` : "";
1419
- p5.log.success(`${result.name}: ${result.modelCount} model${result.modelCount === 1 ? "" : "s"} updated${diffStr}.`);
1486
+ p6.log.success(`${result.name}: ${result.modelCount} model${result.modelCount === 1 ? "" : "s"} updated${diffStr}.`);
1420
1487
  if (result.reason) {
1421
- p5.log.warn(result.reason);
1488
+ p6.log.warn(result.reason);
1422
1489
  }
1423
1490
  return 0;
1424
1491
  }
1425
- const spinner9 = p5.spinner();
1426
- spinner9.start("Refreshing model lists...");
1492
+ const spinner10 = p6.spinner();
1493
+ spinner10.start("Refreshing model lists...");
1427
1494
  const { refreshed } = await refreshAllProviderModels(resolveKey);
1428
- spinner9.stop("");
1495
+ spinner10.stop("");
1429
1496
  const ok = refreshed.filter((r) => r.ok && !r.skipped);
1430
1497
  const skipped = refreshed.filter((r) => r.skipped);
1431
1498
  const failed = refreshed.filter((r) => !r.ok);
1432
1499
  if (ok.length > 0) {
1433
- p5.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
1500
+ p6.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
1434
1501
  for (const r of ok) {
1435
1502
  const diff = r.previousModelCount === void 0 ? 0 : (r.modelCount ?? 0) - r.previousModelCount;
1436
1503
  const diffStr = r.previousModelCount === void 0 ? "" : diff > 0 ? ` (+${diff})` : diff < 0 ? ` (${diff})` : "";
1437
- p5.log.info(` ${r.name}: ${r.modelCount} model${r.modelCount === 1 ? "" : "s"}${diffStr}`);
1504
+ p6.log.info(` ${r.name}: ${r.modelCount} model${r.modelCount === 1 ? "" : "s"}${diffStr}`);
1438
1505
  if (r.reason) {
1439
- p5.log.warn(` ${r.reason}`);
1506
+ p6.log.warn(` ${r.reason}`);
1440
1507
  }
1441
1508
  }
1442
1509
  }
1443
1510
  for (const r of skipped) {
1444
1511
  const countNote = r.modelCount ? ` (${r.modelCount} cached models kept)` : "";
1445
- p5.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
1512
+ p6.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
1446
1513
  }
1447
1514
  for (const r of failed) {
1448
- p5.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
1515
+ p6.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
1449
1516
  }
1450
1517
  return failed.length > 0 ? 1 : 0;
1451
1518
  }
1452
1519
  async function runProvidersList() {
1453
1520
  const entries = await resolveProvidersForDisplay();
1454
1521
  if (entries.length === 0) {
1455
- p5.log.info("No providers configured. Run relay-ai providers add or import.");
1522
+ p6.log.info("No providers configured. Run relay-ai providers add or import.");
1456
1523
  return 0;
1457
1524
  }
1458
1525
  console.log("");
@@ -1468,10 +1535,10 @@ async function runProvidersList() {
1468
1535
  async function pickTemplateFromCatalog() {
1469
1536
  while (true) {
1470
1537
  const registry = loadRegistry();
1471
- const configuredIds = new Set(registry.providers.map((p15) => p15.id));
1538
+ const configuredIds = new Set(registry.providers.map((p16) => p16.id));
1472
1539
  const templates = listAddableTemplates(configuredIds);
1473
1540
  if (templates.length === 0) return null;
1474
- const method = await p5.select({
1541
+ const method = await p6.select({
1475
1542
  message: `Choose a provider (${templates.length} available)`,
1476
1543
  options: [
1477
1544
  { value: "search", label: "Search providers", hint: "e.g. gro, mistral, together" },
@@ -1479,32 +1546,32 @@ async function pickTemplateFromCatalog() {
1479
1546
  { value: "back", label: "Back", hint: "" }
1480
1547
  ]
1481
1548
  });
1482
- if (p5.isCancel(method) || method === "back") return null;
1549
+ if (p6.isCancel(method) || method === "back") return null;
1483
1550
  if (method === "browse") {
1484
1551
  const options2 = templates.map((t) => ({
1485
1552
  value: t.id,
1486
1553
  label: t.name,
1487
1554
  hint: t.npm
1488
1555
  }));
1489
- const picked2 = await p5.select({ message: "Select a provider", options: options2 });
1490
- if (p5.isCancel(picked2)) continue;
1556
+ const picked2 = await p6.select({ message: "Select a provider", options: options2 });
1557
+ if (p6.isCancel(picked2)) continue;
1491
1558
  const template2 = templates.find((t) => t.id === picked2);
1492
1559
  if (template2) return template2;
1493
1560
  continue;
1494
1561
  }
1495
- const searchInput = await p5.text({
1562
+ const searchInput = await p6.text({
1496
1563
  message: "Search providers:",
1497
1564
  placeholder: "e.g. groq, mistral, openrouter"
1498
1565
  });
1499
- if (p5.isCancel(searchInput)) continue;
1566
+ if (p6.isCancel(searchInput)) continue;
1500
1567
  const query = String(searchInput);
1501
1568
  const matched = filterTemplates(templates, query);
1502
1569
  if (matched.length === 0) {
1503
1570
  const alreadyAdded = filterTemplates(listSupportedTemplates(), query).filter((t) => configuredIds.has(t.id));
1504
1571
  if (alreadyAdded.length > 0) {
1505
- p5.log.info(`Already configured: ${alreadyAdded.map((t) => t.name).join(", ")}`);
1572
+ p6.log.info(`Already configured: ${alreadyAdded.map((t) => t.name).join(", ")}`);
1506
1573
  } else {
1507
- p5.log.warn("No providers match \u2014 try a different search");
1574
+ p6.log.warn("No providers match \u2014 try a different search");
1508
1575
  }
1509
1576
  continue;
1510
1577
  }
@@ -1513,11 +1580,11 @@ async function pickTemplateFromCatalog() {
1513
1580
  label: t.name,
1514
1581
  hint: t.npm
1515
1582
  }));
1516
- const picked = await p5.select({
1583
+ const picked = await p6.select({
1517
1584
  message: matched.length === 1 ? "Match found" : `Select provider (${matched.length} matches)`,
1518
1585
  options
1519
1586
  });
1520
- if (p5.isCancel(picked)) continue;
1587
+ if (p6.isCancel(picked)) continue;
1521
1588
  const template = matched.find((t) => t.id === picked);
1522
1589
  if (template) return template;
1523
1590
  }
@@ -1534,7 +1601,7 @@ function showProviderAddFailure(error, hint, fallback) {
1534
1601
  ]);
1535
1602
  }
1536
1603
  async function runDualAuthTemplateFlow(template, existing) {
1537
- const method = await p5.select({
1604
+ const method = await p6.select({
1538
1605
  message: existing ? `Change ${template.name} authentication` : `How would you like to connect to ${template.name}?`,
1539
1606
  options: [
1540
1607
  {
@@ -1550,7 +1617,7 @@ async function runDualAuthTemplateFlow(template, existing) {
1550
1617
  { value: "back", label: "Back", hint: "" }
1551
1618
  ]
1552
1619
  });
1553
- if (p5.isCancel(method) || method === "back") return 0;
1620
+ if (p6.isCancel(method) || method === "back") return 0;
1554
1621
  if (method === "oauth") {
1555
1622
  return runProvidersAuth(template.id);
1556
1623
  }
@@ -1559,12 +1626,12 @@ async function runDualAuthTemplateFlow(template, existing) {
1559
1626
  `${pc4.white("Get an API key at:")} ${fmtUrl(template.signupUrl)}`
1560
1627
  ]);
1561
1628
  }
1562
- const apiKeyInput = await p5.password({
1629
+ const apiKeyInput = await p6.password({
1563
1630
  message: `Paste your ${template.name} API key:`,
1564
1631
  validate: (value) => value.trim() ? void 0 : "Key cannot be empty"
1565
1632
  });
1566
- if (p5.isCancel(apiKeyInput)) {
1567
- p5.cancel("Cancelled.");
1633
+ if (p6.isCancel(apiKeyInput)) {
1634
+ p6.cancel("Cancelled.");
1568
1635
  return 0;
1569
1636
  }
1570
1637
  const apiKey = String(apiKeyInput).trim();
@@ -1572,12 +1639,12 @@ async function runDualAuthTemplateFlow(template, existing) {
1572
1639
  showProviderAddFailure("Key cannot be empty.", void 0, "Could not add provider.");
1573
1640
  return 1;
1574
1641
  }
1575
- const spinner9 = p5.spinner();
1576
- spinner9.start(`Testing connection to ${template.name}...`);
1642
+ const spinner10 = p6.spinner();
1643
+ spinner10.start(`Testing connection to ${template.name}...`);
1577
1644
  const result = await addProviderFromTemplate(template, apiKey, {
1578
1645
  replaceExisting: Boolean(existing)
1579
1646
  });
1580
- spinner9.stop("");
1647
+ spinner10.stop("");
1581
1648
  if (!result.added) {
1582
1649
  showProviderAddFailure(result.error, result.hint, "Could not add provider.");
1583
1650
  return 1;
@@ -1586,8 +1653,8 @@ async function runDualAuthTemplateFlow(template, existing) {
1586
1653
  return 0;
1587
1654
  }
1588
1655
  async function runTemplateAddFlow() {
1589
- if (listAddableTemplates(loadRegistry().providers.map((p15) => p15.id)).length === 0) {
1590
- p5.log.info("All catalog providers are already configured.");
1656
+ if (listAddableTemplates(loadRegistry().providers.map((p16) => p16.id)).length === 0) {
1657
+ p6.log.info("All catalog providers are already configured.");
1591
1658
  return 0;
1592
1659
  }
1593
1660
  const template = await pickTemplateFromCatalog();
@@ -1605,25 +1672,25 @@ async function runTemplateAddFlow() {
1605
1672
  ]);
1606
1673
  const collected = await resolveOrCollectApiKey(false, false);
1607
1674
  if (!collected) {
1608
- p5.cancel("Cancelled.");
1675
+ p6.cancel("Cancelled.");
1609
1676
  return 0;
1610
1677
  }
1611
1678
  apiKey2 = collected;
1612
1679
  }
1613
1680
  await migrateGlobalOpencodeCredential();
1614
- const spinner10 = p5.spinner();
1615
- spinner10.start(`Adding ${template.name}...`);
1681
+ const spinner11 = p6.spinner();
1682
+ spinner11.start(`Adding ${template.name}...`);
1616
1683
  const result2 = await addOpencodeCloudFromApiKey(apiKey2);
1617
- spinner10.stop("");
1684
+ spinner11.stop("");
1618
1685
  if (!result2.added) {
1619
- p5.log.warn(result2.error ?? "OpenCode Zen / Go is already configured.");
1620
- if (result2.hint) p5.log.info(result2.hint);
1686
+ p6.log.warn(result2.error ?? "OpenCode Zen / Go is already configured.");
1687
+ if (result2.hint) p6.log.info(result2.hint);
1621
1688
  return 0;
1622
1689
  }
1623
1690
  if (result2.hint) {
1624
- p5.log.warn(`Added ${template.name}. ${result2.hint}`);
1691
+ p6.log.warn(`Added ${template.name}. ${result2.hint}`);
1625
1692
  } else {
1626
- p5.log.success(`Added ${template.name} \u2014 ${fmtCount(result2.modelCount ?? 0, "model")} updated.`);
1693
+ p6.log.success(`Added ${template.name} \u2014 ${fmtCount(result2.modelCount ?? 0, "model")} updated.`);
1627
1694
  }
1628
1695
  return 0;
1629
1696
  }
@@ -1634,48 +1701,48 @@ async function runTemplateAddFlow() {
1634
1701
  }
1635
1702
  let baseUrlOverride;
1636
1703
  if (template.accountIdPrompt) {
1637
- const accountInput = await p5.text({
1704
+ const accountInput = await p6.text({
1638
1705
  message: template.accountIdPrompt,
1639
1706
  placeholder: "e.g. 4ff191dac2d0bd7538cb1c9126594de3",
1640
1707
  validate: (v) => v.trim() ? void 0 : "Account ID is required"
1641
1708
  });
1642
- if (p5.isCancel(accountInput)) return 0;
1709
+ if (p6.isCancel(accountInput)) return 0;
1643
1710
  const accountId = String(accountInput).trim();
1644
1711
  baseUrlOverride = template.defaultBaseUrl?.replace("{ACCOUNT_ID}", accountId);
1645
1712
  } else if (template.urlPrompt) {
1646
- const urlInput = await p5.text({
1713
+ const urlInput = await p6.text({
1647
1714
  message: template.urlPrompt,
1648
1715
  initialValue: template.defaultBaseUrl,
1649
1716
  validate: (v) => v.trim() ? void 0 : "URL is required"
1650
1717
  });
1651
- if (p5.isCancel(urlInput)) return 0;
1718
+ if (p6.isCancel(urlInput)) return 0;
1652
1719
  baseUrlOverride = String(urlInput).trim();
1653
1720
  const usesHttp = /^http:\/\//i.test(baseUrlOverride);
1654
1721
  if (usesHttp) {
1655
- p5.log.warn("HTTP is not encrypted. Use it only for trusted local or LAN servers, like Ollama on your own network.");
1722
+ p6.log.warn("HTTP is not encrypted. Use it only for trusted local or LAN servers, like Ollama on your own network.");
1656
1723
  }
1657
1724
  const valid = await validateCustomEndpointUrl(baseUrlOverride, { allowInsecureLocal: usesHttp });
1658
1725
  if (!valid.ok) {
1659
- p5.log.error(valid.error ?? "Invalid URL");
1660
- if (valid.hint) p5.log.info(valid.hint);
1726
+ p6.log.error(valid.error ?? "Invalid URL");
1727
+ if (valid.hint) p6.log.info(valid.hint);
1661
1728
  return 1;
1662
1729
  }
1663
1730
  }
1664
1731
  const apiKeyMsg = template.anonymousFreeModels ? `API key (leave empty to use free models only):` : template.apiKeyOptional ? `API key (leave empty for local servers without auth):` : `Paste your ${template.name} API key:`;
1665
- const apiKeyInput = await p5.password({
1732
+ const apiKeyInput = await p6.password({
1666
1733
  message: apiKeyMsg,
1667
1734
  validate: (val) => template.apiKeyOptional ? void 0 : val.trim() ? void 0 : "Key cannot be empty"
1668
1735
  });
1669
- if (p5.isCancel(apiKeyInput)) {
1670
- p5.cancel("Cancelled.");
1736
+ if (p6.isCancel(apiKeyInput)) {
1737
+ p6.cancel("Cancelled.");
1671
1738
  return 0;
1672
1739
  }
1673
1740
  const rawKey = String(apiKeyInput).trim();
1674
1741
  const apiKey = template.apiKeyOptional && !rawKey && !template.anonymousFreeModels ? template.id : rawKey;
1675
- const spinner9 = p5.spinner();
1676
- spinner9.start(`Testing connection to ${template.name}...`);
1742
+ const spinner10 = p6.spinner();
1743
+ spinner10.start(`Testing connection to ${template.name}...`);
1677
1744
  const result = await addProviderFromTemplate(template, apiKey, { baseUrl: baseUrlOverride });
1678
- spinner9.stop("");
1745
+ spinner10.stop("");
1679
1746
  if (!result.added) {
1680
1747
  showProviderAddFailure(result.error, result.hint, "Could not add provider.");
1681
1748
  return 1;
@@ -1684,7 +1751,7 @@ async function runTemplateAddFlow() {
1684
1751
  return 0;
1685
1752
  }
1686
1753
  async function runCustomEndpointAddFlow() {
1687
- const kindChoice = await p5.select({
1754
+ const kindChoice = await p6.select({
1688
1755
  message: "Custom server type",
1689
1756
  options: [
1690
1757
  {
@@ -1700,52 +1767,52 @@ async function runCustomEndpointAddFlow() {
1700
1767
  { value: "back", label: "Back", hint: "" }
1701
1768
  ]
1702
1769
  });
1703
- if (p5.isCancel(kindChoice) || kindChoice === "back") return 0;
1704
- const displayName = await p5.text({
1770
+ if (p6.isCancel(kindChoice) || kindChoice === "back") return 0;
1771
+ const displayName = await p6.text({
1705
1772
  message: "Display name:",
1706
1773
  placeholder: "My Work LLM",
1707
1774
  validate: (v) => v.trim() ? void 0 : "Name is required"
1708
1775
  });
1709
- if (p5.isCancel(displayName)) return 0;
1710
- const baseUrl = await p5.text({
1776
+ if (p6.isCancel(displayName)) return 0;
1777
+ const baseUrl = await p6.text({
1711
1778
  message: "Base URL:",
1712
1779
  placeholder: kindChoice === "openai" ? "https://api.together.xyz/v1" : "https://api.anthropic.com",
1713
1780
  validate: (v) => v.trim() ? void 0 : "URL is required"
1714
1781
  });
1715
- if (p5.isCancel(baseUrl)) return 0;
1782
+ if (p6.isCancel(baseUrl)) return 0;
1716
1783
  const usesHttp = /^http:\/\//i.test(String(baseUrl).trim());
1717
1784
  let allowInsecureHttp = false;
1718
1785
  if (usesHttp) {
1719
- p5.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1720
- const allowLocal = await p5.confirm({
1786
+ p6.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1787
+ const allowLocal = await p6.confirm({
1721
1788
  message: "Allow insecure HTTP for this local/LAN server?",
1722
1789
  initialValue: true
1723
1790
  });
1724
- if (p5.isCancel(allowLocal)) return 0;
1791
+ if (p6.isCancel(allowLocal)) return 0;
1725
1792
  allowInsecureHttp = allowLocal === true;
1726
1793
  }
1727
- const apiKey = await p5.password({
1794
+ const apiKey = await p6.password({
1728
1795
  message: "API key (leave empty for local servers without auth):"
1729
1796
  });
1730
- if (p5.isCancel(apiKey)) return 0;
1731
- const wantsHeaders = await p5.confirm({
1797
+ if (p6.isCancel(apiKey)) return 0;
1798
+ const wantsHeaders = await p6.confirm({
1732
1799
  message: "Does this endpoint need extra custom headers? (e.g. a plan/auth-tracking header)",
1733
1800
  initialValue: false
1734
1801
  });
1735
- if (p5.isCancel(wantsHeaders)) return 0;
1802
+ if (p6.isCancel(wantsHeaders)) return 0;
1736
1803
  const headers = {};
1737
1804
  if (wantsHeaders) {
1738
1805
  for (; ; ) {
1739
- const headerLine = await p5.text({
1806
+ const headerLine = await p6.text({
1740
1807
  message: "Header (leave empty when done):",
1741
1808
  placeholder: "X-Plan: coding"
1742
1809
  });
1743
- if (p5.isCancel(headerLine)) return 0;
1810
+ if (p6.isCancel(headerLine)) return 0;
1744
1811
  const trimmed = String(headerLine).trim();
1745
1812
  if (!trimmed) break;
1746
1813
  const idx = trimmed.indexOf(":");
1747
1814
  if (idx < 1) {
1748
- p5.log.warn('Use the format "Name: Value" \u2014 skipped.');
1815
+ p6.log.warn('Use the format "Name: Value" \u2014 skipped.');
1749
1816
  continue;
1750
1817
  }
1751
1818
  const name = trimmed.slice(0, idx).trim();
@@ -1761,22 +1828,22 @@ async function runCustomEndpointAddFlow() {
1761
1828
  allowInsecureLocal: allowInsecureHttp,
1762
1829
  headers: Object.keys(headers).length > 0 ? headers : void 0
1763
1830
  };
1764
- const spinner9 = p5.spinner();
1765
- spinner9.start("Testing connection...");
1831
+ const spinner10 = p6.spinner();
1832
+ spinner10.start("Testing connection...");
1766
1833
  let result = await addCustomEndpointProvider(addInput);
1767
- spinner9.stop("");
1834
+ spinner10.stop("");
1768
1835
  if (!result.added && result.duplicateOf) {
1769
- const addAnyway = await p5.confirm({
1836
+ const addAnyway = await p6.confirm({
1770
1837
  message: `You already have a backend with the same URL, key and headers (${result.duplicateOf}). Add another?`,
1771
1838
  initialValue: false
1772
1839
  });
1773
- if (p5.isCancel(addAnyway) || !addAnyway) {
1774
- p5.cancel("Cancelled.");
1840
+ if (p6.isCancel(addAnyway) || !addAnyway) {
1841
+ p6.cancel("Cancelled.");
1775
1842
  return 0;
1776
1843
  }
1777
- spinner9.start("Testing connection...");
1844
+ spinner10.start("Testing connection...");
1778
1845
  result = await addCustomEndpointProvider({ ...addInput, confirmDuplicate: true });
1779
- spinner9.stop("");
1846
+ spinner10.stop("");
1780
1847
  }
1781
1848
  if (!result.added) {
1782
1849
  showProviderAddFailure(result.error, result.hint, "Could not add custom provider.");
@@ -1788,70 +1855,70 @@ async function runCustomEndpointAddFlow() {
1788
1855
  async function runCustomEndpointEditFlow(provider) {
1789
1856
  const currentHeaders = provider.api.headers ?? {};
1790
1857
  const headerSummary = Object.keys(currentHeaders).length > 0 ? Object.entries(currentHeaders).map(([k, v]) => `${k}: ${v}`).join(", ") : "none";
1791
- p5.log.info(`Base URL: ${provider.api.url ?? "(none)"}
1858
+ p6.log.info(`Base URL: ${provider.api.url ?? "(none)"}
1792
1859
  Headers: ${headerSummary}`);
1793
- const displayName = await p5.text({
1860
+ const displayName = await p6.text({
1794
1861
  message: "Display name:",
1795
1862
  initialValue: provider.name,
1796
1863
  validate: (v) => v.trim() ? void 0 : "Name is required"
1797
1864
  });
1798
- if (p5.isCancel(displayName)) {
1799
- p5.cancel("Cancelled.");
1865
+ if (p6.isCancel(displayName)) {
1866
+ p6.cancel("Cancelled.");
1800
1867
  return 0;
1801
1868
  }
1802
- const baseUrl = await p5.text({
1869
+ const baseUrl = await p6.text({
1803
1870
  message: "Base URL:",
1804
1871
  initialValue: provider.api.url ?? "",
1805
1872
  validate: (v) => v.trim() ? void 0 : "URL is required"
1806
1873
  });
1807
- if (p5.isCancel(baseUrl)) {
1808
- p5.cancel("Cancelled.");
1874
+ if (p6.isCancel(baseUrl)) {
1875
+ p6.cancel("Cancelled.");
1809
1876
  return 0;
1810
1877
  }
1811
1878
  const usesHttp = /^http:\/\//i.test(String(baseUrl).trim());
1812
1879
  let allowInsecureHttp = false;
1813
1880
  if (usesHttp) {
1814
- p5.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1815
- const allowLocal = await p5.confirm({
1881
+ p6.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1882
+ const allowLocal = await p6.confirm({
1816
1883
  message: "Allow insecure HTTP for this local/LAN server?",
1817
1884
  initialValue: true
1818
1885
  });
1819
- if (p5.isCancel(allowLocal)) return 0;
1886
+ if (p6.isCancel(allowLocal)) return 0;
1820
1887
  allowInsecureHttp = allowLocal === true;
1821
1888
  }
1822
- const apiKey = await p5.password({
1889
+ const apiKey = await p6.password({
1823
1890
  message: "API key (leave empty to keep the current key):"
1824
1891
  });
1825
- if (p5.isCancel(apiKey)) {
1826
- p5.cancel("Cancelled.");
1892
+ if (p6.isCancel(apiKey)) {
1893
+ p6.cancel("Cancelled.");
1827
1894
  return 0;
1828
1895
  }
1829
- const editHeaders = await p5.confirm({
1896
+ const editHeaders = await p6.confirm({
1830
1897
  message: `Replace custom headers? (current: ${headerSummary})`,
1831
1898
  initialValue: false
1832
1899
  });
1833
- if (p5.isCancel(editHeaders)) {
1834
- p5.cancel("Cancelled.");
1900
+ if (p6.isCancel(editHeaders)) {
1901
+ p6.cancel("Cancelled.");
1835
1902
  return 0;
1836
1903
  }
1837
1904
  let headers;
1838
1905
  if (editHeaders) {
1839
1906
  headers = {};
1840
- p5.log.info("Enter the full header set. Leave the first one empty to remove all headers.");
1907
+ p6.log.info("Enter the full header set. Leave the first one empty to remove all headers.");
1841
1908
  for (; ; ) {
1842
- const headerLine = await p5.text({
1909
+ const headerLine = await p6.text({
1843
1910
  message: "Header (leave empty when done):",
1844
1911
  placeholder: "X-Plan: coding"
1845
1912
  });
1846
- if (p5.isCancel(headerLine)) {
1847
- p5.cancel("Cancelled.");
1913
+ if (p6.isCancel(headerLine)) {
1914
+ p6.cancel("Cancelled.");
1848
1915
  return 0;
1849
1916
  }
1850
1917
  const trimmed = String(headerLine).trim();
1851
1918
  if (!trimmed) break;
1852
1919
  const idx = trimmed.indexOf(":");
1853
1920
  if (idx < 1) {
1854
- p5.log.warn('Use the format "Name: Value" \u2014 skipped.');
1921
+ p6.log.warn('Use the format "Name: Value" \u2014 skipped.');
1855
1922
  continue;
1856
1923
  }
1857
1924
  const name = trimmed.slice(0, idx).trim();
@@ -1868,22 +1935,22 @@ Headers: ${headerSummary}`);
1868
1935
  allowInsecureLocal: allowInsecureHttp,
1869
1936
  saveAnyway
1870
1937
  });
1871
- const spinner9 = p5.spinner();
1872
- spinner9.start("Testing connection...");
1938
+ const spinner10 = p6.spinner();
1939
+ spinner10.start("Testing connection...");
1873
1940
  let result = await runUpdate(false);
1874
- spinner9.stop("");
1941
+ spinner10.stop("");
1875
1942
  if (!result.updated && result.error === "Nothing to change.") {
1876
- p5.log.info("No changes made.");
1943
+ p6.log.info("No changes made.");
1877
1944
  return 0;
1878
1945
  }
1879
1946
  if (!result.updated) {
1880
1947
  showProviderAddFailure(result.error, result.hint, "Could not update backend.");
1881
1948
  if (!result.canSaveAnyway) return 1;
1882
- const saveAnyway = await p5.confirm({
1949
+ const saveAnyway = await p6.confirm({
1883
1950
  message: "Save these settings anyway? The model list will not be refreshed.",
1884
1951
  initialValue: false
1885
1952
  });
1886
- if (p5.isCancel(saveAnyway) || !saveAnyway) return 1;
1953
+ if (p6.isCancel(saveAnyway) || !saveAnyway) return 1;
1887
1954
  result = await runUpdate(true);
1888
1955
  if (!result.updated) {
1889
1956
  showProviderAddFailure(result.error, result.hint, "Could not update backend.");
@@ -1891,7 +1958,7 @@ Headers: ${headerSummary}`);
1891
1958
  }
1892
1959
  }
1893
1960
  if (result.modelsStale) {
1894
- p5.log.warn(`${result.provider?.name ?? provider.name} saved, but the model list may be out of date.`);
1961
+ p6.log.warn(`${result.provider?.name ?? provider.name} saved, but the model list may be out of date.`);
1895
1962
  } else {
1896
1963
  logConnected(result.provider?.name ?? provider.name, result.modelCount ?? 0);
1897
1964
  }
@@ -1901,7 +1968,7 @@ async function runProvidersAdd() {
1901
1968
  const registry = loadRegistry();
1902
1969
  const hasOpencode = findOpencodeBinary() !== null;
1903
1970
  const options = [];
1904
- const addableTemplates = listAddableTemplates(registry.providers.map((p15) => p15.id));
1971
+ const addableTemplates = listAddableTemplates(registry.providers.map((p16) => p16.id));
1905
1972
  if (addableTemplates.length > 0) {
1906
1973
  options.push({
1907
1974
  value: "templates",
@@ -1919,14 +1986,14 @@ async function runProvidersAdd() {
1919
1986
  label: "Import providers from OpenCode CLI",
1920
1987
  hint: hasOpencode ? "Import Groq, OpenAI, etc. from your OpenCode config" : "Requires OpenCode CLI"
1921
1988
  });
1922
- const choice = await p5.select({ message: "Add a provider", options });
1923
- if (p5.isCancel(choice)) {
1924
- p5.cancel("Cancelled.");
1989
+ const choice = await p6.select({ message: "Add a provider", options });
1990
+ if (p6.isCancel(choice)) {
1991
+ p6.cancel("Cancelled.");
1925
1992
  return 0;
1926
1993
  }
1927
1994
  if (choice === "import") {
1928
1995
  if (!hasOpencode) {
1929
- p5.log.error("OpenCode CLI not found. Install from https://opencode.ai");
1996
+ p6.log.error("OpenCode CLI not found. Install from https://opencode.ai");
1930
1997
  return 1;
1931
1998
  }
1932
1999
  return runProvidersImport();
@@ -1939,27 +2006,27 @@ async function runProvidersRemove(id, interactive = false) {
1939
2006
  const registry = loadRegistry();
1940
2007
  const provider = registry.providers.find((pr) => pr.id === id);
1941
2008
  if (!provider) {
1942
- p5.log.error(`Provider not found: ${id}`);
2009
+ p6.log.error(`Provider not found: ${id}`);
1943
2010
  return 1;
1944
2011
  }
1945
2012
  if (interactive) {
1946
- const confirm9 = await p5.confirm({
2013
+ const confirm10 = await p6.confirm({
1947
2014
  message: `Remove ${provider.name} (${id})?`,
1948
2015
  initialValue: false
1949
2016
  });
1950
- if (p5.isCancel(confirm9) || !confirm9) {
1951
- p5.cancel("Cancelled.");
2017
+ if (p6.isCancel(confirm10) || !confirm10) {
2018
+ p6.cancel("Cancelled.");
1952
2019
  return 0;
1953
2020
  }
1954
2021
  }
1955
2022
  const result = await removeProviderFromRegistry(id);
1956
2023
  if (!result.removed) {
1957
- p5.log.error(result.error ?? `Could not remove ${id}`);
2024
+ p6.log.error(result.error ?? `Could not remove ${id}`);
1958
2025
  return 1;
1959
2026
  }
1960
- p5.log.success(`Removed ${result.name ?? id}.`);
2027
+ p6.log.success(`Removed ${result.name ?? id}.`);
1961
2028
  if (result.credentialDeleted) {
1962
- p5.log.info("Provider API key removed from Keychain.");
2029
+ p6.log.info("Provider API key removed from Keychain.");
1963
2030
  }
1964
2031
  return 0;
1965
2032
  }
@@ -1968,7 +2035,7 @@ async function runOpenCodeCloudDetail() {
1968
2035
  const routes = registry.providers.filter((provider) => provider.id === "zen" || provider.id === "go");
1969
2036
  printCloudProviderPanel("OpenCode Zen / Go");
1970
2037
  if (routes.length === 0) return "back";
1971
- const choice = await p5.select({
2038
+ const choice = await p6.select({
1972
2039
  message: "Manage an OpenCode catalog",
1973
2040
  options: [
1974
2041
  ...routes.map((provider) => ({
@@ -1979,7 +2046,7 @@ async function runOpenCodeCloudDetail() {
1979
2046
  { value: "back", label: "Back", hint: "" }
1980
2047
  ]
1981
2048
  });
1982
- if (!p5.isCancel(choice) && choice !== "back") {
2049
+ if (!p6.isCancel(choice) && choice !== "back") {
1983
2050
  await runProviderDetail(String(choice));
1984
2051
  }
1985
2052
  return "back";
@@ -1991,12 +2058,18 @@ async function runProviderDetail(id) {
1991
2058
  const registry = loadRegistry();
1992
2059
  const provider = registry.providers.find((pr) => pr.id === id);
1993
2060
  if (!provider) return "back";
1994
- const modelCount = provider.modelsCache?.models.length ?? 0;
2061
+ const modelCount = getProviderModels(provider).length;
1995
2062
  const authLabel = formatRegistryAuthLabel(provider);
1996
2063
  printProviderDetailPanel(provider.name, modelCount, authLabel);
1997
2064
  const template = getTemplateById(provider.templateId) ?? getTemplateById(id);
1998
2065
  const hasDualAuth = template ? hasApiAndOAuth(template) : false;
1999
2066
  const detailOptions = [];
2067
+ if (supportsManualModels(provider) && provider.enabled) {
2068
+ detailOptions.push({ value: "add-model", label: "Add model manually", hint: "Test an exact model ID, then save it" });
2069
+ }
2070
+ if (provider.manualModels?.length) {
2071
+ detailOptions.push({ value: "remove-model", label: "Remove manual model", hint: "Keep discovered models" });
2072
+ }
2000
2073
  if (modelCount > 0) {
2001
2074
  detailOptions.push({
2002
2075
  value: "browse",
@@ -2045,13 +2118,15 @@ async function runProviderDetail(id) {
2045
2118
  { value: "remove", label: "Remove provider", hint: "Delete from registry and Keychain when safe" },
2046
2119
  { value: "back", label: "Back", hint: "" }
2047
2120
  );
2048
- const action = await p5.select({
2121
+ const action = await p6.select({
2049
2122
  message: "What would you like to do?",
2050
2123
  options: detailOptions
2051
2124
  });
2052
- if (p5.isCancel(action) || action === "back") return "back";
2125
+ if (p6.isCancel(action) || action === "back") return "back";
2126
+ if (action === "add-model") return await runManualModelAddFlow(provider) === 0 ? "back" : "failed";
2127
+ if (action === "remove-model") return await runManualModelRemoveFlow(provider) === 0 ? "back" : "failed";
2053
2128
  if (action === "browse") {
2054
- const cachedModels = provider.modelsCache?.models ?? [];
2129
+ const cachedModels = getProviderModels(provider);
2055
2130
  const localModels = cachedModels.map((m) => cachedModelToLocal(m, provider)).filter((m) => m !== null);
2056
2131
  const localProvider = {
2057
2132
  id: provider.id,
@@ -2080,7 +2155,7 @@ async function runProviderDetail(id) {
2080
2155
  if (action === "toggle") {
2081
2156
  const result = toggleProviderEnabled(id);
2082
2157
  if (result.toggled) {
2083
- p5.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
2158
+ p6.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
2084
2159
  }
2085
2160
  return "back";
2086
2161
  }
@@ -2088,17 +2163,17 @@ async function runProviderDetail(id) {
2088
2163
  return code === 0 ? "removed" : "failed";
2089
2164
  }
2090
2165
  async function runProviderApiKeyChange(provider, registry) {
2091
- const entered = await p5.password({
2166
+ const entered = await p6.password({
2092
2167
  message: `Enter the new API key for ${provider.name}`,
2093
2168
  mask: "\u2022"
2094
2169
  });
2095
- if (p5.isCancel(entered)) {
2096
- p5.cancel("Cancelled.");
2170
+ if (p6.isCancel(entered)) {
2171
+ p6.cancel("Cancelled.");
2097
2172
  return 0;
2098
2173
  }
2099
2174
  const key = String(entered).trim();
2100
2175
  if (!key) {
2101
- p5.log.warn("API key cannot be empty.");
2176
+ p6.log.warn("API key cannot be empty.");
2102
2177
  return 1;
2103
2178
  }
2104
2179
  const targetAuthRef = preferredRelayCredentialAuthRef(provider.id, provider.authRef);
@@ -2107,29 +2182,29 @@ async function runProviderApiKeyChange(provider, registry) {
2107
2182
  existing = await readStoredProviderCredential(provider.authRef);
2108
2183
  }
2109
2184
  if (existing && existing !== key) {
2110
- const confirmed = await p5.confirm({
2185
+ const confirmed = await p6.confirm({
2111
2186
  message: "A different key is already stored. Replace it?",
2112
2187
  initialValue: false
2113
2188
  });
2114
- if (p5.isCancel(confirmed) || !confirmed) {
2115
- p5.log.info("Kept the existing stored key.");
2189
+ if (p6.isCancel(confirmed) || !confirmed) {
2190
+ p6.log.info("Kept the existing stored key.");
2116
2191
  return 0;
2117
2192
  }
2118
2193
  }
2119
- const spinner9 = p5.spinner();
2120
- spinner9.start(`Testing ${provider.name} and refreshing models...`);
2194
+ const spinner10 = p6.spinner();
2195
+ spinner10.start(`Testing ${provider.name} and refreshing models...`);
2121
2196
  const result = await refreshProviderModels(provider.id, key, registry);
2122
- spinner9.stop("");
2197
+ spinner10.stop("");
2123
2198
  if (!result.ok) {
2124
- p5.log.error(`${provider.name}: ${result.reason ?? "The new key was rejected."}`);
2199
+ p6.log.error(`${provider.name}: ${result.reason ?? "The new key was rejected."}`);
2125
2200
  return 1;
2126
2201
  }
2127
2202
  const saved = await saveProviderCredential(targetAuthRef, key);
2128
2203
  if (!saved) {
2129
- p5.log.error("The new key works, but the credential store was unavailable \u2014 key was not saved.");
2204
+ p6.log.error("The new key works, but the credential store was unavailable \u2014 key was not saved.");
2130
2205
  return 1;
2131
2206
  }
2132
- p5.log.success(`${provider.name}: key updated and ${result.modelCount ?? 0} model${result.modelCount === 1 ? "" : "s"} available.`);
2207
+ p6.log.success(`${provider.name}: key updated and ${result.modelCount ?? 0} model${result.modelCount === 1 ? "" : "s"} available.`);
2133
2208
  return 0;
2134
2209
  }
2135
2210
  async function runProvidersHub() {
@@ -2157,11 +2232,11 @@ async function runProvidersHub() {
2157
2232
  options.push({ value: "import", label: "\u2192 Import providers from OpenCode CLI", hint: "One-time import" });
2158
2233
  }
2159
2234
  options.push({ value: "done", label: "Done", hint: "" });
2160
- const choice = await p5.select({
2235
+ const choice = await p6.select({
2161
2236
  message: entries.length > 0 ? "Your AI providers" : "Get started",
2162
2237
  options
2163
2238
  });
2164
- if (p5.isCancel(choice) || choice === "done") {
2239
+ if (p6.isCancel(choice) || choice === "done") {
2165
2240
  return lastOperationFailed ? 1 : 0;
2166
2241
  }
2167
2242
  if (choice === "add") {
@@ -2180,10 +2255,10 @@ async function runProvidersHub() {
2180
2255
  const configuredIds = loadRegistry().providers.map((provider) => provider.id);
2181
2256
  const oauthTemplates = listVisibleOAuthTemplates(configuredIds);
2182
2257
  if (oauthTemplates.length === 0) {
2183
- p5.log.info("All visible OAuth providers are already configured.");
2258
+ p6.log.info("All visible OAuth providers are already configured.");
2184
2259
  continue;
2185
2260
  }
2186
- const providerId = await p5.select({
2261
+ const providerId = await p6.select({
2187
2262
  message: "Which provider?",
2188
2263
  options: oauthTemplates.map((template) => ({
2189
2264
  value: template.id,
@@ -2191,7 +2266,7 @@ async function runProvidersHub() {
2191
2266
  hint: "device code"
2192
2267
  }))
2193
2268
  });
2194
- if (!p5.isCancel(providerId)) {
2269
+ if (!p6.isCancel(providerId)) {
2195
2270
  lastOperationFailed = await runProvidersAuth(providerId) !== 0;
2196
2271
  }
2197
2272
  continue;
@@ -2212,7 +2287,7 @@ async function runProvidersHub() {
2212
2287
  async function runProvidersCommand(args) {
2213
2288
  const parsed = parseProvidersArgs(args);
2214
2289
  if (parsed.error) {
2215
- p5.log.error(parsed.error);
2290
+ p6.log.error(parsed.error);
2216
2291
  return 1;
2217
2292
  }
2218
2293
  if (parsed.showHelp) {
@@ -2237,7 +2312,7 @@ async function runProvidersCommand(args) {
2237
2312
 
2238
2313
  // src/codex.ts
2239
2314
  import pc7 from "picocolors";
2240
- import * as p8 from "@clack/prompts";
2315
+ import * as p9 from "@clack/prompts";
2241
2316
  import { join as join6 } from "path";
2242
2317
 
2243
2318
  // src/codex-proxy.ts
@@ -2342,15 +2417,28 @@ function liftAdditionalToolsInput(input, tools) {
2342
2417
  }
2343
2418
  function messageText(content) {
2344
2419
  if (typeof content === "string") return content;
2345
- return (content ?? []).map((p15) => p15.type === "output_text" || p15.type === "input_text" || p15.type === "text" ? p15.text ?? "" : "").join("");
2420
+ return (content ?? []).map((p16) => p16.type === "output_text" || p16.type === "input_text" || p16.type === "text" ? p16.text ?? "" : "").join("");
2421
+ }
2422
+ function messageContent(content) {
2423
+ if (typeof content === "string") return content;
2424
+ const parts = (content ?? []).flatMap((part) => {
2425
+ if (part.type === "output_text" || part.type === "input_text" || part.type === "text") {
2426
+ return typeof part.text === "string" ? [{ type: "text", text: part.text }] : [];
2427
+ }
2428
+ if (part.type === "input_image" && typeof part.image_url === "string" && part.image_url.trim()) {
2429
+ return [{ type: "image", image: part.image_url }];
2430
+ }
2431
+ return [];
2432
+ });
2433
+ return parts.length > 0 ? parts : [{ type: "text", text: "" }];
2346
2434
  }
2347
2435
  function extractDeveloperAndInstructions(items, instructions) {
2348
2436
  const developerParts = [];
2349
2437
  const remaining = [];
2350
2438
  for (const item of items) {
2351
2439
  if ("role" in item && item.role === "developer") {
2352
- const text5 = messageText(item.content);
2353
- if (text5.trim()) developerParts.push(text5.trim());
2440
+ const text6 = messageText(item.content);
2441
+ if (text6.trim()) developerParts.push(text6.trim());
2354
2442
  } else {
2355
2443
  remaining.push(item);
2356
2444
  }
@@ -2423,11 +2511,11 @@ function ensureUserFirst(messages) {
2423
2511
  function reasoningSummaryText(item) {
2424
2512
  return (item.summary ?? []).map((part) => part.type === "summary_text" ? part.text ?? "" : "").join("");
2425
2513
  }
2426
- function makeReasoningOutputItem(id, text5) {
2514
+ function makeReasoningOutputItem(id, text6) {
2427
2515
  return {
2428
2516
  id,
2429
2517
  type: "reasoning",
2430
- summary: text5.trim() ? [{ type: "summary_text", text: text5 }] : []
2518
+ summary: text6.trim() ? [{ type: "summary_text", text: text6 }] : []
2431
2519
  };
2432
2520
  }
2433
2521
  function translateResponsesInput(input, instructions, npm, toolContext = createCodexToolContext()) {
@@ -2528,9 +2616,9 @@ function translateResponsesInput(input, instructions, npm, toolContext = createC
2528
2616
  }]
2529
2617
  });
2530
2618
  } else if (item.type === "agent_message") {
2531
- const text5 = messageText(item.content);
2532
- if (text5.trim()) {
2533
- messages.push({ role: "user", content: [{ type: "text", text: text5 }] });
2619
+ const text6 = messageText(item.content);
2620
+ if (text6.trim()) {
2621
+ messages.push({ role: "user", content: [{ type: "text", text: text6 }] });
2534
2622
  }
2535
2623
  } else if (item.type === "compaction" || item.type === "context_compaction") {
2536
2624
  const summary = decodeCompactionContent(item.encrypted_content) ?? "";
@@ -2540,8 +2628,8 @@ ${summary}` }] });
2540
2628
  }
2541
2629
  } else if ("role" in item) {
2542
2630
  const role = item.role === "assistant" ? "assistant" : "user";
2543
- const text5 = messageText(item.content);
2544
- messages.push({ role, content: [{ type: "text", text: text5 }] });
2631
+ const content = messageContent(item.content);
2632
+ messages.push({ role, content });
2545
2633
  }
2546
2634
  }
2547
2635
  return {
@@ -2574,7 +2662,7 @@ function translateResponsesTools(tools, options = {}) {
2574
2662
  if (options.maxTools !== void 0 && toolCount >= options.maxTools) return;
2575
2663
  out[name] = tool({
2576
2664
  description: description ?? "",
2577
- inputSchema: jsonSchema(parameters ?? { type: "object", properties: {} })
2665
+ inputSchema: jsonSchema(normalizeToolSchemaForNpm(parameters ?? { type: "object", properties: {} }, options.npm))
2578
2666
  });
2579
2667
  toolCount++;
2580
2668
  };
@@ -2601,6 +2689,9 @@ function translateResponsesTools(tools, options = {}) {
2601
2689
  }
2602
2690
  return Object.keys(out).length ? out : void 0;
2603
2691
  }
2692
+ function omitsMaxOutputTokens(metadata) {
2693
+ return metadata?.providerId === "openrouter" || (metadata?.apiBaseUrl?.includes("openrouter.ai") ?? false);
2694
+ }
2604
2695
  function translateResponsesRequest(body, npm, metadata, options = {}) {
2605
2696
  const toolContext = createCodexToolContext();
2606
2697
  let effectiveTools = body.tools ?? [];
@@ -2617,13 +2708,13 @@ function translateResponsesRequest(body, npm, metadata, options = {}) {
2617
2708
  thinkingProviderOptions(npm),
2618
2709
  effortProviderOptions(npm, effort, metadata?.upstreamModelId ?? body.model, metadata)
2619
2710
  );
2620
- const tools = translateResponsesTools([...effectiveTools, ...deferredTools], options);
2711
+ const tools = translateResponsesTools([...effectiveTools, ...deferredTools], { ...options, npm });
2621
2712
  return {
2622
2713
  instructions: system,
2623
2714
  messages,
2624
2715
  tools,
2625
2716
  toolContext,
2626
- maxOutputTokens: body.max_output_tokens,
2717
+ maxOutputTokens: omitsMaxOutputTokens(metadata) ? void 0 : body.max_output_tokens,
2627
2718
  temperature: body.temperature,
2628
2719
  providerOptions,
2629
2720
  headers: options.requestHeaders
@@ -2873,8 +2964,8 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2873
2964
  loopDetected,
2874
2965
  aborted: true
2875
2966
  });
2876
- emit("response.completed", {
2877
- type: "response.completed",
2967
+ emit("response.failed", {
2968
+ type: "response.failed",
2878
2969
  response: {
2879
2970
  id: responseId,
2880
2971
  object: "response",
@@ -2904,8 +2995,8 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2904
2995
  if (is429) {
2905
2996
  writeResponsesRateLimitStream(modelId, msg, write);
2906
2997
  } else {
2907
- emit("response.completed", {
2908
- type: "response.completed",
2998
+ emit("response.failed", {
2999
+ type: "response.failed",
2909
3000
  response: {
2910
3001
  id: responseId,
2911
3002
  object: "response",
@@ -3251,8 +3342,8 @@ function responsesErrorBody(modelId, message, statusCode = 401) {
3251
3342
  };
3252
3343
  }
3253
3344
  function writeResponsesErrorStream(modelId, message, write, statusCode = 401) {
3254
- write(sseChunk("response.completed", {
3255
- type: "response.completed",
3345
+ write(sseChunk("response.failed", {
3346
+ type: "response.failed",
3256
3347
  response: responsesErrorBody(modelId, message, statusCode)
3257
3348
  }));
3258
3349
  }
@@ -3343,6 +3434,14 @@ function classifyCodexDispatch(modelId, relayRoutes, nativeModelIds) {
3343
3434
  if (route) return { kind: "relay", route };
3344
3435
  return { kind: "unknown", modelId };
3345
3436
  }
3437
+ function classifyCodexMixedDispatch(input) {
3438
+ if (input.subagentRoute) return { kind: "relay", route: input.subagentRoute };
3439
+ const dispatch = classifyCodexDispatch(input.modelId, input.relayRoutes, input.nativeModelIds);
3440
+ if (dispatch.kind === "unknown" && input.markedSubagent) {
3441
+ return { kind: "native", modelId: input.modelId };
3442
+ }
3443
+ return dispatch;
3444
+ }
3346
3445
  function createMixedProxyCapability() {
3347
3446
  return randomBytes(32).toString("base64url");
3348
3447
  }
@@ -3470,16 +3569,20 @@ function allowlistedNativeHeaders(inboundHeaders) {
3470
3569
  function prepareNativeCodexBody(body) {
3471
3570
  if (!Array.isArray(body.input)) return body;
3472
3571
  let changed = false;
3473
- const input = body.input.map((item) => {
3474
- if (!item || typeof item !== "object" || Array.isArray(item)) return item;
3572
+ const input = body.input.flatMap((item) => {
3573
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [item];
3475
3574
  const record = item;
3476
- if (record.type !== "compaction" && record.type !== "context_compaction") return item;
3575
+ if (record.type === "reasoning" && typeof record.encrypted_content !== "string") {
3576
+ changed = true;
3577
+ return [];
3578
+ }
3579
+ if (record.type !== "compaction" && record.type !== "context_compaction") return [item];
3477
3580
  const summary = decodeCompactionContent(
3478
3581
  typeof record.encrypted_content === "string" ? record.encrypted_content : void 0
3479
3582
  );
3480
- if (summary === null) return item;
3583
+ if (summary === null) return [item];
3481
3584
  changed = true;
3482
- return {
3585
+ return [{
3483
3586
  type: "message",
3484
3587
  role: "user",
3485
3588
  content: [{
@@ -3487,14 +3590,14 @@ function prepareNativeCodexBody(body) {
3487
3590
  text: `[Summary of earlier conversation]
3488
3591
  ${summary}`
3489
3592
  }]
3490
- };
3593
+ }];
3491
3594
  });
3492
3595
  return changed ? { ...body, input } : body;
3493
3596
  }
3494
3597
  function prepareNativeHttpBody(body) {
3495
- const text5 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3598
+ const text6 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3496
3599
  try {
3497
- const parsed = JSON.parse(text5);
3600
+ const parsed = JSON.parse(text6);
3498
3601
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return body;
3499
3602
  const prepared = prepareNativeCodexBody(parsed);
3500
3603
  return prepared === parsed ? body : JSON.stringify(prepared);
@@ -3651,11 +3754,11 @@ function parseFunctionCalls(value) {
3651
3754
  (v) => v && typeof v === "object" && v.type === "function_call"
3652
3755
  );
3653
3756
  }
3654
- function parsePayloadResponse(text5) {
3757
+ function parsePayloadResponse(text6) {
3655
3758
  const relayIds = /* @__PURE__ */ new Set();
3656
3759
  const completedPayloads = [];
3657
3760
  let argumentDeltas = "";
3658
- for (const line of text5.split(/\r?\n/)) {
3761
+ for (const line of text6.split(/\r?\n/)) {
3659
3762
  if (!line.startsWith("data:")) continue;
3660
3763
  const data = line.slice(5).trim();
3661
3764
  if (!data || data === "[DONE]") continue;
@@ -3681,7 +3784,7 @@ function parsePayloadResponse(text5) {
3681
3784
  }
3682
3785
  }
3683
3786
  try {
3684
- for (const call of parseFunctionCalls(JSON.parse(text5))) {
3787
+ for (const call of parseFunctionCalls(JSON.parse(text6))) {
3685
3788
  if (call.name !== AGENT_PAYLOAD_RELAY_TOOL) continue;
3686
3789
  const payload = parsePayloadArguments(call.arguments);
3687
3790
  if (payload !== void 0) completedPayloads.push(payload);
@@ -3746,9 +3849,9 @@ function createNativePayloadRelay(options) {
3746
3849
  });
3747
3850
  const response = await fetchImpl(url, { method: "POST", headers: nativeHeaders(context.headers), body, redirect: "manual", signal: context.signal });
3748
3851
  if (!response.ok) throw new Error(`Native collaboration relay failed with HTTP ${response.status}`);
3749
- const text5 = await response.text();
3750
- if (Buffer.byteLength(text5, "utf8") > NATIVE_PAYLOAD_MAX_BYTES) throw new Error("Native collaboration relay response exceeded its size limit");
3751
- const payload = parsePayloadResponse(text5);
3852
+ const text6 = await response.text();
3853
+ if (Buffer.byteLength(text6, "utf8") > NATIVE_PAYLOAD_MAX_BYTES) throw new Error("Native collaboration relay response exceeded its size limit");
3854
+ const payload = parsePayloadResponse(text6);
3752
3855
  cacheValue(key, payload, Date.now() + 15 * 6e4);
3753
3856
  return payload;
3754
3857
  },
@@ -3864,9 +3967,9 @@ function estimateCodexRequestChars(params) {
3864
3967
  if (Array.isArray(msg.content)) {
3865
3968
  for (const part of msg.content) {
3866
3969
  if (!part || typeof part !== "object") continue;
3867
- const p15 = part;
3868
- if (typeof p15["text"] === "string") {
3869
- chars += p15["text"].length;
3970
+ const p16 = part;
3971
+ if (typeof p16["text"] === "string") {
3972
+ chars += p16["text"].length;
3870
3973
  } else {
3871
3974
  chars += JSON.stringify(part).length;
3872
3975
  }
@@ -3877,15 +3980,15 @@ function estimateCodexRequestChars(params) {
3877
3980
  }
3878
3981
  return chars;
3879
3982
  }
3880
- function clipTextForContext(text5, maxChars) {
3881
- if (text5.length <= maxChars) return text5;
3983
+ function clipTextForContext(text6, maxChars) {
3984
+ if (text6.length <= maxChars) return text6;
3882
3985
  const marker = `
3883
3986
 
3884
- [... ${text5.length} chars clipped from oversized context item ...]
3987
+ [... ${text6.length} chars clipped from oversized context item ...]
3885
3988
 
3886
3989
  `;
3887
3990
  const edge = Math.max(1, Math.floor((maxChars - marker.length) / 2));
3888
- return `${text5.slice(0, edge)}${marker}${text5.slice(-edge)}`;
3991
+ return `${text6.slice(0, edge)}${marker}${text6.slice(-edge)}`;
3889
3992
  }
3890
3993
  function clipLargeTextParts(params, maxCharsPerPart) {
3891
3994
  const messages = params.messages.map((msg) => {
@@ -3897,9 +4000,9 @@ function clipLargeTextParts(params, maxCharsPerPart) {
3897
4000
  ...msg,
3898
4001
  content: msg.content.map((part) => {
3899
4002
  if (!part || typeof part !== "object") return part;
3900
- const p15 = part;
3901
- if (typeof p15.text !== "string") return part;
3902
- return { ...p15, text: clipTextForContext(p15.text, maxCharsPerPart) };
4003
+ const p16 = part;
4004
+ if (typeof p16.text !== "string") return part;
4005
+ return { ...p16, text: clipTextForContext(p16.text, maxCharsPerPart) };
3903
4006
  })
3904
4007
  };
3905
4008
  });
@@ -3933,7 +4036,7 @@ var COMPACTION_PROMPT_MARKER = "You are performing a CONTEXT CHECKPOINT COMPACTI
3933
4036
  function inputItemText(content) {
3934
4037
  if (typeof content === "string") return content;
3935
4038
  if (!Array.isArray(content)) return "";
3936
- return content.map((p15) => p15 && typeof p15 === "object" && typeof p15.text === "string" ? p15.text : "").join("");
4039
+ return content.map((p16) => p16 && typeof p16 === "object" && typeof p16.text === "string" ? p16.text : "").join("");
3937
4040
  }
3938
4041
  function isLikelyCodexCompactionRequest(body) {
3939
4042
  if (!Array.isArray(body.input)) return false;
@@ -3955,6 +4058,13 @@ function isCodexV2CompactionRequest(body) {
3955
4058
  );
3956
4059
  }
3957
4060
  var COMPACTION_MAX_OUTPUT_TOKENS = 4e3;
4061
+ function streamOutcome(failure, okStatus) {
4062
+ if (!failure) return { outcome: "ok", status: okStatus };
4063
+ return {
4064
+ outcome: "error",
4065
+ status: failure.errorMessage ? upstreamHttpStatus(void 0, failure.errorMessage) : "stream-aborted"
4066
+ };
4067
+ }
3958
4068
  function protectCodexCompactionParams(body, params, contextWindow) {
3959
4069
  if (!isLikelyCodexCompactionRequest(body)) {
3960
4070
  return trimToContextLimit(params, contextWindow);
@@ -4104,11 +4214,11 @@ async function startCodexProxy(routes, options = {}) {
4104
4214
  }));
4105
4215
  }
4106
4216
  return new Promise((resolve2, reject2) => {
4107
- const log14 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
4217
+ const log15 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
4108
4218
  };
4109
4219
  if (debug) resetCodexBodyDumpLog();
4110
4220
  const onRejection = (reason) => {
4111
- if (debug) log14(`unhandled-rejection: ${formatUpstreamError(reason)}`);
4221
+ if (debug) log15(`unhandled-rejection: ${formatUpstreamError(reason)}`);
4112
4222
  };
4113
4223
  process.on("unhandledRejection", onRejection);
4114
4224
  const server = createServer(async (req, res) => {
@@ -4125,7 +4235,7 @@ async function startCodexProxy(routes, options = {}) {
4125
4235
  }
4126
4236
  const effectivePath = mixedPath?.suffix ?? url;
4127
4237
  if (debug) {
4128
- log14(`-> ${req.method} ${url} content-type=${req.headers["content-type"] ?? "(none)"} content-encoding=${req.headers["content-encoding"] ?? "(none)"} content-length=${req.headers["content-length"] ?? "(none)"}`);
4238
+ log15(`-> ${req.method} ${url} content-type=${req.headers["content-type"] ?? "(none)"} content-encoding=${req.headers["content-encoding"] ?? "(none)"} content-length=${req.headers["content-length"] ?? "(none)"}`);
4129
4239
  }
4130
4240
  if (!requireAuth && req.method === "POST") {
4131
4241
  const origin = req.headers.origin;
@@ -4210,7 +4320,7 @@ async function startCodexProxy(routes, options = {}) {
4210
4320
  rawBody = await readBody(req);
4211
4321
  } catch (err) {
4212
4322
  if (debug) {
4213
- log14(`Error: failed to read/decode request body on POST ${url}: ${formatUpstreamError(err)} content-encoding=${req.headers["content-encoding"] ?? "(none)"}`);
4323
+ log15(`Error: failed to read/decode request body on POST ${url}: ${formatUpstreamError(err)} content-encoding=${req.headers["content-encoding"] ?? "(none)"}`);
4214
4324
  }
4215
4325
  sendJson(res, 400, { error: { message: "Invalid request body", type: "invalid_request_error" } });
4216
4326
  return;
@@ -4221,7 +4331,7 @@ async function startCodexProxy(routes, options = {}) {
4221
4331
  } catch (err) {
4222
4332
  if (debug) {
4223
4333
  const headers = JSON.stringify(req.headers);
4224
- log14(`Error: Invalid JSON body on POST ${url}: ${formatUpstreamError(err)} headers=${headers} rawBody=${JSON.stringify(rawBody.slice(0, 2e3))}`);
4334
+ log15(`Error: Invalid JSON body on POST ${url}: ${formatUpstreamError(err)} headers=${headers} rawBody=${JSON.stringify(rawBody.slice(0, 2e3))}`);
4225
4335
  }
4226
4336
  sendJson(res, 400, { error: { message: "Invalid JSON body", type: "invalid_request_error" } });
4227
4337
  return;
@@ -4231,7 +4341,7 @@ async function startCodexProxy(routes, options = {}) {
4231
4341
  const inputItems = Array.isArray(body.input) ? body.input.length : typeof body.input === "string" ? 1 : 0;
4232
4342
  const tools = Array.isArray(body.tools) ? body.tools : [];
4233
4343
  const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
4234
- log14(`request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${rawBody.length} tools=[${toolNames || "none"}]`);
4344
+ log15(`request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${rawBody.length} tools=[${toolNames || "none"}]`);
4235
4345
  appendCodexBodyDump({
4236
4346
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4237
4347
  transport: "http",
@@ -4245,83 +4355,77 @@ async function startCodexProxy(routes, options = {}) {
4245
4355
  for (const t of mcpTools) {
4246
4356
  const mt = t;
4247
4357
  const subTools = mt.type === "namespace" && Array.isArray(mt.tools) ? ` subTools=[${mt.tools.length}]` : "";
4248
- log14(` mcp-tool: name=${mt.name} type=${mt.type} desc=${JSON.stringify(String(mt.description ?? "")).slice(0, 120)}${subTools}`);
4358
+ log15(` mcp-tool: name=${mt.name} type=${mt.type} desc=${JSON.stringify(String(mt.description ?? "")).slice(0, 120)}${subTools}`);
4249
4359
  }
4250
4360
  }
4251
4361
  const modelId = String(body.model ?? "");
4252
4362
  const markedSubagent = Boolean(mixedNative && isCodexSubagentRequest(body, req.headers));
4253
4363
  const subagentRoute = mixedNative && markedSubagent ? resolveCodexSubagentRoute(routes, mixedNative.subagentRouteModelId, body, req.headers) : void 0;
4254
4364
  if (debug && markedSubagent) {
4255
- log14(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4256
- }
4257
- if (mixedNative && markedSubagent && !subagentRoute) {
4258
- audit({ transport: "http", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4259
- sendJson(res, 503, {
4260
- error: {
4261
- message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
4262
- type: "service_unavailable"
4263
- }
4264
- });
4265
- return;
4365
+ log15(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4266
4366
  }
4267
4367
  if (mixedNative) {
4268
- if (!markedSubagent) {
4269
- const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4270
- if (dispatch.kind === "unknown") {
4271
- audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4272
- sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4273
- return;
4274
- }
4275
- if (dispatch.kind === "native") {
4368
+ const dispatch = classifyCodexMixedDispatch({
4369
+ modelId,
4370
+ markedSubagent,
4371
+ subagentRoute,
4372
+ relayRoutes: routes,
4373
+ nativeModelIds: mixedNative.nativeModelIds
4374
+ });
4375
+ if (dispatch.kind === "unknown") {
4376
+ audit({ transport: "http", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4377
+ sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4378
+ return;
4379
+ }
4380
+ if (dispatch.kind === "native") {
4381
+ audit({
4382
+ transport: "http",
4383
+ requestedModel: modelId,
4384
+ dispatch: "native",
4385
+ phase: "dispatch",
4386
+ provider: "openai-native",
4387
+ routeModel: modelId,
4388
+ upstreamModel: modelId
4389
+ });
4390
+ const controller = new AbortController();
4391
+ req.once("aborted", () => controller.abort());
4392
+ try {
4393
+ const nativeResponse = await forwardNativeCodexHttp({
4394
+ body: rawBody,
4395
+ inboundHeaders: req.headers,
4396
+ nativeUrl: mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/\/$/, "")}/responses` : NATIVE_CODEX_RESPONSES_URL,
4397
+ signal: controller.signal,
4398
+ fetchImpl: mixedNative.nativeFetchImpl
4399
+ });
4400
+ const contentType = nativeResponse.headers.get("content-type");
4401
+ res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4402
+ res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4276
4403
  audit({
4277
4404
  transport: "http",
4278
4405
  requestedModel: modelId,
4279
4406
  dispatch: "native",
4280
- phase: "dispatch",
4407
+ phase: "complete",
4281
4408
  provider: "openai-native",
4282
4409
  routeModel: modelId,
4283
- upstreamModel: modelId
4410
+ upstreamModel: modelId,
4411
+ outcome: nativeResponse.ok ? "ok" : "error",
4412
+ status: nativeResponse.status
4284
4413
  });
4285
- const controller = new AbortController();
4286
- req.once("aborted", () => controller.abort());
4287
- try {
4288
- const nativeResponse = await forwardNativeCodexHttp({
4289
- body: rawBody,
4290
- inboundHeaders: req.headers,
4291
- nativeUrl: mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/\/$/, "")}/responses` : NATIVE_CODEX_RESPONSES_URL,
4292
- signal: controller.signal,
4293
- fetchImpl: mixedNative.nativeFetchImpl
4294
- });
4295
- const contentType = nativeResponse.headers.get("content-type");
4296
- res.writeHead(nativeResponse.status, contentType ? { "content-type": contentType } : void 0);
4297
- res.end(Buffer.from(await nativeResponse.arrayBuffer()));
4298
- audit({
4299
- transport: "http",
4300
- requestedModel: modelId,
4301
- dispatch: "native",
4302
- phase: "complete",
4303
- provider: "openai-native",
4304
- routeModel: modelId,
4305
- upstreamModel: modelId,
4306
- outcome: nativeResponse.ok ? "ok" : "error",
4307
- status: nativeResponse.status
4308
- });
4309
- } catch (err) {
4310
- audit({
4311
- transport: "http",
4312
- requestedModel: modelId,
4313
- dispatch: "native",
4314
- phase: "complete",
4315
- provider: "openai-native",
4316
- routeModel: modelId,
4317
- upstreamModel: modelId,
4318
- outcome: "error",
4319
- status: "forward-failed"
4320
- });
4321
- if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4322
- }
4323
- return;
4414
+ } catch (err) {
4415
+ audit({
4416
+ transport: "http",
4417
+ requestedModel: modelId,
4418
+ dispatch: "native",
4419
+ phase: "complete",
4420
+ provider: "openai-native",
4421
+ routeModel: modelId,
4422
+ upstreamModel: modelId,
4423
+ outcome: "error",
4424
+ status: "forward-failed"
4425
+ });
4426
+ if (!res.writableEnded) sendJson(res, 502, { error: { message: "Native Codex request failed", type: "upstream_error" } });
4324
4427
  }
4428
+ return;
4325
4429
  }
4326
4430
  }
4327
4431
  let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
@@ -4330,12 +4434,12 @@ async function startCodexProxy(routes, options = {}) {
4330
4434
  const fallbackLm = fallbackRoute ? models.get(fallbackRoute.modelId) : void 0;
4331
4435
  if (fallbackRoute && fallbackLm) {
4332
4436
  if (debug) {
4333
- log14(`resolveModel fallback: requested="${modelId}" \u2192 ${fallbackRoute.modelId}`);
4437
+ log15(`resolveModel fallback: requested="${modelId}" \u2192 ${fallbackRoute.modelId}`);
4334
4438
  }
4335
4439
  resolved = { route: fallbackRoute, languageModel: fallbackLm };
4336
4440
  } else {
4337
4441
  if (debug) {
4338
- log14(`resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
4442
+ log15(`resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
4339
4443
  }
4340
4444
  sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4341
4445
  return;
@@ -4384,20 +4488,20 @@ async function startCodexProxy(routes, options = {}) {
4384
4488
  const before = params.messages.length;
4385
4489
  const estimatedChars = estimateCodexRequestChars(params);
4386
4490
  const compaction = isLikelyCodexCompactionRequest(body);
4387
- if (debug) log14(`context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before}`);
4491
+ if (debug) log15(`context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before}`);
4388
4492
  params = protectCodexCompactionParams(body, params, route.contextWindow);
4389
4493
  if (debug && params.messages.length < before) {
4390
- log14(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
4494
+ log15(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
4391
4495
  }
4392
4496
  }
4393
4497
  const v2Compaction = isCodexV2CompactionRequest(body);
4394
4498
  if (v2Compaction) {
4395
4499
  params = appendCompactionInstruction(params);
4396
- if (debug) log14(`compaction v2: synthesizing single compaction item for model=${route.modelId}`);
4500
+ if (debug) log15(`compaction v2: synthesizing single compaction item for model=${route.modelId}`);
4397
4501
  }
4398
4502
  if (debug) {
4399
4503
  const effort = body.reasoning?.effort;
4400
- log14(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
4504
+ log15(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
4401
4505
  }
4402
4506
  if (body.stream) {
4403
4507
  res.writeHead(200, {
@@ -4420,18 +4524,20 @@ async function startCodexProxy(routes, options = {}) {
4420
4524
  }
4421
4525
  }
4422
4526
  };
4527
+ let streamFailure;
4423
4528
  try {
4424
4529
  if (v2Compaction) {
4425
4530
  await streamCompactionResponse(languageModel, params, modelId, write);
4426
4531
  } else
4427
4532
  await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
4533
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
4428
4534
  if (debug) {
4429
4535
  const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
4430
- log14(`response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
4536
+ log15(`response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
4431
4537
  }
4432
4538
  }, (progress) => {
4433
4539
  if (debug) {
4434
- log14(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
4540
+ log15(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
4435
4541
  }
4436
4542
  });
4437
4543
  audit({
@@ -4442,8 +4548,7 @@ async function startCodexProxy(routes, options = {}) {
4442
4548
  provider: route.providerId ?? "relay",
4443
4549
  routeModel: route.modelId,
4444
4550
  upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
4445
- outcome: "ok",
4446
- status: 200
4551
+ ...streamOutcome(streamFailure, 200)
4447
4552
  });
4448
4553
  } catch (err) {
4449
4554
  const msg = formatUpstreamError(err);
@@ -4459,7 +4564,7 @@ async function startCodexProxy(routes, options = {}) {
4459
4564
  outcome: "error",
4460
4565
  status
4461
4566
  });
4462
- if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4567
+ if (debug) log15(`sdk error: ${route.modelId}: ${msg}`);
4463
4568
  if (status === 429) {
4464
4569
  writeResponsesRateLimitStream(modelId, msg, write);
4465
4570
  } else {
@@ -4505,7 +4610,7 @@ async function startCodexProxy(routes, options = {}) {
4505
4610
  outcome: "error",
4506
4611
  status
4507
4612
  });
4508
- if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4613
+ if (debug) log15(`sdk error: ${route.modelId}: ${msg}`);
4509
4614
  if (status === 429) {
4510
4615
  sendJson(res, 200, responsesRateLimitBody(modelId, msg));
4511
4616
  } else {
@@ -4515,7 +4620,7 @@ async function startCodexProxy(routes, options = {}) {
4515
4620
  }
4516
4621
  } catch (err) {
4517
4622
  const msg = formatUpstreamError(err);
4518
- log14(`handler error: ${msg}`);
4623
+ log15(`handler error: ${msg}`);
4519
4624
  sendJson(res, 500, { error: { message: msg, type: "api_error" } });
4520
4625
  }
4521
4626
  return;
@@ -4533,6 +4638,7 @@ async function startCodexProxy(routes, options = {}) {
4533
4638
  if (buf.length < 2) return null;
4534
4639
  const b0 = buf[0];
4535
4640
  const b1 = buf[1];
4641
+ const fin = (b0 & 128) !== 0;
4536
4642
  const masked = (b1 & 128) !== 0;
4537
4643
  let payloadLen = b1 & 127;
4538
4644
  let offset = 2;
@@ -4543,11 +4649,11 @@ async function startCodexProxy(routes, options = {}) {
4543
4649
  } else if (payloadLen === 127) {
4544
4650
  if (buf.length < 10) return null;
4545
4651
  const declaredLength = buf.readBigUInt64BE(2);
4546
- if (declaredLength > BigInt(MAX_CODEX_REQUEST_BYTES)) return { text: "", complete: true, opcode: -1 };
4652
+ if (declaredLength > BigInt(MAX_CODEX_REQUEST_BYTES)) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: buf.length };
4547
4653
  payloadLen = Number(declaredLength);
4548
4654
  offset = 10;
4549
4655
  }
4550
- if (payloadLen > MAX_CODEX_REQUEST_BYTES) return { text: "", complete: true, opcode: -1 };
4656
+ if (payloadLen > MAX_CODEX_REQUEST_BYTES) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: buf.length };
4551
4657
  const maskLen = masked ? 4 : 0;
4552
4658
  if (buf.length < offset + maskLen + payloadLen) return null;
4553
4659
  const mask = masked ? buf.slice(offset, offset + 4) : null;
@@ -4557,11 +4663,11 @@ async function startCodexProxy(routes, options = {}) {
4557
4663
  payload[i] = buf[offset + i] ^ (mask ? mask[i % 4] : 0);
4558
4664
  }
4559
4665
  const opcode = b0 & 15;
4560
- if (![1, 8, 9, 10].includes(opcode)) return { text: "", complete: true, opcode };
4561
- return { text: payload.toString("utf8"), complete: true, opcode };
4666
+ if (![0, 1, 8, 9, 10].includes(opcode)) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: offset + payloadLen };
4667
+ return { payload, complete: true, opcode, fin, consumed: offset + payloadLen };
4562
4668
  }
4563
- function wsEncodeTextFrame(text5) {
4564
- const payload = Buffer.from(text5, "utf8");
4669
+ function wsEncodeTextFrame(text6) {
4670
+ const payload = Buffer.from(text6, "utf8");
4565
4671
  const len = payload.length;
4566
4672
  let header;
4567
4673
  if (len < 126) {
@@ -4629,6 +4735,7 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4629
4735
  `
4630
4736
  );
4631
4737
  let frameBuf = Buffer.alloc(0);
4738
+ let fragmentedText;
4632
4739
  let externalActive = false;
4633
4740
  let nativeActive = false;
4634
4741
  let nativeUpstream;
@@ -4693,85 +4800,105 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4693
4800
  };
4694
4801
  const onData = (chunk) => {
4695
4802
  frameBuf = Buffer.concat([frameBuf, chunk]);
4696
- const frame = wsDecodeFrame(frameBuf);
4697
- if (!frame) return;
4698
- frameBuf = Buffer.alloc(0);
4699
- if (frame.opcode === 9) {
4700
- socket.write(wsPongFrame(frame.text));
4701
- return;
4702
- }
4703
- if (frame.opcode === 8) {
4704
- closeSocket();
4705
- return;
4706
- }
4707
- if (frame.opcode === -1) {
4708
- socket.write(wsCloseFrame(1009));
4709
- socket.end();
4710
- return;
4711
- }
4712
- if (frame.opcode !== 1) {
4713
- socket.write(wsCloseFrame(1003));
4714
- socket.end();
4715
- return;
4716
- }
4717
- if (externalActive) {
4718
- closeSocket(1008);
4719
- return;
4720
- }
4721
- void (async () => {
4722
- let body;
4723
- try {
4724
- body = JSON.parse(frame.text);
4725
- } catch {
4726
- if (debug) log14(`WS Error: Invalid JSON body: rawBody=${JSON.stringify(frame.text.slice(0, 2e3))}`);
4727
- sendWsEvent(`event: error
4728
- data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_request_error" } })}
4729
-
4730
- `);
4803
+ while (!socketClosing) {
4804
+ const frame = wsDecodeFrame(frameBuf);
4805
+ if (!frame) return;
4806
+ frameBuf = frameBuf.slice(frame.consumed);
4807
+ if (frame.opcode === 9) {
4808
+ if (!frame.fin || frame.payload.length > 125) {
4809
+ socket.write(wsCloseFrame(1002));
4810
+ socket.end();
4811
+ return;
4812
+ }
4813
+ socket.write(wsPongFrame(frame.payload.toString("utf8")));
4814
+ continue;
4815
+ }
4816
+ if (frame.opcode === 10) continue;
4817
+ if (frame.opcode === 8) {
4731
4818
  closeSocket();
4732
4819
  return;
4733
4820
  }
4734
- if (debug) {
4735
- const prevId = body.previous_response_id ?? null;
4736
- const inputItems = Array.isArray(body.input) ? body.input.length : typeof body.input === "string" ? 1 : 0;
4737
- const tools = Array.isArray(body.tools) ? body.tools : [];
4738
- const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
4739
- log14(`WS request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${frame.text.length} tools=[${toolNames || "none"}]`);
4740
- const reasoning = body.reasoning && typeof body.reasoning === "object" ? Object.keys(body.reasoning).join(",") : typeof body.reasoning;
4741
- const clientMetadata = body.client_metadata && typeof body.client_metadata === "object" ? Object.keys(body.client_metadata).join(",") : typeof body.client_metadata;
4742
- log14(`WS request shape: stream=${String(body.stream)} store=${String(body.store)} generate=${String(body.generate)} parallel_tool_calls=${String(body.parallel_tool_calls)} reasoning_keys=[${reasoning || "none"}] include=${Array.isArray(body.include) ? body.include.join(",") : String(body.include)} client_metadata_keys=[${clientMetadata || "none"}]`);
4743
- appendCodexBodyDump({
4744
- ts: (/* @__PURE__ */ new Date()).toISOString(),
4745
- transport: "ws",
4746
- direction: "request",
4747
- model: String(body.model ?? ""),
4748
- previous_response_id: prevId,
4749
- tools: body.tools,
4750
- input: body.input
4751
- });
4821
+ if (frame.opcode === -1) {
4822
+ socket.write(wsCloseFrame(1009));
4823
+ socket.end();
4824
+ return;
4752
4825
  }
4753
- const modelId = String(body.model ?? "");
4754
- currentRequestModel = modelId;
4755
- const markedSubagent = Boolean(mixedNative && isCodexSubagentRequest(body, req.headers));
4756
- const subagentRoute = mixedNative && markedSubagent ? resolveCodexSubagentRoute(routes, mixedNative.subagentRouteModelId, body, req.headers) : void 0;
4757
- if (debug && markedSubagent) {
4758
- log14(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4826
+ if (frame.opcode === 1) {
4827
+ if (fragmentedText) {
4828
+ socket.write(wsCloseFrame(1002));
4829
+ socket.end();
4830
+ return;
4831
+ }
4832
+ if (!frame.fin) {
4833
+ fragmentedText = [frame.payload];
4834
+ continue;
4835
+ }
4836
+ } else if (frame.opcode === 0) {
4837
+ if (!fragmentedText) {
4838
+ socket.write(wsCloseFrame(1002));
4839
+ socket.end();
4840
+ return;
4841
+ }
4842
+ fragmentedText.push(frame.payload);
4843
+ if (!frame.fin) continue;
4844
+ } else {
4845
+ socket.write(wsCloseFrame(1003));
4846
+ socket.end();
4847
+ return;
4759
4848
  }
4760
- if (mixedNative && markedSubagent && !subagentRoute) {
4761
- audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4762
- sendWsEvent(`event: error
4763
- data: ${JSON.stringify({ error: {
4764
- message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
4765
- type: "service_unavailable"
4766
- } })}
4767
-
4768
- `);
4769
- closeSocket();
4849
+ const frameText = frame.opcode === 0 ? Buffer.concat(fragmentedText ?? []).toString("utf8") : frame.payload.toString("utf8");
4850
+ fragmentedText = void 0;
4851
+ if (externalActive) {
4852
+ closeSocket(1008);
4770
4853
  return;
4771
4854
  }
4772
- if (mixedNative) {
4773
- if (!markedSubagent) {
4774
- const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4855
+ void (async () => {
4856
+ let body;
4857
+ try {
4858
+ body = JSON.parse(frameText);
4859
+ } catch {
4860
+ if (debug) log15(`WS Error: Invalid JSON body: rawBody=${JSON.stringify(frameText.slice(0, 2e3))}`);
4861
+ sendWsEvent(`event: error
4862
+ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_request_error" } })}
4863
+
4864
+ `);
4865
+ closeSocket();
4866
+ return;
4867
+ }
4868
+ if (debug) {
4869
+ const prevId = body.previous_response_id ?? null;
4870
+ const inputItems = Array.isArray(body.input) ? body.input.length : typeof body.input === "string" ? 1 : 0;
4871
+ const tools = Array.isArray(body.tools) ? body.tools : [];
4872
+ const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
4873
+ log15(`WS request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${frameText.length} tools=[${toolNames || "none"}]`);
4874
+ const reasoning = body.reasoning && typeof body.reasoning === "object" ? Object.keys(body.reasoning).join(",") : typeof body.reasoning;
4875
+ const clientMetadata = body.client_metadata && typeof body.client_metadata === "object" ? Object.keys(body.client_metadata).join(",") : typeof body.client_metadata;
4876
+ log15(`WS request shape: stream=${String(body.stream)} store=${String(body.store)} generate=${String(body.generate)} parallel_tool_calls=${String(body.parallel_tool_calls)} reasoning_keys=[${reasoning || "none"}] include=${Array.isArray(body.include) ? body.include.join(",") : String(body.include)} client_metadata_keys=[${clientMetadata || "none"}]`);
4877
+ appendCodexBodyDump({
4878
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4879
+ transport: "ws",
4880
+ direction: "request",
4881
+ model: String(body.model ?? ""),
4882
+ previous_response_id: prevId,
4883
+ tools: body.tools,
4884
+ input: body.input
4885
+ });
4886
+ }
4887
+ const modelId = String(body.model ?? "");
4888
+ currentRequestModel = modelId;
4889
+ const markedSubagent = Boolean(mixedNative && isCodexSubagentRequest(body, req.headers));
4890
+ const subagentRoute = mixedNative && markedSubagent ? resolveCodexSubagentRoute(routes, mixedNative.subagentRouteModelId, body, req.headers) : void 0;
4891
+ if (debug && markedSubagent) {
4892
+ log15(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4893
+ }
4894
+ if (mixedNative) {
4895
+ const dispatch = classifyCodexMixedDispatch({
4896
+ modelId,
4897
+ markedSubagent,
4898
+ subagentRoute,
4899
+ relayRoutes: routes,
4900
+ nativeModelIds: mixedNative.nativeModelIds
4901
+ });
4775
4902
  if (dispatch.kind === "unknown") {
4776
4903
  audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4777
4904
  sendWsEvent(`event: error
@@ -4793,14 +4920,14 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4793
4920
  });
4794
4921
  const nativeBody = prepareNativeCodexBody(body);
4795
4922
  if (debug && nativeBody !== body) {
4796
- log14(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4923
+ log15(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4797
4924
  }
4798
4925
  if (nativeActive && nativeUpstream) {
4799
4926
  if (nativeUpstream.readyState === WebSocket.OPEN) {
4800
- if (debug) log14(`WS native forwarding next turn: model=${modelId}`);
4927
+ if (debug) log15(`WS native forwarding next turn: model=${modelId}`);
4801
4928
  nativeSendTurn?.(nativeBody, modelId);
4802
4929
  } else if (debug) {
4803
- log14(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4930
+ log15(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4804
4931
  }
4805
4932
  return;
4806
4933
  }
@@ -4833,7 +4960,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4833
4960
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4834
4961
  clearTimers();
4835
4962
  if (debug && message) {
4836
- log14(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4963
+ log15(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4837
4964
  }
4838
4965
  if (message && !nativeCompleted) {
4839
4966
  audit({
@@ -4857,7 +4984,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4857
4984
  };
4858
4985
  const sendNativeTurn = (turnBody, turnModelId) => {
4859
4986
  if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4860
- if (debug) log14(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4987
+ if (debug) log15(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4861
4988
  return;
4862
4989
  }
4863
4990
  nativeTurnModelId = turnModelId;
@@ -4868,7 +4995,7 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4868
4995
  };
4869
4996
  try {
4870
4997
  if (debug) {
4871
- log14(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4998
+ log15(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4872
4999
  }
4873
5000
  upstream = new WebSocket(target.url, { headers: target.headers });
4874
5001
  nativeUpstream = upstream;
@@ -4878,11 +5005,11 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4878
5005
  upstream.once("open", () => {
4879
5006
  nativeOpened = true;
4880
5007
  if (connectTimer) clearTimeout(connectTimer);
4881
- if (debug) log14(`WS native upstream open: model=${modelId}`);
5008
+ if (debug) log15(`WS native upstream open: model=${modelId}`);
4882
5009
  sendNativeTurn(nativeBody, modelId);
4883
5010
  });
4884
5011
  upstream.once("unexpected-response", (_request, response) => {
4885
- if (debug) log14(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
5012
+ if (debug) log15(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
4886
5013
  response.resume();
4887
5014
  closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
4888
5015
  });
@@ -4890,10 +5017,10 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4890
5017
  if (socket.destroyed) return;
4891
5018
  nativeFrameCount += 1;
4892
5019
  if (firstFrameTimer) clearTimeout(firstFrameTimer);
4893
- const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
5020
+ const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
4894
5021
  let eventType = "non-json";
4895
5022
  try {
4896
- const parsed = JSON.parse(text5);
5023
+ const parsed = JSON.parse(text6);
4897
5024
  if (typeof parsed.type === "string") eventType = parsed.type;
4898
5025
  if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
4899
5026
  nativeCompleted = true;
@@ -4912,20 +5039,20 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4912
5039
  } catch {
4913
5040
  }
4914
5041
  if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
4915
- log14(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text5.length}`);
5042
+ log15(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text6.length}`);
4916
5043
  }
4917
- socket.write(wsEncodeTextFrame(text5));
5044
+ socket.write(wsEncodeTextFrame(text6));
4918
5045
  });
4919
5046
  upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
4920
5047
  upstream.once("close", (code, reason) => {
4921
5048
  const detail = reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : "";
4922
- if (debug) log14(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
5049
+ if (debug) log15(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
4923
5050
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4924
5051
  nativeActive = false;
4925
5052
  if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
4926
5053
  });
4927
5054
  socket.once("close", () => {
4928
- if (debug) log14(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
5055
+ if (debug) log15(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
4929
5056
  finished = true;
4930
5057
  nativeActive = false;
4931
5058
  nativeSendTurn = void 0;
@@ -4942,148 +5069,149 @@ data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "i
4942
5069
  return;
4943
5070
  }
4944
5071
  }
4945
- }
4946
- externalActive = true;
4947
- let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
4948
- if (!resolved) {
4949
- const fb = routes[0];
4950
- const fbLm = fb ? models.get(fb.modelId) : void 0;
4951
- if (fb && fbLm) {
4952
- if (debug) log14(`WS resolveModel fallback: requested="${modelId}" \u2192 ${fb.modelId}`);
4953
- resolved = { route: fb, languageModel: fbLm };
4954
- } else {
4955
- if (debug) log14(`WS resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
4956
- sendWsEvent(`event: error
5072
+ externalActive = true;
5073
+ let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
5074
+ if (!resolved) {
5075
+ const fb = routes[0];
5076
+ const fbLm = fb ? models.get(fb.modelId) : void 0;
5077
+ if (fb && fbLm) {
5078
+ if (debug) log15(`WS resolveModel fallback: requested="${modelId}" \u2192 ${fb.modelId}`);
5079
+ resolved = { route: fb, languageModel: fbLm };
5080
+ } else {
5081
+ if (debug) log15(`WS resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
5082
+ sendWsEvent(`event: error
4957
5083
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4958
5084
 
4959
5085
  `);
4960
- closeSocket();
4961
- return;
4962
- }
4963
- }
4964
- const { route, languageModel } = resolved;
4965
- const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
4966
- audit({
4967
- transport: "ws",
4968
- requestedModel: modelId,
4969
- dispatch: relayDispatch,
4970
- phase: "dispatch",
4971
- provider: route.providerId ?? "relay",
4972
- routeModel: route.modelId,
4973
- upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
4974
- });
4975
- currentExternalCompletedResponse = void 0;
4976
- currentExternalStateInput = void 0;
4977
- currentExternalConsumedResponseId = void 0;
4978
- const continuation = resolveExternalContinuation(body);
4979
- if (continuation.orphanedResponseId) {
4980
- if (debug) log14(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
4981
- writeResponsesErrorStream(modelId, "Unknown or expired previous_response_id", sendWsEvent, 400);
4982
- externalActive = false;
4983
- return;
4984
- }
4985
- try {
4986
- const routedBody = await prepareExternalCodexBody(continuation.body, {
4987
- relay: nativePayloadRelay,
4988
- mixedNative,
4989
- headers: req.headers
4990
- });
4991
- const requestHeaders = openCodeGoHeaders(
4992
- route.providerId,
4993
- route.baseURL,
4994
- extractConversationId(req.headers, routedBody),
4995
- route.headers
4996
- );
4997
- currentExternalStateInput = responsesInputItems(routedBody.input);
4998
- currentExternalConsumedResponseId = continuation.consumedResponseId;
4999
- let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
5000
- routedBody,
5001
- route.npm,
5002
- {
5003
- providerId: route.providerId,
5004
- apiBaseUrl: route.baseURL,
5005
- supportedParameters: route.supportedParameters,
5006
- reasoning: route.reasoning,
5007
- interleavedReasoningField: route.interleavedReasoningField,
5008
- upstreamModelId: route.upstreamModelId
5009
- },
5010
- {
5011
- maxTools: maxToolsForNpm(route.npm),
5012
- ...requestHeaders ? { requestHeaders } : {}
5013
- }
5014
- ), route));
5015
- if (route.contextWindow && route.contextWindow > 0) {
5016
- const before = params.messages.length;
5017
- const estimatedChars = estimateCodexRequestChars(params);
5018
- const compaction = isLikelyCodexCompactionRequest(body);
5019
- if (debug) log14(`WS context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before} tools=${params.tools ? Object.keys(params.tools).length : 0}`);
5020
- params = protectCodexCompactionParams(body, params, route.contextWindow);
5021
- if (debug && params.messages.length < before) {
5022
- log14(`WS context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages tools=${params.tools ? Object.keys(params.tools).length : 0}`);
5023
- }
5024
- }
5025
- const v2Compaction = isCodexV2CompactionRequest(body);
5026
- if (v2Compaction) {
5027
- params = appendCompactionInstruction(params);
5028
- if (debug) log14(`WS compaction v2: synthesizing single compaction item for model=${route.modelId}`);
5029
- }
5030
- if (debug) {
5031
- const effort = body.reasoning?.effort;
5032
- log14(`WS model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
5033
- }
5034
- if (v2Compaction) {
5035
- await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
5036
- } else
5037
- await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
5038
- if (debug) {
5039
- const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
5040
- log14(`WS response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
5041
- }
5042
- }, (progress) => {
5043
- if (debug) {
5044
- log14(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
5045
- }
5046
- });
5047
- if (currentExternalCompletedResponse && currentExternalStateInput) {
5048
- if (currentExternalConsumedResponseId) {
5049
- externalResponseStates.delete(currentExternalConsumedResponseId);
5086
+ closeSocket();
5087
+ return;
5050
5088
  }
5051
- rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
5052
5089
  }
5090
+ const { route, languageModel } = resolved;
5091
+ const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
5053
5092
  audit({
5054
5093
  transport: "ws",
5055
5094
  requestedModel: modelId,
5056
5095
  dispatch: relayDispatch,
5057
- phase: "complete",
5096
+ phase: "dispatch",
5058
5097
  provider: route.providerId ?? "relay",
5059
5098
  routeModel: route.modelId,
5060
- upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5061
- outcome: "ok",
5062
- status: "response.completed"
5099
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
5063
5100
  });
5064
- } catch (err) {
5065
- const msg = formatUpstreamError(err);
5066
- const status = upstreamHttpStatus(err, msg);
5067
- audit({
5068
- transport: "ws",
5069
- requestedModel: modelId,
5070
- dispatch: relayDispatch,
5071
- phase: "complete",
5072
- provider: route.providerId ?? "relay",
5073
- routeModel: route.modelId,
5074
- upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5075
- outcome: "error",
5076
- status
5077
- });
5078
- if (debug) log14(`WS sdk error: ${route.modelId}: ${msg}`);
5079
- if (status === 429) {
5080
- writeResponsesRateLimitStream(modelId, msg, sendWsEvent);
5081
- } else {
5082
- writeResponsesErrorStream(modelId, msg, sendWsEvent, status);
5101
+ currentExternalCompletedResponse = void 0;
5102
+ currentExternalStateInput = void 0;
5103
+ currentExternalConsumedResponseId = void 0;
5104
+ let streamFailure;
5105
+ const continuation = resolveExternalContinuation(body);
5106
+ if (continuation.orphanedResponseId) {
5107
+ if (debug) log15(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
5108
+ writeResponsesErrorStream(modelId, "Unknown or expired previous_response_id", sendWsEvent, 400);
5109
+ externalActive = false;
5110
+ return;
5083
5111
  }
5084
- }
5085
- externalActive = false;
5086
- })();
5112
+ try {
5113
+ const routedBody = await prepareExternalCodexBody(continuation.body, {
5114
+ relay: nativePayloadRelay,
5115
+ mixedNative,
5116
+ headers: req.headers
5117
+ });
5118
+ const requestHeaders = openCodeGoHeaders(
5119
+ route.providerId,
5120
+ route.baseURL,
5121
+ extractConversationId(req.headers, routedBody),
5122
+ route.headers
5123
+ );
5124
+ currentExternalStateInput = responsesInputItems(routedBody.input);
5125
+ currentExternalConsumedResponseId = continuation.consumedResponseId;
5126
+ let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
5127
+ routedBody,
5128
+ route.npm,
5129
+ {
5130
+ providerId: route.providerId,
5131
+ apiBaseUrl: route.baseURL,
5132
+ supportedParameters: route.supportedParameters,
5133
+ reasoning: route.reasoning,
5134
+ interleavedReasoningField: route.interleavedReasoningField,
5135
+ upstreamModelId: route.upstreamModelId
5136
+ },
5137
+ {
5138
+ maxTools: maxToolsForNpm(route.npm),
5139
+ ...requestHeaders ? { requestHeaders } : {}
5140
+ }
5141
+ ), route));
5142
+ if (route.contextWindow && route.contextWindow > 0) {
5143
+ const before = params.messages.length;
5144
+ const estimatedChars = estimateCodexRequestChars(params);
5145
+ const compaction = isLikelyCodexCompactionRequest(body);
5146
+ if (debug) log15(`WS context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before} tools=${params.tools ? Object.keys(params.tools).length : 0}`);
5147
+ params = protectCodexCompactionParams(body, params, route.contextWindow);
5148
+ if (debug && params.messages.length < before) {
5149
+ log15(`WS context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages tools=${params.tools ? Object.keys(params.tools).length : 0}`);
5150
+ }
5151
+ }
5152
+ const v2Compaction = isCodexV2CompactionRequest(body);
5153
+ if (v2Compaction) {
5154
+ params = appendCompactionInstruction(params);
5155
+ if (debug) log15(`WS compaction v2: synthesizing single compaction item for model=${route.modelId}`);
5156
+ }
5157
+ if (debug) {
5158
+ const effort = body.reasoning?.effort;
5159
+ log15(`WS model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
5160
+ }
5161
+ if (v2Compaction) {
5162
+ await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
5163
+ } else
5164
+ await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
5165
+ if (summary.errorMessage || summary.aborted) streamFailure = summary;
5166
+ if (debug) {
5167
+ const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
5168
+ log15(`WS response done: model=${route.modelId} reasoningChars=${summary.reasoningChars} textChars=${summary.textChars} toolCalls=${summary.toolCallCount} toolNames=[${summary.toolNames.join(",")}] loopDetected=${summary.loopDetected ?? "no"} dsmlRecovered=${summary.dsmlToolCallsRecovered ?? 0}${failure} reasoningPreview=${JSON.stringify(summary.reasoningPreview)}`);
5169
+ }
5170
+ }, (progress) => {
5171
+ if (debug) {
5172
+ log15(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
5173
+ }
5174
+ });
5175
+ if (currentExternalCompletedResponse && currentExternalStateInput) {
5176
+ if (currentExternalConsumedResponseId) {
5177
+ externalResponseStates.delete(currentExternalConsumedResponseId);
5178
+ }
5179
+ rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
5180
+ }
5181
+ audit({
5182
+ transport: "ws",
5183
+ requestedModel: modelId,
5184
+ dispatch: relayDispatch,
5185
+ phase: "complete",
5186
+ provider: route.providerId ?? "relay",
5187
+ routeModel: route.modelId,
5188
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5189
+ ...streamOutcome(streamFailure, "response.completed")
5190
+ });
5191
+ } catch (err) {
5192
+ const msg = formatUpstreamError(err);
5193
+ const status = upstreamHttpStatus(err, msg);
5194
+ audit({
5195
+ transport: "ws",
5196
+ requestedModel: modelId,
5197
+ dispatch: relayDispatch,
5198
+ phase: "complete",
5199
+ provider: route.providerId ?? "relay",
5200
+ routeModel: route.modelId,
5201
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5202
+ outcome: "error",
5203
+ status
5204
+ });
5205
+ if (debug) log15(`WS sdk error: ${route.modelId}: ${msg}`);
5206
+ if (status === 429) {
5207
+ writeResponsesRateLimitStream(modelId, msg, sendWsEvent);
5208
+ } else {
5209
+ writeResponsesErrorStream(modelId, msg, sendWsEvent, status);
5210
+ }
5211
+ }
5212
+ externalActive = false;
5213
+ })();
5214
+ }
5087
5215
  };
5088
5216
  socket.on("error", () => socket.destroy());
5089
5217
  socket.once("close", () => externalResponseStates.clear());
@@ -5242,7 +5370,7 @@ function restoreCodexOverlay(env = process.env) {
5242
5370
  return removed;
5243
5371
  }
5244
5372
  function remainingOverlayPaths(env = process.env) {
5245
- return ownedOverlayPaths(env).filter((p15) => existsSync3(p15));
5373
+ return ownedOverlayPaths(env).filter((p16) => existsSync3(p16));
5246
5374
  }
5247
5375
  function recoverInterruptedCodexSession(env = process.env) {
5248
5376
  const before = remainingOverlayPaths(env);
@@ -5461,7 +5589,7 @@ function launchCodex(modelId, env, extraArgs) {
5461
5589
 
5462
5590
  // src/codex/prompts.ts
5463
5591
  import pc5 from "picocolors";
5464
- import * as p6 from "@clack/prompts";
5592
+ import * as p7 from "@clack/prompts";
5465
5593
  function codexLaunchModeOptions() {
5466
5594
  return [
5467
5595
  {
@@ -5477,13 +5605,13 @@ function codexLaunchModeOptions() {
5477
5605
  ];
5478
5606
  }
5479
5607
  async function pickCodexLaunchMode() {
5480
- const choice = await p6.select({
5608
+ const choice = await p7.select({
5481
5609
  message: "Load native Codex models alongside Relay models?",
5482
5610
  options: codexLaunchModeOptions(),
5483
5611
  initialValue: "relay-only"
5484
5612
  });
5485
- if (p6.isCancel(choice)) {
5486
- p6.cancel("Cancelled.");
5613
+ if (p7.isCancel(choice)) {
5614
+ p7.cancel("Cancelled.");
5487
5615
  return null;
5488
5616
  }
5489
5617
  return choice;
@@ -5499,13 +5627,13 @@ async function pickCodexProvider(providers, prefs, hasFavorites = false, initial
5499
5627
  });
5500
5628
  }
5501
5629
  const initial = initialProviderId && options.some((o) => o.value === initialProviderId) ? initialProviderId : prefs.lastCodexProvider && options.some((o) => o.value === prefs.lastCodexProvider) ? prefs.lastCodexProvider : options[0].value;
5502
- const chosen = await p6.select({
5630
+ const chosen = await p7.select({
5503
5631
  message: "Which provider for Codex?",
5504
5632
  options,
5505
5633
  initialValue: initial
5506
5634
  });
5507
- if (p6.isCancel(chosen)) {
5508
- p6.cancel("Cancelled.");
5635
+ if (p7.isCancel(chosen)) {
5636
+ p7.cancel("Cancelled.");
5509
5637
  return null;
5510
5638
  }
5511
5639
  if (chosen === "__favorites__") return "__favorites__";
@@ -5522,12 +5650,12 @@ async function pickCodexModel(provider, prefs) {
5522
5650
  navOption("__browse_all__", "Browse all models \u2192", `${provider.models.length} available`),
5523
5651
  navOption("__back__", "\u2190 Go back", "Select a different provider")
5524
5652
  ];
5525
- const picked = await p6.select({
5653
+ const picked = await p7.select({
5526
5654
  message: `Model for ${provider.name}?`,
5527
5655
  options,
5528
5656
  initialValue: recentModels[0].id
5529
5657
  });
5530
- if (p6.isCancel(picked) || String(picked) === "__back__") {
5658
+ if (p7.isCancel(picked) || String(picked) === "__back__") {
5531
5659
  return "back";
5532
5660
  }
5533
5661
  if (String(picked) === "__browse_all__") {
@@ -5556,12 +5684,12 @@ async function pickCodexModel(provider, prefs) {
5556
5684
  }
5557
5685
  function confirmCodexLaunch(providerName, modelLabel, modelId, route) {
5558
5686
  const via = route.tier === "direct" ? pc5.green("direct") : `${pc5.dim("via")} ${pc5.yellow("relay-ai proxy")}`;
5559
- return p6.confirm({
5687
+ return p7.confirm({
5560
5688
  message: `${confirmLaunchMessage("Codex", modelLabel, modelId, providerName)} ${pc5.dim("(")}${via}${pc5.dim(")")}`,
5561
5689
  initialValue: true
5562
5690
  }).then((answer) => {
5563
- if (p6.isCancel(answer)) {
5564
- p6.cancel("Cancelled.");
5691
+ if (p7.isCancel(answer)) {
5692
+ p7.cancel("Cancelled.");
5565
5693
  return false;
5566
5694
  }
5567
5695
  return answer;
@@ -5671,7 +5799,7 @@ function buildFavoritesAppCatalog(resolved) {
5671
5799
  }
5672
5800
 
5673
5801
  // src/codex/favorites-launch.ts
5674
- import * as p7 from "@clack/prompts";
5802
+ import * as p8 from "@clack/prompts";
5675
5803
 
5676
5804
  // src/favorites-resolver.ts
5677
5805
  async function resolveFavorite(fav, ctx) {
@@ -5748,7 +5876,7 @@ async function pickFavoriteStartingModel(compatible, favorites, agent, productLa
5748
5876
  if (provider && model) available.push({ provider, model });
5749
5877
  }
5750
5878
  if (available.length === 0) {
5751
- p7.log.warn(`No saved ${productLabel} favorites are currently available.`);
5879
+ p8.log.warn(`No saved ${productLabel} favorites are currently available.`);
5752
5880
  return "unavailable";
5753
5881
  }
5754
5882
  const favOptions = available.map((f, i) => ({
@@ -5756,13 +5884,13 @@ async function pickFavoriteStartingModel(compatible, favorites, agent, productLa
5756
5884
  label: `${f.model.name || f.model.id} \u2014 ${f.provider.name}`,
5757
5885
  hint: f.model.id
5758
5886
  }));
5759
- const pickedIdx = await p7.select({
5887
+ const pickedIdx = await p8.select({
5760
5888
  message: "Starting model?",
5761
5889
  options: favOptions,
5762
5890
  initialValue: "0"
5763
5891
  });
5764
- if (p7.isCancel(pickedIdx)) {
5765
- p7.cancel("Cancelled.");
5892
+ if (p8.isCancel(pickedIdx)) {
5893
+ p8.cancel("Cancelled.");
5766
5894
  return "cancelled";
5767
5895
  }
5768
5896
  return available[Number(pickedIdx)] ?? "unavailable";
@@ -5805,7 +5933,7 @@ function buildCodexProxyRoutesFromResolved(resolved, providersById) {
5805
5933
  };
5806
5934
  }).filter((r) => r !== void 0);
5807
5935
  if (skippedOAuth.length > 0) {
5808
- p7.log.warn(
5936
+ p8.log.warn(
5809
5937
  `Skipped ${skippedOAuth.length} OAuth favorite(s) (OAuth auth not supported in favorites catalog): ${skippedOAuth.join(", ")}`
5810
5938
  );
5811
5939
  }
@@ -5831,7 +5959,7 @@ async function resolveCodexFavorites(activeProvider, selectedModel, compatible,
5831
5959
  ctx
5832
5960
  );
5833
5961
  if (droppedFavorites.length > 0) {
5834
- p7.log.warn(
5962
+ p8.log.warn(
5835
5963
  `Skipped ${droppedFavorites.length} stale/unauthorized favorite(s): ${droppedFavorites.map((f) => `${f.providerId}:${f.modelId}`).join(", ")}`
5836
5964
  );
5837
5965
  }
@@ -6241,7 +6369,7 @@ function resolveLaunchTarget(explicit, prefs, agent) {
6241
6369
  }
6242
6370
  function findProviderAndModel(providers, target) {
6243
6371
  if (!target.providerId || !target.modelId) return null;
6244
- const provider = providers.find((p15) => p15.id === target.providerId);
6372
+ const provider = providers.find((p16) => p16.id === target.providerId);
6245
6373
  if (!provider) return null;
6246
6374
  const model = provider.models.find((m) => m.id === target.modelId);
6247
6375
  if (!model) return null;
@@ -6428,14 +6556,14 @@ function printCodexCleanupReminder(hadProxy) {
6428
6556
  if (isAgentStdoutMode()) return;
6429
6557
  const left = remainingOverlayPaths();
6430
6558
  if (left.length > 0) {
6431
- p8.log.warn("Temporary Codex overlay files may still be on disk.");
6432
- p8.log.info("Run: relay-ai codex --restore");
6559
+ p9.log.warn("Temporary Codex overlay files may still be on disk.");
6560
+ p9.log.info("Run: relay-ai codex --restore");
6433
6561
  return;
6434
6562
  }
6435
6563
  const parts = ["Temporary Codex profile removed."];
6436
6564
  if (hadProxy) parts.push("Local Responses proxy stopped.");
6437
6565
  parts.push("If a future session acts stuck: relay-ai codex --restore");
6438
- p8.log.info(parts.join(" "));
6566
+ p9.log.info(parts.join(" "));
6439
6567
  }
6440
6568
  function vertexEntryToLocalModel(entry) {
6441
6569
  return {
@@ -6452,26 +6580,26 @@ function vertexEntryToLocalModel(entry) {
6452
6580
  }
6453
6581
  async function runCodexVertexLaunch(passthroughArgs, trace) {
6454
6582
  if (!hasApplicationDefaultCredentials()) {
6455
- p8.log.error("Google Application Default Credentials not found.");
6456
- p8.log.info("Run: gcloud auth application-default login");
6583
+ p9.log.error("Google Application Default Credentials not found.");
6584
+ p9.log.info("Run: gcloud auth application-default login");
6457
6585
  return 1;
6458
6586
  }
6459
6587
  const config = buildVertexRuntimeConfig();
6460
6588
  if (!config) {
6461
- p8.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
6462
- p8.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
6589
+ p9.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
6590
+ p9.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
6463
6591
  return 1;
6464
6592
  }
6465
6593
  let selectedEntry;
6466
6594
  if (config.models.length === 1) {
6467
6595
  selectedEntry = config.models[0];
6468
6596
  } else {
6469
- const choice = await p8.select({
6597
+ const choice = await p9.select({
6470
6598
  message: "Select a Vertex AI model:",
6471
6599
  options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
6472
6600
  });
6473
- if (p8.isCancel(choice)) {
6474
- p8.cancel("Cancelled.");
6601
+ if (p9.isCancel(choice)) {
6602
+ p9.cancel("Cancelled.");
6475
6603
  return 0;
6476
6604
  }
6477
6605
  selectedEntry = choice;
@@ -6499,7 +6627,7 @@ async function runCodexVertexLaunch(passthroughArgs, trace) {
6499
6627
  const debugLogPath = getCodexProxyDebugLogPath();
6500
6628
  let proxyHandle = null;
6501
6629
  try {
6502
- p8.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
6630
+ p9.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
6503
6631
  proxyHandle = await startCodexProxy(allRoutes, { debug: trace });
6504
6632
  const proxyPort = proxyHandle.port;
6505
6633
  const catalogPath = getCatalogOutputPath("vertex");
@@ -6562,7 +6690,7 @@ async function runCodexCommand2(codexArgs, trace = false, launch = {}) {
6562
6690
  setAgentStdoutMode(agentStdout);
6563
6691
  const debugLogPath = getCodexProxyDebugLogPath();
6564
6692
  if (trace && !configOnly) {
6565
- p8.log.info(`Debug log: ${debugLogPath}`);
6693
+ p9.log.info(`Debug log: ${debugLogPath}`);
6566
6694
  }
6567
6695
  const isTty = Boolean(process.stdin.isTTY);
6568
6696
  if (launch.vertex) {
@@ -6611,10 +6739,10 @@ Error: ${launchPlan.error}
6611
6739
  if (!configOnly) {
6612
6740
  if (!agentStdout) codexCliIntro();
6613
6741
  if (interrupted.recovered && !agentStdout) {
6614
- p8.log.warn(
6742
+ p9.log.warn(
6615
6743
  "Found leftover Codex files from an interrupted session (closed terminal, crash, or force-quit)."
6616
6744
  );
6617
- p8.log.info(
6745
+ p9.log.info(
6618
6746
  `Removed ${interrupted.removedCount ?? "those"} file(s) automatically. If anything still looks wrong: relay-ai codex --restore`
6619
6747
  );
6620
6748
  }
@@ -6628,7 +6756,7 @@ Error: ${launchPlan.error}
6628
6756
  return 1;
6629
6757
  }
6630
6758
  } else {
6631
- const catalogSpinner = p8.spinner();
6759
+ const catalogSpinner = p9.spinner();
6632
6760
  catalogSpinner.start("Loading your providers...");
6633
6761
  try {
6634
6762
  catalog = await fetchProviderCatalog({ agent: "codex" });
@@ -6642,8 +6770,8 @@ Error: ${launchPlan.error}
6642
6770
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "codex");
6643
6771
  if (compatible.length === 0) {
6644
6772
  if (!configOnly) {
6645
- p8.log.warn("No Codex-compatible providers in your registry.");
6646
- p8.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
6773
+ p9.log.warn("No Codex-compatible providers in your registry.");
6774
+ p9.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
6647
6775
  }
6648
6776
  return 0;
6649
6777
  }
@@ -6656,17 +6784,17 @@ Error: ${launchPlan.error}
6656
6784
  }
6657
6785
  const favoritesActive = favorites.length > 0 && !launchPlan.skip && !mixedMode;
6658
6786
  if (favoritesActive && !configOnly) {
6659
- p8.log.info(
6787
+ p9.log.info(
6660
6788
  `Favorites mode active \u2014 Codex picker will show ${favorites.length + 1} models (1 starting + ${favorites.length} favorites).`
6661
6789
  );
6662
- p8.log.info("Edit with `relay-ai models`.");
6790
+ p9.log.info("Edit with `relay-ai models`.");
6663
6791
  }
6664
6792
  let activeProvider = compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0];
6665
6793
  let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastCodexModel) ?? activeProvider.models[0];
6666
6794
  if (!configOnly && launchPlan.skip && launchPlan.target) {
6667
6795
  const resolved = findProviderAndModel(compatible, launchPlan.target);
6668
6796
  if (!resolved) {
6669
- p8.log.error(
6797
+ p9.log.error(
6670
6798
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
6671
6799
  );
6672
6800
  return 1;
@@ -6674,7 +6802,7 @@ Error: ${launchPlan.error}
6674
6802
  activeProvider = resolved.provider;
6675
6803
  selectedModel = resolved.model;
6676
6804
  if (!agentStdout) {
6677
- p8.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
6805
+ p9.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
6678
6806
  }
6679
6807
  } else if (!configOnly) {
6680
6808
  let currentInitialProvider = prefs.lastCodexProvider && compatible.some((o) => o.id === prefs.lastCodexProvider) ? prefs.lastCodexProvider : compatible[0].id;
@@ -6722,7 +6850,7 @@ Error: ${launchPlan.error}
6722
6850
  const apiKey = await resolveLocalProviderApiKey(activeProvider);
6723
6851
  if (!apiKey) {
6724
6852
  if (!configOnly) {
6725
- p8.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id} or add an API key.`);
6853
+ p9.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id} or add an API key.`);
6726
6854
  }
6727
6855
  return 1;
6728
6856
  }
@@ -6914,7 +7042,7 @@ Mixed Codex mode is unavailable: ${err instanceof Error ? err.message : err}`));
6914
7042
  });
6915
7043
  if (configOnly) {
6916
7044
  const home = process.env["HOME"] ?? "";
6917
- const shortenPath = (p15) => home ? p15.replace(home, "~") : p15;
7045
+ const shortenPath = (p16) => home ? p16.replace(home, "~") : p16;
6918
7046
  console.log("");
6919
7047
  console.log(pc7.bold(pc7.cyan(" CONFIG PREVIEW \u2014 relay-ai codex")));
6920
7048
  console.log("");
@@ -6991,7 +7119,7 @@ Mixed Codex mode is unavailable: ${err instanceof Error ? err.message : err}`));
6991
7119
 
6992
7120
  // src/gemini.ts
6993
7121
  import pc8 from "picocolors";
6994
- import * as p10 from "@clack/prompts";
7122
+ import * as p11 from "@clack/prompts";
6995
7123
 
6996
7124
  // src/gemini/launch.ts
6997
7125
  import { spawn as spawn3 } from "child_process";
@@ -7080,7 +7208,7 @@ function launchGemini(geminiPath, modelId, env, extraArgs) {
7080
7208
  }
7081
7209
 
7082
7210
  // src/gemini/prompts.ts
7083
- import * as p9 from "@clack/prompts";
7211
+ import * as p10 from "@clack/prompts";
7084
7212
  async function pickGeminiProvider(providers, prefs, hasFavorites = false, initialProviderId) {
7085
7213
  if (providers.length === 0 && !hasFavorites) return null;
7086
7214
  const options = providers.map((lp) => providerSelectOption(lp));
@@ -7092,13 +7220,13 @@ async function pickGeminiProvider(providers, prefs, hasFavorites = false, initia
7092
7220
  });
7093
7221
  }
7094
7222
  const initial = initialProviderId && options.some((o) => o.value === initialProviderId) ? initialProviderId : prefs.lastGeminiProvider && options.some((o) => o.value === prefs.lastGeminiProvider) ? prefs.lastGeminiProvider : options[0].value;
7095
- const chosen = await p9.select({
7223
+ const chosen = await p10.select({
7096
7224
  message: "Which provider for Gemini CLI?",
7097
7225
  options,
7098
7226
  initialValue: initial
7099
7227
  });
7100
- if (p9.isCancel(chosen)) {
7101
- p9.cancel("Cancelled.");
7228
+ if (p10.isCancel(chosen)) {
7229
+ p10.cancel("Cancelled.");
7102
7230
  return null;
7103
7231
  }
7104
7232
  if (chosen === "__favorites__") return "__favorites__";
@@ -7115,12 +7243,12 @@ async function pickGeminiModel(provider, prefs) {
7115
7243
  navOption("__browse_all__", "Browse all models \u2192", `${provider.models.length} available`),
7116
7244
  navOption("__back__", "\u2190 Go back", "Select a different provider")
7117
7245
  ];
7118
- const picked = await p9.select({
7246
+ const picked = await p10.select({
7119
7247
  message: `Model for ${provider.name}?`,
7120
7248
  options,
7121
7249
  initialValue: recentModels[0].id
7122
7250
  });
7123
- if (p9.isCancel(picked) || String(picked) === "__back__") {
7251
+ if (p10.isCancel(picked) || String(picked) === "__back__") {
7124
7252
  return "back";
7125
7253
  }
7126
7254
  if (String(picked) === "__browse_all__") {
@@ -7148,12 +7276,12 @@ async function pickGeminiModel(provider, prefs) {
7148
7276
  return selectedModel;
7149
7277
  }
7150
7278
  function confirmGeminiLaunch(providerName, modelLabel, modelId) {
7151
- return p9.confirm({
7279
+ return p10.confirm({
7152
7280
  message: confirmLaunchMessage("Gemini CLI", modelLabel, modelId, providerName),
7153
7281
  initialValue: true
7154
7282
  }).then((answer) => {
7155
- if (p9.isCancel(answer)) {
7156
- p9.cancel("Cancelled.");
7283
+ if (p10.isCancel(answer)) {
7284
+ p10.cancel("Cancelled.");
7157
7285
  return false;
7158
7286
  }
7159
7287
  return answer;
@@ -7167,7 +7295,7 @@ async function pickGeminiFavoriteModel(providers, favorites) {
7167
7295
  if (provider2 && model2) favList.push({ provider: provider2, model: model2 });
7168
7296
  }
7169
7297
  if (favList.length === 0) {
7170
- p9.log.warn("None of your saved favorites are available in the current registry.");
7298
+ p10.log.warn("None of your saved favorites are available in the current registry.");
7171
7299
  return null;
7172
7300
  }
7173
7301
  const options = [
@@ -7178,12 +7306,12 @@ async function pickGeminiFavoriteModel(providers, favorites) {
7178
7306
  })),
7179
7307
  { value: "__back__", label: "\u2190 Go back", hint: "Select a different provider" }
7180
7308
  ];
7181
- const picked = await p9.select({
7309
+ const picked = await p10.select({
7182
7310
  message: "Pick a favorite model for Gemini CLI:",
7183
7311
  options,
7184
7312
  initialValue: options[0].value
7185
7313
  });
7186
- if (p9.isCancel(picked) || String(picked) === "__back__") return "back";
7314
+ if (p10.isCancel(picked) || String(picked) === "__back__") return "back";
7187
7315
  const [pickedProviderId, pickedModelId] = picked.split("::");
7188
7316
  const provider = providers.find((lp) => lp.id === pickedProviderId);
7189
7317
  const model = provider?.models.find((m) => m.id === pickedModelId);
@@ -7253,13 +7381,13 @@ function mergeConsecutiveMessages2(messages) {
7253
7381
  }
7254
7382
  return merged;
7255
7383
  }
7256
- function stripGeminiIdentity(text5) {
7257
- return text5.replace(/You are Gemini CLI[\s\S]*?(?=\n\n|$)/gi, "").replace(/I'm Gemini CLI[\s\S]*?(?=\n\n|$)/gi, "").replace(/Gemini CLI/gi, "AI CLI");
7384
+ function stripGeminiIdentity(text6) {
7385
+ return text6.replace(/You are Gemini CLI[\s\S]*?(?=\n\n|$)/gi, "").replace(/I'm Gemini CLI[\s\S]*?(?=\n\n|$)/gi, "").replace(/Gemini CLI/gi, "AI CLI");
7258
7386
  }
7259
7387
  function translateGeminiRequest(body, options = {}) {
7260
7388
  let system;
7261
7389
  if (body.systemInstruction?.parts) {
7262
- const rawSystem = body.systemInstruction.parts.map((p15) => p15.text || "").join("\n");
7390
+ const rawSystem = body.systemInstruction.parts.map((p16) => p16.text || "").join("\n");
7263
7391
  system = stripGeminiIdentity(rawSystem).trim();
7264
7392
  }
7265
7393
  const messages = [];
@@ -7270,38 +7398,38 @@ function translateGeminiRequest(body, options = {}) {
7270
7398
  const parts = [];
7271
7399
  const toolResults = [];
7272
7400
  const turnParts = turn.parts || [];
7273
- for (const p15 of turnParts) {
7274
- if (p15.text !== void 0) {
7275
- const text5 = stripGeminiIdentity(p15.text);
7276
- if (text5.includes("<thinking>")) {
7277
- const tokens = text5.split(/<thinking>([\s\S]*?)<\/thinking>/);
7401
+ for (const p16 of turnParts) {
7402
+ if (p16.text !== void 0) {
7403
+ const text6 = stripGeminiIdentity(p16.text);
7404
+ if (text6.includes("<thinking>")) {
7405
+ const tokens = text6.split(/<thinking>([\s\S]*?)<\/thinking>/);
7278
7406
  for (let i = 0; i < tokens.length; i++) {
7279
7407
  const token = tokens[i].trim();
7280
7408
  if (!token) continue;
7281
7409
  parts.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
7282
7410
  }
7283
7411
  } else {
7284
- parts.push({ type: "text", text: text5 });
7412
+ parts.push({ type: "text", text: text6 });
7285
7413
  }
7286
- } else if (p15.inlineData) {
7414
+ } else if (p16.inlineData) {
7287
7415
  parts.push({
7288
7416
  type: "file",
7289
- data: Buffer.from(p15.inlineData.data, "base64"),
7290
- mediaType: p15.inlineData.mimeType
7417
+ data: Buffer.from(p16.inlineData.data, "base64"),
7418
+ mediaType: p16.inlineData.mimeType
7291
7419
  });
7292
- } else if (p15.functionCall) {
7420
+ } else if (p16.functionCall) {
7293
7421
  const id = "call_" + randomUUID().replace(/-/g, "");
7294
- const name = p15.functionCall.name;
7422
+ const name = p16.functionCall.name;
7295
7423
  if (!nameToIdList.has(name)) nameToIdList.set(name, []);
7296
7424
  nameToIdList.get(name).push(id);
7297
7425
  parts.push({
7298
7426
  type: "tool-call",
7299
7427
  toolCallId: id,
7300
7428
  toolName: name,
7301
- input: p15.functionCall.args || {}
7429
+ input: p16.functionCall.args || {}
7302
7430
  });
7303
- } else if (p15.functionResponse) {
7304
- const name = p15.functionResponse.name;
7431
+ } else if (p16.functionResponse) {
7432
+ const name = p16.functionResponse.name;
7305
7433
  const idList = nameToIdList.get(name) || [];
7306
7434
  const id = idList.shift() || "call_" + randomUUID().replace(/-/g, "");
7307
7435
  toolResults.push({
@@ -7310,7 +7438,7 @@ function translateGeminiRequest(body, options = {}) {
7310
7438
  toolName: name,
7311
7439
  output: {
7312
7440
  type: "text",
7313
- value: typeof p15.functionResponse.response === "string" ? p15.functionResponse.response : JSON.stringify(p15.functionResponse.response || {})
7441
+ value: typeof p16.functionResponse.response === "string" ? p16.functionResponse.response : JSON.stringify(p16.functionResponse.response || {})
7314
7442
  }
7315
7443
  });
7316
7444
  }
@@ -7467,14 +7595,14 @@ ${rawBody}`);
7467
7595
  const current = sessionRouteOverride ?? (lookupGeminiRoute(routes, requestedModel) ?? defaultRoute);
7468
7596
  const availableList = routes.map((r) => ` - ${r.aliasId} (${r.displayName})`).join("\n");
7469
7597
  const exampleId = routes.length > 1 ? routes[1].aliasId : routes[0]?.aliasId ?? "deepseek-v4";
7470
- const text5 = `Current model: ${current.displayName} (${current.aliasId})
7598
+ const text6 = `Current model: ${current.displayName} (${current.aliasId})
7471
7599
 
7472
7600
  Available models:
7473
7601
  ${availableList}
7474
7602
 
7475
7603
  \u{1F4A1} To switch models, type: .model <id>
7476
7604
  Example: .model ${exampleId}`;
7477
- sendMockGeminiResponse(res, text5, isStream, current.aliasId);
7605
+ sendMockGeminiResponse(res, text6, isStream, current.aliasId);
7478
7606
  return;
7479
7607
  }
7480
7608
  const targetRoute = lookupGeminiRoute(routes, modelCommand);
@@ -7530,9 +7658,9 @@ ${JSON.stringify(params, null, 2)}`);
7530
7658
  const toolCallBuffers = /* @__PURE__ */ new Map();
7531
7659
  let isThinking = false;
7532
7660
  for await (const part of stream) {
7533
- const p15 = part;
7534
- plog(`Stream chunk type: ${p15.type}`);
7535
- if (isThinking && (p15.type === "tool-input-start" || p15.type === "tool-call" || p15.type === "finish")) {
7661
+ const p16 = part;
7662
+ plog(`Stream chunk type: ${p16.type}`);
7663
+ if (isThinking && (p16.type === "tool-input-start" || p16.type === "tool-call" || p16.type === "finish")) {
7536
7664
  isThinking = false;
7537
7665
  const chunk = {
7538
7666
  candidates: [{ content: { role: "model", parts: [{ text: `
@@ -7545,34 +7673,34 @@ ${JSON.stringify(params, null, 2)}`);
7545
7673
 
7546
7674
  `);
7547
7675
  }
7548
- if (p15.type === "reasoning") {
7549
- let text5 = p15.textDelta ?? p15.text ?? "";
7676
+ if (p16.type === "reasoning") {
7677
+ let text6 = p16.textDelta ?? p16.text ?? "";
7550
7678
  if (!isThinking) {
7551
7679
  isThinking = true;
7552
- text5 = `<thinking>
7553
- ` + text5;
7680
+ text6 = `<thinking>
7681
+ ` + text6;
7554
7682
  }
7555
7683
  const chunk = {
7556
- candidates: [{ content: { role: "model", parts: [{ text: text5 }] } }],
7684
+ candidates: [{ content: { role: "model", parts: [{ text: text6 }] } }],
7557
7685
  modelVersion: route.aliasId
7558
7686
  };
7559
7687
  res.write(`data: ${JSON.stringify(chunk)}
7560
7688
 
7561
7689
  `);
7562
- } else if (p15.type === "text-delta") {
7563
- let text5 = p15.textDelta ?? p15.text ?? "";
7690
+ } else if (p16.type === "text-delta") {
7691
+ let text6 = p16.textDelta ?? p16.text ?? "";
7564
7692
  if (isThinking) {
7565
7693
  isThinking = false;
7566
- text5 = `
7694
+ text6 = `
7567
7695
  </thinking>
7568
7696
 
7569
- ` + text5;
7697
+ ` + text6;
7570
7698
  }
7571
7699
  const chunk = {
7572
7700
  candidates: [{
7573
7701
  content: {
7574
7702
  role: "model",
7575
- parts: [{ text: text5 }]
7703
+ parts: [{ text: text6 }]
7576
7704
  }
7577
7705
  }],
7578
7706
  modelVersion: route.aliasId
@@ -7580,17 +7708,17 @@ ${JSON.stringify(params, null, 2)}`);
7580
7708
  const data = `data: ${JSON.stringify(chunk)}
7581
7709
 
7582
7710
  `;
7583
- plog(`Streaming text delta: ${p15.textDelta}`);
7711
+ plog(`Streaming text delta: ${p16.textDelta}`);
7584
7712
  res.write(data);
7585
- } else if (p15.type === "tool-input-start") {
7586
- toolCallBuffers.set(p15.toolCallId, { name: p15.toolName, json: "" });
7587
- } else if (p15.type === "tool-input-delta") {
7588
- const buf = toolCallBuffers.get(p15.toolCallId);
7589
- if (buf) buf.json += p15.delta;
7590
- } else if (p15.type === "tool-call") {
7591
- const buf = toolCallBuffers.get(p15.toolCallId);
7592
- const args = buf ? JSON.parse(buf.json || "{}") : p15.input || {};
7593
- const name = buf ? buf.name : p15.toolName;
7713
+ } else if (p16.type === "tool-input-start") {
7714
+ toolCallBuffers.set(p16.toolCallId, { name: p16.toolName, json: "" });
7715
+ } else if (p16.type === "tool-input-delta") {
7716
+ const buf = toolCallBuffers.get(p16.toolCallId);
7717
+ if (buf) buf.json += p16.delta;
7718
+ } else if (p16.type === "tool-call") {
7719
+ const buf = toolCallBuffers.get(p16.toolCallId);
7720
+ const args = buf ? JSON.parse(buf.json || "{}") : p16.input || {};
7721
+ const name = buf ? buf.name : p16.toolName;
7594
7722
  plog(`Streaming tool call: ${name} with args: ${JSON.stringify(args)}`);
7595
7723
  const chunk = {
7596
7724
  candidates: [{
@@ -7606,18 +7734,18 @@ ${JSON.stringify(params, null, 2)}`);
7606
7734
  res.write(`data: ${JSON.stringify(chunk)}
7607
7735
 
7608
7736
  `);
7609
- } else if (p15.type === "finish") {
7737
+ } else if (p16.type === "finish") {
7610
7738
  const chunk = {
7611
7739
  candidates: [{
7612
- finishReason: mapFinishReason(p15.finishReason ?? "")
7740
+ finishReason: mapFinishReason(p16.finishReason ?? "")
7613
7741
  }],
7614
7742
  usageMetadata: {
7615
- promptTokenCount: p15.totalUsage?.inputTokens || 0,
7616
- candidatesTokenCount: p15.totalUsage?.outputTokens || 0
7743
+ promptTokenCount: p16.totalUsage?.inputTokens || 0,
7744
+ candidatesTokenCount: p16.totalUsage?.outputTokens || 0
7617
7745
  },
7618
7746
  modelVersion: route.aliasId
7619
7747
  };
7620
- plog(`Stream finish. Reason: ${p15.finishReason}`);
7748
+ plog(`Stream finish. Reason: ${p16.finishReason}`);
7621
7749
  res.write(`data: ${JSON.stringify(chunk)}
7622
7750
 
7623
7751
  `);
@@ -7755,28 +7883,28 @@ function parseModelCommand(turn) {
7755
7883
  if (!turn || turn.role !== "user") return null;
7756
7884
  const parts = turn.parts || [];
7757
7885
  if (parts.length !== 1) return null;
7758
- const text5 = parts[0]?.text;
7759
- if (typeof text5 !== "string") return null;
7760
- const trimmed = text5.trim();
7886
+ const text6 = parts[0]?.text;
7887
+ if (typeof text6 !== "string") return null;
7888
+ const trimmed = text6.trim();
7761
7889
  if (!trimmed.startsWith(".model")) return null;
7762
7890
  if (trimmed === ".model") return "";
7763
7891
  if (trimmed.charAt(6) !== " ") return null;
7764
7892
  return trimmed.slice(7).trim();
7765
7893
  }
7766
- function sendMockGeminiResponse(res, text5, isStream, modelVersion) {
7894
+ function sendMockGeminiResponse(res, text6, isStream, modelVersion) {
7767
7895
  if (isStream) {
7768
7896
  res.writeHead(200, {
7769
7897
  "Content-Type": "text/event-stream",
7770
7898
  "Cache-Control": "no-cache",
7771
7899
  "Connection": "keep-alive"
7772
7900
  });
7773
- writeGeminiStreamText(res, text5, modelVersion);
7901
+ writeGeminiStreamText(res, text6, modelVersion);
7774
7902
  res.end();
7775
7903
  } else {
7776
7904
  res.writeHead(200, { "Content-Type": "application/json" });
7777
7905
  res.end(JSON.stringify({
7778
7906
  candidates: [{
7779
- content: { role: "model", parts: [{ text: text5 }] },
7907
+ content: { role: "model", parts: [{ text: text6 }] },
7780
7908
  finishReason: "STOP"
7781
7909
  }],
7782
7910
  usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0 },
@@ -7784,10 +7912,10 @@ function sendMockGeminiResponse(res, text5, isStream, modelVersion) {
7784
7912
  }));
7785
7913
  }
7786
7914
  }
7787
- function writeGeminiStreamText(res, text5, modelVersion) {
7915
+ function writeGeminiStreamText(res, text6, modelVersion) {
7788
7916
  const chunk = {
7789
7917
  candidates: [{
7790
- content: { role: "model", parts: [{ text: text5 }] },
7918
+ content: { role: "model", parts: [{ text: text6 }] },
7791
7919
  finishReason: "STOP"
7792
7920
  }],
7793
7921
  usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0 },
@@ -7944,7 +8072,7 @@ Error: ${launchPlan.error}
7944
8072
  return 1;
7945
8073
  }
7946
8074
  } else {
7947
- const catalogSpinner = p10.spinner();
8075
+ const catalogSpinner = p11.spinner();
7948
8076
  catalogSpinner.start("Loading your providers...");
7949
8077
  try {
7950
8078
  catalog = await fetchProviderCatalog({ agent: "gemini" });
@@ -7957,21 +8085,21 @@ Error: ${launchPlan.error}
7957
8085
  }
7958
8086
  const compatible = providersForTarget(providersForPicker(catalog), "gemini");
7959
8087
  if (compatible.length === 0) {
7960
- p10.log.warn("No Gemini-compatible providers in your registry.");
7961
- p10.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
8088
+ p11.log.warn("No Gemini-compatible providers in your registry.");
8089
+ p11.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
7962
8090
  return 0;
7963
8091
  }
7964
8092
  let activeProvider = compatible.find((lp) => lp.id === prefs.lastGeminiProvider) ?? compatible[0];
7965
8093
  let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastGeminiModel) ?? activeProvider.models[0];
7966
8094
  if (!selectedModel) {
7967
- p10.log.error(`Provider "${activeProvider.name}" has no models available.`);
8095
+ p11.log.error(`Provider "${activeProvider.name}" has no models available.`);
7968
8096
  return 1;
7969
8097
  }
7970
8098
  ;
7971
8099
  if (launchPlan.skip && launchPlan.target) {
7972
8100
  const resolved = findProviderAndModel(compatible, launchPlan.target);
7973
8101
  if (!resolved) {
7974
- p10.log.error(
8102
+ p11.log.error(
7975
8103
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
7976
8104
  );
7977
8105
  return 1;
@@ -7979,12 +8107,12 @@ Error: ${launchPlan.error}
7979
8107
  activeProvider = resolved.provider;
7980
8108
  selectedModel = resolved.model;
7981
8109
  if (!agentStdout) {
7982
- p10.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
8110
+ p11.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
7983
8111
  }
7984
8112
  } else {
7985
8113
  if (!agentStdout) {
7986
8114
  console.log("");
7987
- p10.log.info(`Launching ${pc8.bold("Gemini CLI")} with relay-ai`);
8115
+ p11.log.info(`Launching ${pc8.bold("Gemini CLI")} with relay-ai`);
7988
8116
  }
7989
8117
  const chosenProvider = await pickGeminiProvider(
7990
8118
  compatible,
@@ -8016,7 +8144,7 @@ Error: ${launchPlan.error}
8016
8144
  recordLaunchSelection("gemini", activeProvider.id, selectedModel.id, prefs);
8017
8145
  const launchApiKey = await resolveLocalProviderApiKey(activeProvider);
8018
8146
  if (!launchApiKey?.trim()) {
8019
- p10.log.error(
8147
+ p11.log.error(
8020
8148
  `No API key found for ${activeProvider.name}. Set it with relay-ai providers add.`
8021
8149
  );
8022
8150
  return 1;
@@ -8112,14 +8240,14 @@ Error: ${launchPlan.error}
8112
8240
  oauthBackend = backendRoutes.backend;
8113
8241
  proxyHandle = await startGeminiProxy(finalRoutes, trace);
8114
8242
  } catch (err) {
8115
- p10.log.error(`Failed to start Gemini proxy: ${err instanceof Error ? err.message : String(err)}`);
8243
+ p11.log.error(`Failed to start Gemini proxy: ${err instanceof Error ? err.message : String(err)}`);
8116
8244
  oauthBackend?.handle.close();
8117
8245
  return 1;
8118
8246
  }
8119
8247
  const childEnv = prepareGeminiChildEnv(proxyHandle.port, proxyHandle.token);
8120
8248
  if (!agentStdout) {
8121
- p10.log.info(`Gemini proxy started on port ${proxyHandle.port}`);
8122
- p10.log.info(`\u{1F4A1} Type ${pc8.bold(".model <id>")} in the chat to switch models mid-session.`);
8249
+ p11.log.info(`Gemini proxy started on port ${proxyHandle.port}`);
8250
+ p11.log.info(`\u{1F4A1} Type ${pc8.bold(".model <id>")} in the chat to switch models mid-session.`);
8123
8251
  }
8124
8252
  let exitCode = 1;
8125
8253
  try {
@@ -8130,7 +8258,7 @@ Error: ${launchPlan.error}
8130
8258
  oauthBackend?.handle.close();
8131
8259
  }
8132
8260
  if (!agentStdout) {
8133
- p10.log.info("Gemini proxy stopped.");
8261
+ p11.log.info("Gemini proxy stopped.");
8134
8262
  }
8135
8263
  if (trace) {
8136
8264
  printTraceLog(getGeminiProxyDebugLogPath());
@@ -8140,7 +8268,7 @@ Error: ${launchPlan.error}
8140
8268
 
8141
8269
  // src/antigravity.ts
8142
8270
  import pc9 from "picocolors";
8143
- import * as p11 from "@clack/prompts";
8271
+ import * as p12 from "@clack/prompts";
8144
8272
 
8145
8273
  // src/antigravity/cloud-code-gateway.ts
8146
8274
  import http from "http";
@@ -8235,18 +8363,18 @@ var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Map([
8235
8363
  ["OBJECT", "object"],
8236
8364
  ["STRING", "string"]
8237
8365
  ]);
8238
- function expandTextWithThinking(text5) {
8239
- if (!text5.includes("<thinking>")) {
8240
- return [{ type: "text", text: text5 }];
8366
+ function expandTextWithThinking(text6) {
8367
+ if (!text6.includes("<thinking>")) {
8368
+ return [{ type: "text", text: text6 }];
8241
8369
  }
8242
8370
  const out = [];
8243
- const tokens = text5.split(/<thinking>([\s\S]*?)<\/thinking>/);
8371
+ const tokens = text6.split(/<thinking>([\s\S]*?)<\/thinking>/);
8244
8372
  for (let i = 0; i < tokens.length; i++) {
8245
8373
  const token = tokens[i] ?? "";
8246
8374
  if (!token.trim()) continue;
8247
8375
  out.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
8248
8376
  }
8249
- return out.length > 0 ? out : [{ type: "text", text: text5 }];
8377
+ return out.length > 0 ? out : [{ type: "text", text: text6 }];
8250
8378
  }
8251
8379
  function normalizeSchemaType(value) {
8252
8380
  if (typeof value === "string") {
@@ -8316,9 +8444,9 @@ function translateRequest(ccReq, options = {}) {
8316
8444
  continue;
8317
8445
  }
8318
8446
  const sdkRole = role === "model" ? "assistant" : "user";
8319
- const hasFunctionCall = msg.parts.some((p15) => p15.functionCall);
8320
- const hasAssistantReasoning = role === "model" && msg.parts.some((p15) => p15.thought || p15.text?.includes("<thinking>"));
8321
- const hasComplexParts = msg.parts.some((p15) => p15.thought || p15.inlineData || p15.functionCall || p15.functionResponse);
8447
+ const hasFunctionCall = msg.parts.some((p16) => p16.functionCall);
8448
+ const hasAssistantReasoning = role === "model" && msg.parts.some((p16) => p16.thought || p16.text?.includes("<thinking>"));
8449
+ const hasComplexParts = msg.parts.some((p16) => p16.thought || p16.inlineData || p16.functionCall || p16.functionResponse);
8322
8450
  const singleText = msg.parts.length === 1 ? msg.parts[0]?.text : void 0;
8323
8451
  if (!hasComplexParts && singleText !== void 0 && !singleText.includes("<thinking>")) {
8324
8452
  sdkMessages.push({
@@ -9471,7 +9599,7 @@ async function startCloudCodeGateway(routes, opts = {}) {
9471
9599
  const templateKey = opts.templateKey ?? "gemini-3.5-flash-low";
9472
9600
  const trace = opts.trace ?? false;
9473
9601
  const trackActiveRoute = opts.trackActiveRoute ?? false;
9474
- const log14 = opts.logFn ?? (() => {
9602
+ const log15 = opts.logFn ?? (() => {
9475
9603
  });
9476
9604
  const catalogFixture = fetchAvailableModels_default;
9477
9605
  const injectedCatalog = injectRelayModels(catalogFixture, routes, templateKey);
@@ -9521,12 +9649,12 @@ async function startCloudCodeGateway(routes, opts = {}) {
9521
9649
  const contentType = (req.headers["content-type"] ?? "").toLowerCase();
9522
9650
  const lowerUrl = url.toLowerCase();
9523
9651
  if (trace) {
9524
- log14(`[gateway] ${method} ${url}`);
9525
- log14(`[gateway] content-type: ${contentType}`);
9526
- log14(`[gateway] body-size: ${bodyStr.length}`);
9652
+ log15(`[gateway] ${method} ${url}`);
9653
+ log15(`[gateway] content-type: ${contentType}`);
9654
+ log15(`[gateway] body-size: ${bodyStr.length}`);
9527
9655
  }
9528
9656
  if (contentType.includes("proto") || contentType.includes("grpc") && !contentType.includes("json")) {
9529
- log14(`[gateway] UNSUPPORTED content-type: ${contentType}`);
9657
+ log15(`[gateway] UNSUPPORTED content-type: ${contentType}`);
9530
9658
  respondJson(res, 415, {
9531
9659
  error: {
9532
9660
  code: 415,
@@ -9547,47 +9675,47 @@ async function startCloudCodeGateway(routes, opts = {}) {
9547
9675
  }
9548
9676
  return value;
9549
9677
  }).slice(0, 500);
9550
- log14(`[gateway] body-preview: ${preview}`);
9678
+ log15(`[gateway] body-preview: ${preview}`);
9551
9679
  }
9552
9680
  if (lowerUrl.includes("loadcodeassist")) {
9553
- if (trace) log14("[gateway] \u2192 loadCodeAssist");
9681
+ if (trace) log15("[gateway] \u2192 loadCodeAssist");
9554
9682
  respondJson(res, 200, loadCodeAssist_default);
9555
9683
  return;
9556
9684
  }
9557
9685
  if (lowerUrl.includes("fetchavailablemodels") || lowerUrl.includes("getavailablemodels")) {
9558
- if (trace) log14("[gateway] \u2192 fetchAvailableModels");
9686
+ if (trace) log15("[gateway] \u2192 fetchAvailableModels");
9559
9687
  respondJson(res, 200, injectedCatalog);
9560
9688
  return;
9561
9689
  }
9562
9690
  if (lowerUrl.includes("modelconfigs")) {
9563
- if (trace) log14("[gateway] \u2192 listModelConfigs");
9691
+ if (trace) log15("[gateway] \u2192 listModelConfigs");
9564
9692
  respondJson(res, 200, modelConfigsResponse);
9565
9693
  return;
9566
9694
  }
9567
9695
  if (lowerUrl.includes("generatecontent") || lowerUrl.includes("generatechat")) {
9568
9696
  const model = parsed?.model;
9569
- if (trace) log14(`[gateway] extracted model: ${model ?? "N/A"}`);
9697
+ if (trace) log15(`[gateway] extracted model: ${model ?? "N/A"}`);
9570
9698
  const route = resolveRouteForModel(model);
9571
9699
  if (route) {
9572
9700
  if (trace) {
9573
- log14(
9701
+ log15(
9574
9702
  `[gateway] resolved route: ${route.catalogId} (${route.providerId}/${route.upstreamModelId} via ${model})`
9575
9703
  );
9576
9704
  }
9577
9705
  const media = sanitizeUnsupportedInlineData(parsed);
9578
9706
  parsed = media.request;
9579
9707
  if (media.latestUserTurnHasUnsupportedMedia) {
9580
- if (trace) log14("[gateway] unsupported media in current user turn; provider call skipped");
9708
+ if (trace) log15("[gateway] unsupported media in current user turn; provider call skipped");
9581
9709
  respondUnsupportedMedia(res, route, lowerUrl.includes("stream"));
9582
9710
  return;
9583
9711
  }
9584
9712
  if (trackActiveRoute && selectedSlotIds.has(model ?? "") && isUserTurnRequest(parsed)) {
9585
9713
  activeRoute = route;
9586
- if (trace) log14(`[gateway] active route: ${route.catalogId} via ${model}`);
9714
+ if (trace) log15(`[gateway] active route: ${route.catalogId} via ${model}`);
9587
9715
  }
9588
9716
  if (isCloudCodeOAuthRoute(route)) {
9589
- handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14).catch((err) => {
9590
- log14(`[gateway] cloud-code forward error: ${err instanceof Error ? err.stack || err.message : String(err)}`);
9717
+ handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log15).catch((err) => {
9718
+ log15(`[gateway] cloud-code forward error: ${err instanceof Error ? err.stack || err.message : String(err)}`);
9591
9719
  if (!res.headersSent) {
9592
9720
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9593
9721
  } else if (!res.writableEnded) {
@@ -9614,13 +9742,13 @@ async function startCloudCodeGateway(routes, opts = {}) {
9614
9742
  rememberReasoningEcho(reasoningEchoesByConversation, conversationKey, reasoning);
9615
9743
  };
9616
9744
  if (isStream) {
9617
- handleStreamingRequest(res, route, baseProviderOptions, parsed, log14, {
9745
+ handleStreamingRequest(res, route, baseProviderOptions, parsed, log15, {
9618
9746
  requestOptions,
9619
9747
  onReasoningWithToolCall: rememberReasoning,
9620
9748
  trace
9621
9749
  }).catch((err) => {
9622
- log14(`[gateway] stream error: ${formatUpstreamError(err)}`);
9623
- if (trace) log14(`[gateway] stream error detail: ${formatUpstreamErrorTrace(err)}`);
9750
+ log15(`[gateway] stream error: ${formatUpstreamError(err)}`);
9751
+ if (trace) log15(`[gateway] stream error detail: ${formatUpstreamErrorTrace(err)}`);
9624
9752
  if (!res.headersSent) {
9625
9753
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9626
9754
  } else if (!res.writableEnded) {
@@ -9628,13 +9756,13 @@ async function startCloudCodeGateway(routes, opts = {}) {
9628
9756
  }
9629
9757
  });
9630
9758
  } else {
9631
- handleUnaryRequest(res, route, baseProviderOptions, parsed, log14, {
9759
+ handleUnaryRequest(res, route, baseProviderOptions, parsed, log15, {
9632
9760
  requestOptions,
9633
9761
  onReasoningWithToolCall: rememberReasoning,
9634
9762
  trace
9635
9763
  }).catch((err) => {
9636
- log14(`[gateway] unary error: ${formatUpstreamError(err)}`);
9637
- if (trace) log14(`[gateway] unary error detail: ${formatUpstreamErrorTrace(err)}`);
9764
+ log15(`[gateway] unary error: ${formatUpstreamError(err)}`);
9765
+ if (trace) log15(`[gateway] unary error detail: ${formatUpstreamErrorTrace(err)}`);
9638
9766
  if (!res.headersSent) {
9639
9767
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9640
9768
  }
@@ -9730,7 +9858,7 @@ async function startCloudCodeGateway(routes, opts = {}) {
9730
9858
  return;
9731
9859
  }
9732
9860
  if (trace) {
9733
- log14(`[gateway] unknown endpoint: ${url}`);
9861
+ log15(`[gateway] unknown endpoint: ${url}`);
9734
9862
  }
9735
9863
  respondJson(res, 200, {});
9736
9864
  }).catch((err) => {
@@ -9824,7 +9952,7 @@ function rememberReasoningEcho(cache, key, reasoning) {
9824
9952
  existing.push(normalized);
9825
9953
  cache.set(key, existing.slice(-MAX_REASONING_ECHOES_PER_CONVERSATION));
9826
9954
  }
9827
- async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14) {
9955
+ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log15) {
9828
9956
  const projectId = typeof route.providerData?.projectId === "string" ? route.providerData.projectId : "";
9829
9957
  if (!projectId) {
9830
9958
  respondJson(res, 500, {
@@ -9853,7 +9981,7 @@ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14
9853
9981
  });
9854
9982
  if (!upstream.ok) {
9855
9983
  const errBody = await upstream.text();
9856
- log14(`[gateway] cloud-code upstream error ${upstream.status}: ${errBody}`);
9984
+ log15(`[gateway] cloud-code upstream error ${upstream.status}: ${errBody}`);
9857
9985
  respondJson(res, upstream.status >= 500 ? 502 : upstream.status, {
9858
9986
  error: { code: upstream.status, message: errBody || upstream.statusText }
9859
9987
  });
@@ -9883,11 +10011,11 @@ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14
9883
10011
  });
9884
10012
  res.end(body);
9885
10013
  }
9886
- function emitThinkingDelta(res, route, responseId, text5, startSse) {
9887
- if (!text5) return;
10014
+ function emitThinkingDelta(res, route, responseId, text6, startSse) {
10015
+ if (!text6) return;
9888
10016
  startSse();
9889
10017
  const chunk = formatCloudCodeChunk({
9890
- thought: text5,
10018
+ thought: text6,
9891
10019
  modelVersion: route.catalogId,
9892
10020
  responseId
9893
10021
  });
@@ -9895,9 +10023,9 @@ function emitThinkingDelta(res, route, responseId, text5, startSse) {
9895
10023
 
9896
10024
  `);
9897
10025
  }
9898
- function trailingPartial(text5, tag) {
9899
- for (let len = Math.min(tag.length - 1, text5.length); len > 0; len--) {
9900
- if (text5.endsWith(tag.slice(0, len))) return len;
10026
+ function trailingPartial(text6, tag) {
10027
+ for (let len = Math.min(tag.length - 1, text6.length); len > 0; len--) {
10028
+ if (text6.endsWith(tag.slice(0, len))) return len;
9901
10029
  }
9902
10030
  return 0;
9903
10031
  }
@@ -9909,13 +10037,13 @@ function createThinkFilter() {
9909
10037
  let src = partial + chunk;
9910
10038
  partial = "";
9911
10039
  let thought = "";
9912
- let text5 = "";
10040
+ let text6 = "";
9913
10041
  while (src.length > 0) {
9914
10042
  if (state === "scanning") {
9915
10043
  const idx = src.indexOf("<think>");
9916
10044
  if (idx === -1) {
9917
10045
  const len = trailingPartial(src, "<think>");
9918
- text5 += src.slice(0, src.length - len);
10046
+ text6 += src.slice(0, src.length - len);
9919
10047
  if (len > 0) {
9920
10048
  partial = src.slice(src.length - len);
9921
10049
  } else {
@@ -9923,7 +10051,7 @@ function createThinkFilter() {
9923
10051
  }
9924
10052
  break;
9925
10053
  }
9926
- text5 += src.slice(0, idx);
10054
+ text6 += src.slice(0, idx);
9927
10055
  src = src.slice(idx + 7);
9928
10056
  state = "inside";
9929
10057
  } else {
@@ -9940,7 +10068,7 @@ function createThinkFilter() {
9940
10068
  state = "passthrough";
9941
10069
  }
9942
10070
  }
9943
- return { thought, text: text5 };
10071
+ return { thought, text: text6 };
9944
10072
  };
9945
10073
  }
9946
10074
  function emitStreamError(res, route, responseId, message, startSse) {
@@ -10002,8 +10130,8 @@ function respondUnsupportedMedia(res, route, streaming) {
10002
10130
  metadata: {}
10003
10131
  });
10004
10132
  }
10005
- function parsePseudoToolCall(text5, knownToolNames) {
10006
- const trimmed = text5.trim();
10133
+ function parsePseudoToolCall(text6, knownToolNames) {
10134
+ const trimmed = text6.trim();
10007
10135
  if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
10008
10136
  try {
10009
10137
  const obj = JSON.parse(trimmed);
@@ -10018,13 +10146,13 @@ function parsePseudoToolCall(text5, knownToolNames) {
10018
10146
  }
10019
10147
  return null;
10020
10148
  }
10021
- async function handleStreamingRequest(res, route, providerOptions, parsed, log14, options = {}) {
10149
+ async function handleStreamingRequest(res, route, providerOptions, parsed, log15, options = {}) {
10022
10150
  const sdkParams = applyClaudeCodeOAuthIdentity(route, translateRequest(parsed, {
10023
10151
  ...options.requestOptions,
10024
10152
  maxTools: maxToolsForNpm(route.npm)
10025
10153
  }));
10026
10154
  if (options.trace) {
10027
- log14(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10155
+ log15(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10028
10156
  }
10029
10157
  const effectiveProviderOptions = deepMergeProviderOptions(
10030
10158
  providerOptions,
@@ -10085,35 +10213,35 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10085
10213
  bufferingJsonText = false;
10086
10214
  };
10087
10215
  for await (const part of stream) {
10088
- const p15 = part;
10089
- if (p15.type === "reasoning-delta" || p15.type === "reasoning") {
10090
- const reasoning = reasoningDeltaText(p15);
10216
+ const p16 = part;
10217
+ if (p16.type === "reasoning-delta" || p16.type === "reasoning") {
10218
+ const reasoning = reasoningDeltaText(p16);
10091
10219
  responseReasoning += reasoning;
10092
10220
  emitThinkingDelta(res, route, responseId, reasoning, startSse);
10093
10221
  continue;
10094
10222
  }
10095
- if (p15.type === "text-delta") {
10096
- const { thought, text: text5 } = thinkFilter(reasoningDeltaText(p15));
10223
+ if (p16.type === "text-delta") {
10224
+ const { thought, text: text6 } = thinkFilter(reasoningDeltaText(p16));
10097
10225
  if (thought) {
10098
10226
  responseReasoning += thought;
10099
10227
  emitThinkingDelta(res, route, responseId, thought, startSse);
10100
10228
  }
10101
- if (text5) {
10102
- log14(`[gateway] text-delta: ${JSON.stringify(text5.slice(0, 500))}`);
10103
- if (!bufferingJsonText && (textBuffer + text5).trimStart().startsWith("{")) {
10229
+ if (text6) {
10230
+ log15(`[gateway] text-delta: ${JSON.stringify(text6.slice(0, 500))}`);
10231
+ if (!bufferingJsonText && (textBuffer + text6).trimStart().startsWith("{")) {
10104
10232
  bufferingJsonText = true;
10105
10233
  }
10106
10234
  if (bufferingJsonText) {
10107
- textBuffer += text5;
10235
+ textBuffer += text6;
10108
10236
  const pseudoTool = textBuffer.trimEnd().endsWith("}") ? parsePseudoToolCall(textBuffer, knownToolNames) : null;
10109
10237
  if (pseudoTool) {
10110
- log14(`[gateway] parsed pseudo tool-call from text: ${pseudoTool.name}`);
10238
+ log15(`[gateway] parsed pseudo tool-call from text: ${pseudoTool.name}`);
10111
10239
  emitPseudoToolCall(pseudoTool);
10112
10240
  }
10113
10241
  } else {
10114
10242
  startSse();
10115
10243
  const chunk = formatCloudCodeChunk({
10116
- text: text5,
10244
+ text: text6,
10117
10245
  modelVersion: route.catalogId,
10118
10246
  responseId
10119
10247
  });
@@ -10122,25 +10250,25 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10122
10250
  `);
10123
10251
  }
10124
10252
  }
10125
- } else if (p15.type === "tool-input-start") {
10126
- const id = p15.id ?? p15.toolCallId;
10127
- toolCallBuffers.set(id, { name: p15.toolName, json: "" });
10128
- } else if (p15.type === "tool-input-delta") {
10129
- const id = p15.id ?? p15.toolCallId;
10253
+ } else if (p16.type === "tool-input-start") {
10254
+ const id = p16.id ?? p16.toolCallId;
10255
+ toolCallBuffers.set(id, { name: p16.toolName, json: "" });
10256
+ } else if (p16.type === "tool-input-delta") {
10257
+ const id = p16.id ?? p16.toolCallId;
10130
10258
  const buf = toolCallBuffers.get(id);
10131
- if (buf) buf.json += p15.delta;
10132
- } else if (p15.type === "tool-call") {
10259
+ if (buf) buf.json += p16.delta;
10260
+ } else if (p16.type === "tool-call") {
10133
10261
  sawToolCall = true;
10134
- const id = p15.toolCallId ?? p15.id;
10262
+ const id = p16.toolCallId ?? p16.id;
10135
10263
  const buf = toolCallBuffers.get(id);
10136
10264
  let args = {};
10137
10265
  try {
10138
- args = buf ? JSON.parse(buf.json || "{}") : p15.input || {};
10266
+ args = buf ? JSON.parse(buf.json || "{}") : p16.input || {};
10139
10267
  } catch {
10140
- args = p15.input || {};
10268
+ args = p16.input || {};
10141
10269
  }
10142
- const name = buf ? buf.name : p15.toolName;
10143
- log14(`[gateway] tool-call: ${name}`);
10270
+ const name = buf ? buf.name : p16.toolName;
10271
+ log15(`[gateway] tool-call: ${name}`);
10144
10272
  startSse();
10145
10273
  const chunk = formatCloudCodeChunk({
10146
10274
  functionCall: { name, args: normalizeFunctionCallArgs(args) },
@@ -10150,42 +10278,42 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10150
10278
  res.write(`data: ${JSON.stringify(chunk)}
10151
10279
 
10152
10280
  `);
10153
- } else if (p15.type === "finish") {
10154
- log14(`[gateway] finish: ${p15.finishReason ?? "unknown"}`);
10281
+ } else if (p16.type === "finish") {
10282
+ log15(`[gateway] finish: ${p16.finishReason ?? "unknown"}`);
10155
10283
  if (textBuffer) {
10156
10284
  const pseudoTool = parsePseudoToolCall(textBuffer, knownToolNames);
10157
10285
  if (pseudoTool) {
10158
- log14(`[gateway] parsed pseudo tool-call on finish: ${pseudoTool.name}`);
10286
+ log15(`[gateway] parsed pseudo tool-call on finish: ${pseudoTool.name}`);
10159
10287
  emitPseudoToolCall(pseudoTool);
10160
10288
  } else {
10161
10289
  flushBufferedText();
10162
10290
  }
10163
10291
  }
10164
10292
  startSse();
10165
- const reason = mapFinishReason2(p15.finishReason ?? "");
10293
+ const reason = mapFinishReason2(p16.finishReason ?? "");
10166
10294
  const chunk = formatCloudCodeChunk({
10167
10295
  modelVersion: route.catalogId,
10168
10296
  responseId,
10169
10297
  finishReason: reason,
10170
10298
  usage: {
10171
- promptTokens: p15.totalUsage?.inputTokens || 0,
10172
- completionTokens: p15.totalUsage?.outputTokens || 0
10299
+ promptTokens: p16.totalUsage?.inputTokens || 0,
10300
+ completionTokens: p16.totalUsage?.outputTokens || 0
10173
10301
  }
10174
10302
  });
10175
10303
  res.write(`data: ${JSON.stringify(chunk)}
10176
10304
 
10177
10305
  `);
10178
- } else if (p15.type === "error") {
10179
- const message = formatUpstreamError(p15.error);
10180
- log14(`[gateway] stream provider error: ${message}`);
10306
+ } else if (p16.type === "error") {
10307
+ const message = formatUpstreamError(p16.error);
10308
+ log15(`[gateway] stream provider error: ${message}`);
10181
10309
  if (options.trace) {
10182
- log14(`[gateway] stream provider error detail: ${formatUpstreamErrorTrace(p15.error)}`);
10310
+ log15(`[gateway] stream provider error detail: ${formatUpstreamErrorTrace(p16.error)}`);
10183
10311
  }
10184
10312
  flushBufferedText();
10185
10313
  emitStreamError(res, route, responseId, message, startSse);
10186
10314
  break;
10187
- } else if (p15.type === "reasoning-start" || p15.type === "reasoning-end") {
10188
- log14(`[gateway] ${p15.type}`);
10315
+ } else if (p16.type === "reasoning-start" || p16.type === "reasoning-end") {
10316
+ log15(`[gateway] ${p16.type}`);
10189
10317
  }
10190
10318
  }
10191
10319
  if (!res.headersSent) {
@@ -10196,13 +10324,13 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10196
10324
  }
10197
10325
  res.end();
10198
10326
  }
10199
- async function handleUnaryRequest(res, route, providerOptions, parsed, log14, options = {}) {
10327
+ async function handleUnaryRequest(res, route, providerOptions, parsed, log15, options = {}) {
10200
10328
  const sdkParams = applyClaudeCodeOAuthIdentity(route, translateRequest(parsed, {
10201
10329
  ...options.requestOptions,
10202
10330
  maxTools: maxToolsForNpm(route.npm)
10203
10331
  }));
10204
10332
  if (options.trace) {
10205
- log14(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10333
+ log15(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10206
10334
  }
10207
10335
  const effectiveProviderOptions = deepMergeProviderOptions(
10208
10336
  providerOptions,
@@ -10757,11 +10885,11 @@ function resolveAntigravityBootModel(provider, modelSelector) {
10757
10885
  async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10758
10886
  const resolved = favorites.map((favorite) => resolveFavoriteModel(favorite, allProviders)).filter((entry) => entry !== null);
10759
10887
  if (resolved.length === 0) {
10760
- p11.log.warn("No Antigravity CLI favorites are available.");
10761
- p11.log.info(pc9.dim("Manage them with `relay-ai favorites --agy`."));
10888
+ p12.log.warn("No Antigravity CLI favorites are available.");
10889
+ p12.log.info(pc9.dim("Manage them with `relay-ai favorites --agy`."));
10762
10890
  return null;
10763
10891
  }
10764
- const picked = await p11.select({
10892
+ const picked = await p12.select({
10765
10893
  message: "Launch from Antigravity CLI favorites",
10766
10894
  options: resolved.map(({ provider, model }) => ({
10767
10895
  value: `${provider.id}:${model.id}`,
@@ -10770,8 +10898,8 @@ async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10770
10898
  })),
10771
10899
  initialValue: `${resolved[0].provider.id}:${resolved[0].model.id}`
10772
10900
  });
10773
- if (p11.isCancel(picked)) {
10774
- p11.cancel("Cancelled.");
10901
+ if (p12.isCancel(picked)) {
10902
+ p12.cancel("Cancelled.");
10775
10903
  return null;
10776
10904
  }
10777
10905
  const [providerId, ...modelParts] = picked.split(":");
@@ -10780,31 +10908,31 @@ async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10780
10908
  }
10781
10909
  async function resolveAntigravityLaunch(prefs, boot) {
10782
10910
  let catalog;
10783
- const catalogSpinner = p11.spinner();
10911
+ const catalogSpinner = p12.spinner();
10784
10912
  catalogSpinner.start("Loading providers...");
10785
10913
  try {
10786
10914
  catalog = await fetchProviderCatalog();
10787
10915
  } catch (err) {
10788
10916
  catalogSpinner.stop("");
10789
- p11.log.error(String(err instanceof Error ? err.message : err));
10917
+ p12.log.error(String(err instanceof Error ? err.message : err));
10790
10918
  return null;
10791
10919
  }
10792
10920
  catalogSpinner.stop("");
10793
10921
  const allProviders = providersForTarget(providersForPicker(catalog), "antigravity");
10794
10922
  if (allProviders.length === 0) {
10795
- p11.log.warn("No providers available.");
10796
- p11.log.info(pc9.dim("Run relay-ai providers add or import to get started."));
10923
+ p12.log.warn("No providers available.");
10924
+ p12.log.info(pc9.dim("Run relay-ai providers add or import to get started."));
10797
10925
  return null;
10798
10926
  }
10799
10927
  if (boot?.launchProvider && boot?.launchModel) {
10800
- const provider = allProviders.find((p15) => p15.id === boot.launchProvider);
10928
+ const provider = allProviders.find((p16) => p16.id === boot.launchProvider);
10801
10929
  if (!provider) {
10802
- p11.log.error(`Provider not found: ${boot.launchProvider}`);
10930
+ p12.log.error(`Provider not found: ${boot.launchProvider}`);
10803
10931
  return null;
10804
10932
  }
10805
10933
  const { model, error } = resolveAntigravityBootModel(provider, boot.launchModel);
10806
10934
  if (!model) {
10807
- p11.log.error(error ?? `Model not found: ${boot.launchModel} on provider ${provider.name}`);
10935
+ p12.log.error(error ?? `Model not found: ${boot.launchModel} on provider ${provider.name}`);
10808
10936
  return null;
10809
10937
  }
10810
10938
  return { provider, model, allProviders };
@@ -10821,13 +10949,13 @@ async function resolveAntigravityLaunch(prefs, boot) {
10821
10949
  const conflicts = detectConflicts();
10822
10950
  let currentInitialProvider = initialProvider;
10823
10951
  while (true) {
10824
- const chosen = await p11.select({
10952
+ const chosen = await p12.select({
10825
10953
  message: "Which provider?",
10826
10954
  options: providerOptions,
10827
10955
  initialValue: currentInitialProvider
10828
10956
  });
10829
- if (p11.isCancel(chosen)) {
10830
- p11.cancel("Cancelled.");
10957
+ if (p12.isCancel(chosen)) {
10958
+ p12.cancel("Cancelled.");
10831
10959
  return null;
10832
10960
  }
10833
10961
  if (chosen === AGY_FAVORITES_PROVIDER_ID) {
@@ -10860,32 +10988,32 @@ async function resolveAndBuildRoutes(provider, model, allProviders, prefs, opts)
10860
10988
  maxRoutes: opts.maxRoutes
10861
10989
  });
10862
10990
  if (!result) {
10863
- p11.log.error(`No credential for ${provider.name}. Run: relay-ai providers auth ${provider.id} or add an API key.`);
10991
+ p12.log.error(`No credential for ${provider.name}. Run: relay-ai providers auth ${provider.id} or add an API key.`);
10864
10992
  return null;
10865
10993
  }
10866
10994
  if (result.routes.length > 1) {
10867
- p11.log.info(
10995
+ p12.log.info(
10868
10996
  `Favorites mode active \u2014 Antigravity picker will show ${result.routes.length} models.`
10869
10997
  );
10870
- p11.log.info("Edit with `relay-ai favorites --agy`.");
10998
+ p12.log.info("Edit with `relay-ai favorites --agy`.");
10871
10999
  }
10872
11000
  if (result.droppedFavorites.length > 0) {
10873
- p11.log.warn(
11001
+ p12.log.warn(
10874
11002
  `Skipped ${result.droppedFavorites.length} stale/unauthorized favorite(s): ` + result.droppedFavorites.map((fav) => `${fav.providerId}:${fav.modelId}`).join(", ")
10875
11003
  );
10876
11004
  }
10877
11005
  if (result.capacitySkippedFavorites.length > 0) {
10878
- p11.log.warn(formatAgyCapacityWarning(opts.validatedSlotCount, result.capacitySkippedFavorites.length));
10879
- p11.log.warn(
11006
+ p12.log.warn(formatAgyCapacityWarning(opts.validatedSlotCount, result.capacitySkippedFavorites.length));
11007
+ p12.log.warn(
10880
11008
  "Not exposed: " + result.capacitySkippedFavorites.map((fav) => `${fav.providerId}:${fav.modelId}`).join(", ")
10881
11009
  );
10882
11010
  if (opts.pauseForCapacityWarning && isInteractiveTerminal() && !agyArgsAreNonInteractive(opts.childArgs)) {
10883
- const proceed = await p11.confirm({
11011
+ const proceed = await p12.confirm({
10884
11012
  message: "Continue with the validated AGY switch catalog?",
10885
11013
  initialValue: true
10886
11014
  });
10887
- if (p11.isCancel(proceed) || !proceed) {
10888
- p11.cancel("Cancelled.");
11015
+ if (p12.isCancel(proceed) || !proceed) {
11016
+ p12.cancel("Cancelled.");
10889
11017
  return null;
10890
11018
  }
10891
11019
  }
@@ -10937,7 +11065,7 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10937
11065
  const prefs = loadPreferences();
10938
11066
  relayIntro(intro);
10939
11067
  if (tracePrefix === "agy" && (prefs.favoriteModels?.length ?? 0) > 0 && (prefs.antigravityCliFavoriteModels?.length ?? 0) === 0 && !prefs.antigravityCliFavoritesHintShown) {
10940
- p11.log.info("Tip: AGY uses its own favorites list. Run `relay-ai favorites --agy` to set up switching.");
11068
+ p12.log.info("Tip: AGY uses its own favorites list. Run `relay-ai favorites --agy` to set up switching.");
10941
11069
  savePreferences({ antigravityCliFavoritesHintShown: true });
10942
11070
  }
10943
11071
  const selection = await resolveAntigravityLaunch(prefs, boot);
@@ -10950,7 +11078,7 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10950
11078
  fixture: fetchAvailableModels_default
10951
11079
  });
10952
11080
  for (const warning of compatibility.warnings) {
10953
- p11.log.warn(warning);
11081
+ p12.log.warn(warning);
10954
11082
  }
10955
11083
  const routeLimit = compatibility.mode === "multi-model" ? compatibility.validatedSwitchSlotCount : 1;
10956
11084
  const routeResult = await resolveAndBuildRoutes(provider, model, allProviders, prefs, {
@@ -10970,12 +11098,12 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10970
11098
  try {
10971
11099
  gatewayHandle = await startCloudCodeGateway(routeResult.routes, { trace, logFn });
10972
11100
  } catch (err) {
10973
- p11.log.error(`Failed to start Cloud Code gateway: ${err}`);
11101
+ p12.log.error(`Failed to start Cloud Code gateway: ${err}`);
10974
11102
  return 1;
10975
11103
  }
10976
- p11.log.info(`Cloud Code gateway on ${pc9.cyan(`127.0.0.1:${gatewayHandle.port}`)}`);
10977
- p11.log.success(`Active model: ${formatCodexModelLabel(model)} ${pc9.dim("via")} ${provider.name}`);
10978
- if (traceLogPath) p11.log.info(`Gateway trace \u2192 ${pc9.dim(traceLogPath)}`);
11104
+ p12.log.info(`Cloud Code gateway on ${pc9.cyan(`127.0.0.1:${gatewayHandle.port}`)}`);
11105
+ p12.log.success(`Active model: ${formatCodexModelLabel(model)} ${pc9.dim("via")} ${provider.name}`);
11106
+ if (traceLogPath) p12.log.info(`Gateway trace \u2192 ${pc9.dim(traceLogPath)}`);
10979
11107
  relayOutro("Launching", `${formatCodexModelLabel(model)} (${provider.name})`);
10980
11108
  try {
10981
11109
  const cleanEnv = buildAntigravityChildEnv(gatewayHandle.url);
@@ -11003,12 +11131,12 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
11003
11131
  async (env, _routes, gatewayHandle) => {
11004
11132
  const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "app-profile");
11005
11133
  if (isAntigravityAppRunning(profileDir)) {
11006
- const restart = await p11.confirm({
11134
+ const restart = await p12.confirm({
11007
11135
  message: "Restart Antigravity to apply this Relay gateway?",
11008
11136
  initialValue: true
11009
11137
  });
11010
- if (p11.isCancel(restart) || !restart) {
11011
- p11.log.info("Quit and reopen Antigravity when you are ready for the new gateway to take effect.");
11138
+ if (p12.isCancel(restart) || !restart) {
11139
+ p12.log.info("Quit and reopen Antigravity when you are ready for the new gateway to take effect.");
11012
11140
  return 0;
11013
11141
  }
11014
11142
  quitAntigravityAppGracefully();
@@ -11019,18 +11147,18 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
11019
11147
  }
11020
11148
  const launchCode = await launchAntigravityApp(env, profileDir, gatewayHandle.url, childArgs);
11021
11149
  if (launchCode !== 0) return launchCode;
11022
- p11.log.info("Antigravity is using the Relay Cloud Code gateway.");
11023
- p11.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11150
+ p12.log.info("Antigravity is using the Relay Cloud Code gateway.");
11151
+ p12.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11024
11152
  await waitForShutdown();
11025
11153
  await new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS));
11026
11154
  console.log("");
11027
- p11.log.step("Gateway stopped.");
11028
- const shouldClose = await p11.confirm({
11155
+ p12.log.step("Gateway stopped.");
11156
+ const shouldClose = await p12.confirm({
11029
11157
  message: "Close Antigravity?",
11030
11158
  initialValue: true
11031
11159
  });
11032
- if (!p11.isCancel(shouldClose) && shouldClose) {
11033
- p11.log.step("Stopping Antigravity...");
11160
+ if (!p12.isCancel(shouldClose) && shouldClose) {
11161
+ p12.log.step("Stopping Antigravity...");
11034
11162
  quitAntigravityAppGracefully();
11035
11163
  if (!await waitForAntigravityAppQuit(profileDir)) {
11036
11164
  forceQuitAntigravityApp(profileDir);
@@ -11051,12 +11179,12 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11051
11179
  async (env, _routes, gatewayHandle) => {
11052
11180
  const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "profile");
11053
11181
  if (isAntigravityIdeRunning(profileDir)) {
11054
- const restart = await p11.confirm({
11182
+ const restart = await p12.confirm({
11055
11183
  message: "Restart Antigravity IDE to apply this Relay gateway?",
11056
11184
  initialValue: true
11057
11185
  });
11058
- if (p11.isCancel(restart) || !restart) {
11059
- p11.log.info("Quit and reopen Antigravity IDE when you are ready for the new gateway to take effect.");
11186
+ if (p12.isCancel(restart) || !restart) {
11187
+ p12.log.info("Quit and reopen Antigravity IDE when you are ready for the new gateway to take effect.");
11060
11188
  return 0;
11061
11189
  }
11062
11190
  quitAntigravityIdeGracefully();
@@ -11067,18 +11195,18 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11067
11195
  }
11068
11196
  const launchCode = await launchAntigravityIde(env, profileDir, gatewayHandle.url, childArgs);
11069
11197
  if (launchCode !== 0) return launchCode;
11070
- p11.log.info("Antigravity IDE is using the Relay Cloud Code gateway.");
11071
- p11.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11198
+ p12.log.info("Antigravity IDE is using the Relay Cloud Code gateway.");
11199
+ p12.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11072
11200
  await waitForShutdown();
11073
11201
  await new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS));
11074
11202
  console.log("");
11075
- p11.log.step("Gateway stopped.");
11076
- const shouldClose = await p11.confirm({
11203
+ p12.log.step("Gateway stopped.");
11204
+ const shouldClose = await p12.confirm({
11077
11205
  message: "Close Antigravity IDE?",
11078
11206
  initialValue: true
11079
11207
  });
11080
- if (!p11.isCancel(shouldClose) && shouldClose) {
11081
- p11.log.step("Stopping Antigravity IDE...");
11208
+ if (!p12.isCancel(shouldClose) && shouldClose) {
11209
+ p12.log.step("Stopping Antigravity IDE...");
11082
11210
  quitAntigravityIdeGracefully();
11083
11211
  if (!await waitForAntigravityIdeQuit(profileDir)) {
11084
11212
  forceQuitAntigravityIde(profileDir);
@@ -11093,7 +11221,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11093
11221
 
11094
11222
  // src/codex-app.ts
11095
11223
  import pc10 from "picocolors";
11096
- import * as p12 from "@clack/prompts";
11224
+ import * as p13 from "@clack/prompts";
11097
11225
  import { join as join13 } from "path";
11098
11226
 
11099
11227
  // src/codex/app-provider-routes.ts
@@ -11220,12 +11348,12 @@ function readCodexConfigText(path3 = getCodexConfigPath()) {
11220
11348
  if (!existsSync8(path3)) return "";
11221
11349
  return readFileSync3(path3, "utf8");
11222
11350
  }
11223
- function parseCodexConfig(text5) {
11224
- if (!text5.trim()) return {};
11225
- return asRecord(parse(text5));
11351
+ function parseCodexConfig(text6) {
11352
+ if (!text6.trim()) return {};
11353
+ return asRecord(parse(text6));
11226
11354
  }
11227
- function captureRestoreState(text5) {
11228
- const config = parseCodexConfig(text5);
11355
+ function captureRestoreState(text6) {
11356
+ const config = parseCodexConfig(text6);
11229
11357
  const profile = rootString(config, "profile");
11230
11358
  const model = rootString(config, "model");
11231
11359
  const modelProvider = rootString(config, "model_provider");
@@ -11257,8 +11385,8 @@ function captureRestoreState(text5) {
11257
11385
  multiAgentV2: features.multi_agent_v2
11258
11386
  };
11259
11387
  }
11260
- function isAppManagedConfig(text5) {
11261
- const config = parseCodexConfig(text5);
11388
+ function isAppManagedConfig(text6) {
11389
+ const config = parseCodexConfig(text6);
11262
11390
  const mp = rootString(config, "model_provider");
11263
11391
  if (mp.had && mp.value === CODEX_APP_PROVIDER_ID) return true;
11264
11392
  const baseUrl = rootString(config, "openai_base_url");
@@ -11327,8 +11455,8 @@ function mergeAppConfig(existing, spec) {
11327
11455
  }
11328
11456
  return out;
11329
11457
  }
11330
- function validateAppConfigText(text5, spec) {
11331
- const config = parseCodexConfig(text5);
11458
+ function validateAppConfigText(text6, spec) {
11459
+ const config = parseCodexConfig(text6);
11332
11460
  if ("profile" in config) {
11333
11461
  throw new Error("Generated config still contains legacy root profile key");
11334
11462
  }
@@ -11362,17 +11490,17 @@ function applyAppConfigPatch(spec, configPath = getCodexConfigPath()) {
11362
11490
  throw new Error(`Invalid existing Codex config at ${configPath}: ${err instanceof Error ? err.message : err}`);
11363
11491
  }
11364
11492
  const merged = mergeAppConfig(existing, spec);
11365
- const text5 = `${stringify(merged)}
11493
+ const text6 = `${stringify(merged)}
11366
11494
  `;
11367
- validateAppConfigText(text5, spec);
11495
+ validateAppConfigText(text6, spec);
11368
11496
  mkdirSync4(dirname2(configPath), { recursive: true });
11369
- atomicWriteFile(configPath, text5);
11497
+ atomicWriteFile(configPath, text6);
11370
11498
  const written = readCodexConfigText(configPath);
11371
- if (written !== text5) {
11499
+ if (written !== text6) {
11372
11500
  throw new Error(`Codex config readback mismatch at ${configPath}`);
11373
11501
  }
11374
11502
  validateAppConfigText(written, spec);
11375
- return text5;
11503
+ return text6;
11376
11504
  }
11377
11505
  function applyRestoreKey(config, key, had, value) {
11378
11506
  if (had && value !== void 0) {
@@ -11432,10 +11560,10 @@ function restoreConfigFromState(state, configPath = getCodexConfigPath()) {
11432
11560
  return true;
11433
11561
  }
11434
11562
  function previewAppConfigToml(spec) {
11435
- const text5 = `${stringify(buildCodexAppRootConfig(spec))}
11563
+ const text6 = `${stringify(buildCodexAppRootConfig(spec))}
11436
11564
  `;
11437
- validateAppConfigText(text5, spec);
11438
- return text5;
11565
+ validateAppConfigText(text6, spec);
11566
+ return text6;
11439
11567
  }
11440
11568
 
11441
11569
  // src/codex/app-readiness.ts
@@ -11544,12 +11672,12 @@ function backupConfigToml(env = process.env) {
11544
11672
  return backupPath;
11545
11673
  }
11546
11674
  function saveAppRestoreStateBeforePatch(env = process.env) {
11547
- const text5 = readCodexConfigText();
11675
+ const text6 = readCodexConfigText();
11548
11676
  const existing = readAppRestoreState(env);
11549
- if (existing && isAppManagedConfig(text5)) {
11677
+ if (existing && isAppManagedConfig(text6)) {
11550
11678
  return existing;
11551
11679
  }
11552
- const state = captureRestoreState(text5);
11680
+ const state = captureRestoreState(text6);
11553
11681
  writeAppRestoreState(state, env);
11554
11682
  return state;
11555
11683
  }
@@ -11592,8 +11720,8 @@ function restoreCodexAppOverlay(env = process.env) {
11592
11720
  message: `Another relay-ai codex-app session is running (pid ${lock.pid}). Ctrl+C it first, then run --restore.`
11593
11721
  };
11594
11722
  }
11595
- const text5 = readCodexConfigText();
11596
- const managed = isAppManagedConfig(text5);
11723
+ const text6 = readCodexConfigText();
11724
+ const managed = isAppManagedConfig(text6);
11597
11725
  const restoreState = readAppRestoreState(env);
11598
11726
  if (!managed && !restoreState && !lock) {
11599
11727
  removeAppCatalogs(env);
@@ -11665,10 +11793,14 @@ function waitForShutdown2() {
11665
11793
  async function shutdownCodexAppSession(dependencies) {
11666
11794
  if (dependencies.isAppRunning()) {
11667
11795
  dependencies.quitApp();
11668
- const exited = await dependencies.waitForAppExit();
11796
+ let exited = await dependencies.waitForAppExit();
11797
+ if (!exited && dependencies.forceQuitApp) {
11798
+ dependencies.forceQuitApp();
11799
+ exited = await dependencies.waitForAppExit();
11800
+ }
11669
11801
  if (!exited) {
11670
11802
  throw new Error(
11671
- "ChatGPT Desktop did not exit after graceful shutdown; refusing to restore config until Desktop exits. Close Desktop, then run relay-ai codex-app --restore."
11803
+ "ChatGPT Desktop did not exit after graceful shutdown or force-quit; refusing to restore config until Desktop exits. Close Desktop, then run relay-ai codex-app --restore."
11672
11804
  );
11673
11805
  }
11674
11806
  }
@@ -11708,14 +11840,14 @@ async function waitForShutdownWithConfirm(assumeYes = false) {
11708
11840
  if (signal !== "sigint") return signal;
11709
11841
  if (assumeYes) return signal;
11710
11842
  console.log("");
11711
- const choice = await p12.select({
11843
+ const choice = await p13.select({
11712
11844
  message: "Close ChatGPT Desktop and restore your Codex config?",
11713
11845
  options: [
11714
11846
  { value: "yes", label: "Yes, close ChatGPT Desktop and restore config" },
11715
11847
  { value: "no", label: "No, keep session running" }
11716
11848
  ]
11717
11849
  });
11718
- if (p12.isCancel(choice) || choice === "yes") return signal;
11850
+ if (p13.isCancel(choice) || choice === "yes") return signal;
11719
11851
  }
11720
11852
  }
11721
11853
  function codexAppHelpText() {
@@ -11789,26 +11921,26 @@ function vertexEntryToLocalModel2(entry) {
11789
11921
  }
11790
11922
  async function runCodexAppVertexLaunch(configOnly, trace = false) {
11791
11923
  if (!hasApplicationDefaultCredentials()) {
11792
- p12.log.error("Google Application Default Credentials not found.");
11793
- p12.log.info("Run: gcloud auth application-default login");
11924
+ p13.log.error("Google Application Default Credentials not found.");
11925
+ p13.log.info("Run: gcloud auth application-default login");
11794
11926
  return 1;
11795
11927
  }
11796
11928
  const config = buildVertexRuntimeConfig();
11797
11929
  if (!config) {
11798
- p12.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
11799
- p12.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
11930
+ p13.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
11931
+ p13.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
11800
11932
  return 1;
11801
11933
  }
11802
11934
  let selectedEntry;
11803
11935
  if (config.models.length === 1) {
11804
11936
  selectedEntry = config.models[0];
11805
11937
  } else {
11806
- const choice = await p12.select({
11938
+ const choice = await p13.select({
11807
11939
  message: "Select a starting Vertex AI model:",
11808
11940
  options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
11809
11941
  });
11810
- if (p12.isCancel(choice)) {
11811
- p12.cancel("Cancelled.");
11942
+ if (p13.isCancel(choice)) {
11943
+ p13.cancel("Cancelled.");
11812
11944
  return 0;
11813
11945
  }
11814
11946
  selectedEntry = choice;
@@ -11865,7 +11997,7 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11865
11997
  try {
11866
11998
  restoreOverlay();
11867
11999
  } catch (err) {
11868
- p12.log.error(String(err instanceof Error ? err.message : err));
12000
+ p13.log.error(String(err instanceof Error ? err.message : err));
11869
12001
  }
11870
12002
  };
11871
12003
  try {
@@ -11905,14 +12037,14 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11905
12037
  patchedConfigSha256: fileSha256(getCodexConfigPath()),
11906
12038
  ...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
11907
12039
  });
11908
- p12.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
12040
+ p13.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
11909
12041
  logProxy(proxyPort);
11910
12042
  logActiveModel(selectedEntry.display_name, selectedEntry.id);
11911
12043
  try {
11912
12044
  await launchOrRestartCodexApp();
11913
12045
  } catch (err) {
11914
- p12.log.warn(String(err instanceof Error ? err.message : err));
11915
- p12.log.info(codexAppInstallHint());
12046
+ p13.log.warn(String(err instanceof Error ? err.message : err));
12047
+ p13.log.info(codexAppInstallHint());
11916
12048
  throw err;
11917
12049
  }
11918
12050
  printCodexAppSessionPanel({
@@ -11929,21 +12061,22 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11929
12061
  isAppRunning: isCodexAppRunning,
11930
12062
  quitApp: quitCodexAppGracefully,
11931
12063
  waitForAppExit: () => waitForCodexAppQuit(),
12064
+ ...process.platform === "win32" ? { forceQuitApp: forceQuitCodexApp } : {},
11932
12065
  restoreOverlay,
11933
12066
  closeResources
11934
12067
  });
11935
- p12.log.success(result.message);
12068
+ p13.log.success(result.message);
11936
12069
  return 0;
11937
12070
  } catch (err) {
11938
12071
  shutdownFailed = true;
11939
- p12.log.error(String(err instanceof Error ? err.message : err));
12072
+ p13.log.error(String(err instanceof Error ? err.message : err));
11940
12073
  return 1;
11941
12074
  }
11942
12075
  } finally {
11943
12076
  if (sessionActive && !isCodexAppRunning()) restoreOverlaySafely();
11944
12077
  closeResources();
11945
12078
  if (sessionActive && !shutdownFailed) {
11946
- p12.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
12079
+ p13.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
11947
12080
  }
11948
12081
  }
11949
12082
  }
@@ -11974,7 +12107,7 @@ async function runCodexAppCommand(args, opts = {}) {
11974
12107
  const trace = args.includes("--trace");
11975
12108
  const debugLogPath = getCodexProxyDebugLogPath();
11976
12109
  if (trace && !configOnly) {
11977
- p12.log.info(`Debug log: ${debugLogPath}`);
12110
+ p13.log.info(`Debug log: ${debugLogPath}`);
11978
12111
  }
11979
12112
  const isTty = Boolean(process.stdin.isTTY);
11980
12113
  if (!configOnly) {
@@ -11992,13 +12125,13 @@ async function runCodexAppCommand(args, opts = {}) {
11992
12125
  if (!configOnly) {
11993
12126
  codexAppIntro();
11994
12127
  if (interrupted.recovered) {
11995
- p12.log.warn("Recovered from an interrupted codex-app session (restored Codex config).");
12128
+ p13.log.warn("Recovered from an interrupted codex-app session (restored Codex config).");
11996
12129
  }
11997
12130
  }
11998
12131
  if (opts.vertex) {
11999
12132
  return runCodexAppVertexLaunch(configOnly, trace);
12000
12133
  }
12001
- const catalogSpinner = p12.spinner();
12134
+ const catalogSpinner = p13.spinner();
12002
12135
  catalogSpinner.start("Loading your providers...");
12003
12136
  let catalog;
12004
12137
  try {
@@ -12012,8 +12145,8 @@ async function runCodexAppCommand(args, opts = {}) {
12012
12145
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "codex-app");
12013
12146
  if (compatible.length === 0) {
12014
12147
  if (!configOnly) {
12015
- p12.log.warn("No Codex-compatible providers in your registry.");
12016
- p12.log.info("Add a provider with relay-ai providers add.");
12148
+ p13.log.warn("No Codex-compatible providers in your registry.");
12149
+ p13.log.info("Add a provider with relay-ai providers add.");
12017
12150
  }
12018
12151
  return 0;
12019
12152
  }
@@ -12027,10 +12160,10 @@ async function runCodexAppCommand(args, opts = {}) {
12027
12160
  }
12028
12161
  const favoritesActive = favorites.length > 0 && !mixedMode;
12029
12162
  if (favoritesActive && !configOnly) {
12030
- p12.log.info(
12163
+ p13.log.info(
12031
12164
  `Favorites mode active \u2014 Codex App picker will show ${favorites.length + 1} models (1 starting + ${favorites.length} favorites).`
12032
12165
  );
12033
- p12.log.info("Edit with `relay-ai models`.");
12166
+ p13.log.info("Edit with `relay-ai models`.");
12034
12167
  }
12035
12168
  let activeProvider = providerForCodexPicker(
12036
12169
  compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0]
@@ -12044,7 +12177,7 @@ async function runCodexAppCommand(args, opts = {}) {
12044
12177
  providerForCodexPicker
12045
12178
  );
12046
12179
  if ("error" in bootSelection) {
12047
- p12.log.error(bootSelection.error);
12180
+ p13.log.error(bootSelection.error);
12048
12181
  return 1;
12049
12182
  }
12050
12183
  activeProvider = bootSelection.provider;
@@ -12082,7 +12215,7 @@ async function runCodexAppCommand(args, opts = {}) {
12082
12215
  const apiKey = await resolveLocalProviderApiKey(activeProvider);
12083
12216
  if (!apiKey) {
12084
12217
  if (!configOnly) {
12085
- p12.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id}.`);
12218
+ p13.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id}.`);
12086
12219
  }
12087
12220
  return 1;
12088
12221
  }
@@ -12116,7 +12249,7 @@ async function runCodexAppCommand(args, opts = {}) {
12116
12249
  subagentFavorites: prefs.codexSubagentModels ?? []
12117
12250
  });
12118
12251
  if (mixedModels.capacitySkipped.length > 0) {
12119
- p12.log.warn(
12252
+ p13.log.warn(
12120
12253
  `Skipped ${mixedModels.capacitySkipped.length} favorite(s) because the mixed catalog is full: ` + mixedModels.capacitySkipped.map((f) => `${f.providerId}:${f.modelId}`).join(", ")
12121
12254
  );
12122
12255
  }
@@ -12176,7 +12309,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12176
12309
  try {
12177
12310
  restoreOverlay();
12178
12311
  } catch (err) {
12179
- p12.log.error(String(err instanceof Error ? err.message : err));
12312
+ p13.log.error(String(err instanceof Error ? err.message : err));
12180
12313
  }
12181
12314
  };
12182
12315
  try {
@@ -12257,7 +12390,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12257
12390
  }
12258
12391
  });
12259
12392
  proxyPort = proxyHandle.port;
12260
- p12.log.info(`Route audit (metadata only): ${routeAuditPath}`);
12393
+ p13.log.info(`Route audit (metadata only): ${routeAuditPath}`);
12261
12394
  } else if (favoritesActive && resolvedFavorites.length > 0) {
12262
12395
  const needsBackend = (r) => {
12263
12396
  const m = r.model;
@@ -12344,8 +12477,8 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12344
12477
  try {
12345
12478
  await launchOrRestartCodexApp(void 0, opts.assumeYes);
12346
12479
  } catch (err) {
12347
- p12.log.warn(String(err instanceof Error ? err.message : err));
12348
- p12.log.info(codexAppInstallHint());
12480
+ p13.log.warn(String(err instanceof Error ? err.message : err));
12481
+ p13.log.info(codexAppInstallHint());
12349
12482
  throw err;
12350
12483
  }
12351
12484
  printCodexAppSessionPanel({
@@ -12363,28 +12496,29 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12363
12496
  isAppRunning: isCodexAppRunning,
12364
12497
  quitApp: quitCodexAppGracefully,
12365
12498
  waitForAppExit: () => waitForCodexAppQuit(),
12499
+ ...process.platform === "win32" ? { forceQuitApp: forceQuitCodexApp } : {},
12366
12500
  restoreOverlay,
12367
12501
  closeResources
12368
12502
  });
12369
- p12.log.success(result.message);
12503
+ p13.log.success(result.message);
12370
12504
  return 0;
12371
12505
  } catch (err) {
12372
12506
  shutdownFailed = true;
12373
- p12.log.error(String(err instanceof Error ? err.message : err));
12507
+ p13.log.error(String(err instanceof Error ? err.message : err));
12374
12508
  return 1;
12375
12509
  }
12376
12510
  } finally {
12377
12511
  if (sessionActive && !isCodexAppRunning()) restoreOverlaySafely();
12378
12512
  closeResources();
12379
12513
  if (sessionActive && !shutdownFailed) {
12380
- p12.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
12514
+ p13.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
12381
12515
  }
12382
12516
  }
12383
12517
  }
12384
12518
 
12385
12519
  // src/claude-app.ts
12386
12520
  import pc11 from "picocolors";
12387
- import * as p13 from "@clack/prompts";
12521
+ import * as p14 from "@clack/prompts";
12388
12522
 
12389
12523
  // src/claude-desktop/app-config.ts
12390
12524
  import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
@@ -12811,10 +12945,10 @@ async function runClaudeAppCommand(args, boot) {
12811
12945
  return 1;
12812
12946
  }
12813
12947
  if (hasStaleSession()) {
12814
- p13.log.warn("Recovered from an interrupted claude-app session.");
12948
+ p14.log.warn("Recovered from an interrupted claude-app session.");
12815
12949
  recoverSession();
12816
12950
  }
12817
- const catalogSpinner = p13.spinner();
12951
+ const catalogSpinner = p14.spinner();
12818
12952
  catalogSpinner.start("Loading your providers...");
12819
12953
  let catalog;
12820
12954
  try {
@@ -12827,7 +12961,7 @@ async function runClaudeAppCommand(args, boot) {
12827
12961
  catalogSpinner.stop("");
12828
12962
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "claude-app");
12829
12963
  if (compatible.length === 0) {
12830
- p13.log.warn("No compatible providers in your registry.");
12964
+ p14.log.warn("No compatible providers in your registry.");
12831
12965
  return 0;
12832
12966
  }
12833
12967
  const prefs = loadPreferences();
@@ -12844,7 +12978,7 @@ async function runClaudeAppCommand(args, boot) {
12844
12978
  providerForClaudePicker
12845
12979
  );
12846
12980
  if ("error" in bootSelection) {
12847
- p13.log.error(bootSelection.error);
12981
+ p14.log.error(bootSelection.error);
12848
12982
  return 1;
12849
12983
  }
12850
12984
  activeProvider = bootSelection.provider;
@@ -12856,7 +12990,7 @@ async function runClaudeAppCommand(args, boot) {
12856
12990
  useFavorites = true;
12857
12991
  const firstFavorite = resolveFirstAvailableFavorite(favorites, compatible);
12858
12992
  if (!firstFavorite) {
12859
- p13.log.warn("No saved Claude App favorites are currently available.");
12993
+ p14.log.warn("No saved Claude App favorites are currently available.");
12860
12994
  return 0;
12861
12995
  }
12862
12996
  activeProvider = firstFavorite.provider;
@@ -12869,7 +13003,7 @@ async function runClaudeAppCommand(args, boot) {
12869
13003
  }
12870
13004
  }
12871
13005
  if (!activeProvider || !selectedModel) {
12872
- p13.log.error("No Claude App launch model was selected.");
13006
+ p14.log.error("No Claude App launch model was selected.");
12873
13007
  return 1;
12874
13008
  }
12875
13009
  const catalogResolution = await resolveClaudeAppCatalog(
@@ -12879,16 +13013,16 @@ async function runClaudeAppCommand(args, boot) {
12879
13013
  favorites
12880
13014
  );
12881
13015
  if (!catalogResolution.ok) {
12882
- p13.log.error(catalogResolution.error);
13016
+ p14.log.error(catalogResolution.error);
12883
13017
  return 1;
12884
13018
  }
12885
13019
  if (catalogResolution.droppedFavorites.length > 0) {
12886
13020
  const skipped = catalogResolution.droppedFavorites.map((favorite) => `${favorite.providerId}/${favorite.modelId}`).join(", ");
12887
- p13.log.warn(`Skipped unavailable or unauthorized favorite(s): ${skipped}`);
13021
+ p14.log.warn(`Skipped unavailable or unauthorized favorite(s): ${skipped}`);
12888
13022
  }
12889
13023
  if (catalogResolution.capacitySkippedFavorites.length > 0) {
12890
13024
  const skipped = catalogResolution.capacitySkippedFavorites.map((favorite) => `${favorite.providerId}/${favorite.modelId}`).join(", ");
12891
- p13.log.warn(`Skipped favorite(s) beyond the 20-model catalog limit: ${skipped}`);
13025
+ p14.log.warn(`Skipped favorite(s) beyond the 20-model catalog limit: ${skipped}`);
12892
13026
  }
12893
13027
  let cloudCodeBackend = null;
12894
13028
  let proxyHandle = null;
@@ -12937,7 +13071,7 @@ ${pc11.green("\u2714")} Proxy started on port ${proxyHandle.port}`);
12937
13071
  try {
12938
13072
  await launchOrRestartClaudeApp();
12939
13073
  } catch (err) {
12940
- p13.log.warn(String(err instanceof Error ? err.message : err));
13074
+ p14.log.warn(String(err instanceof Error ? err.message : err));
12941
13075
  }
12942
13076
  console.log(`
12943
13077
  ${pc11.bold("Claude Desktop 3P Mode Active")}`);
@@ -12953,8 +13087,8 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
12953
13087
  sessionActive = false;
12954
13088
  if (cloudCodeBackend) cloudCodeBackend.handle.close();
12955
13089
  if (isClaudeAppRunning()) {
12956
- const shouldClose = await p13.confirm({ message: "Claude Desktop is still running. Close it?" });
12957
- if (shouldClose && !p13.isCancel(shouldClose)) {
13090
+ const shouldClose = await p14.confirm({ message: "Claude Desktop is still running. Close it?" });
13091
+ if (shouldClose && !p14.isCancel(shouldClose)) {
12958
13092
  quitClaudeAppGracefully();
12959
13093
  }
12960
13094
  }
@@ -12965,7 +13099,7 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
12965
13099
  cleanupSession(uuid);
12966
13100
  }
12967
13101
  if (cloudCodeBackend) cloudCodeBackend.handle.close();
12968
- p13.log.error(String(err instanceof Error ? err.message : err));
13102
+ p14.log.error(String(err instanceof Error ? err.message : err));
12969
13103
  return 1;
12970
13104
  }
12971
13105
  }
@@ -13013,7 +13147,7 @@ function skillInstallTargets() {
13013
13147
  });
13014
13148
  }
13015
13149
  function formatProviderModels(provider) {
13016
- const models = provider.modelsCache?.models ?? [];
13150
+ const models = getProviderModels(provider);
13017
13151
  if (models.length === 0) return ` (no cached models \u2014 run: relay-ai providers refresh-models ${provider.id})`;
13018
13152
  const lines = models.slice(0, 40).map((m) => ` ${m.id}${m.name !== m.id ? ` (${m.name})` : ""}`);
13019
13153
  if (models.length > 40) lines.push(` ... and ${models.length - 40} more`);
@@ -13022,7 +13156,7 @@ function formatProviderModels(provider) {
13022
13156
  function buildLiveStateSection() {
13023
13157
  const prefs = loadPreferences();
13024
13158
  const registry = loadRegistry();
13025
- const enabled = registry.providers.filter((p15) => p15.enabled);
13159
+ const enabled = registry.providers.filter((p16) => p16.enabled);
13026
13160
  const prefLines = [];
13027
13161
  if (prefs.lastProvider || prefs.lastModel) {
13028
13162
  prefLines.push(` Claude last launch: provider=${prefs.lastProvider ?? "(none)"} model=${prefs.lastModel ?? "(none)"}`);
@@ -13039,9 +13173,9 @@ function buildLiveStateSection() {
13039
13173
  prefLines.push(` ${f.providerId} / ${f.modelId}`);
13040
13174
  }
13041
13175
  }
13042
- const providerBlocks = enabled.length === 0 ? [" No registry providers configured. Built-in cloud: zen, go (OpenCode Zen/Go)."] : enabled.map((p15) => [
13043
- ` ${p15.name} (${p15.id}) \u2014 ${p15.modelsCache?.models.length ?? 0} cached model(s)`,
13044
- formatProviderModels(p15)
13176
+ const providerBlocks = enabled.length === 0 ? [" No registry providers configured. Built-in cloud: zen, go (OpenCode Zen/Go)."] : enabled.map((p16) => [
13177
+ ` ${p16.name} (${p16.id}) \u2014 ${getProviderModels(p16).length} model(s)`,
13178
+ formatProviderModels(p16)
13045
13179
  ].join("\n"));
13046
13180
  return `
13047
13181
  ================================================================================
@@ -15115,20 +15249,20 @@ ${pc12.bold("Examples:")}
15115
15249
  relay-ai antigravity
15116
15250
  relay-ai antigravity --provider zen --model deepseek-v4-flash-free`;
15117
15251
  }
15118
- function printHelp(text5) {
15252
+ function printHelp(text6) {
15119
15253
  console.log(`
15120
- ${text5}
15254
+ ${text6}
15121
15255
  `);
15122
15256
  }
15123
15257
  async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindow, trace, claudeArgs) {
15124
15258
  let proxyHandle;
15125
15259
  try {
15126
15260
  proxyHandle = await startProxyCatalog(catalogRoutes, startingRoute.aliasId, trace);
15127
- p14.log.info(
15261
+ p15.log.info(
15128
15262
  `Switch menu active \u2014 proxy on port ${proxyHandle.port} ` + pc12.dim(`(${catalogRoutes.length} model${catalogRoutes.length !== 1 ? "s" : ""} in /model)`)
15129
15263
  );
15130
15264
  } catch (err) {
15131
- p14.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
15265
+ p15.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
15132
15266
  return 1;
15133
15267
  }
15134
15268
  const childEnv = buildChildEnv(
@@ -15141,7 +15275,7 @@ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindo
15141
15275
  );
15142
15276
  const debugLogPath = prepareClaudeTraceLog();
15143
15277
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
15144
- if (trace) p14.log.info(`Debug log: ${debugLogPath}`);
15278
+ if (trace) p15.log.info(`Debug log: ${debugLogPath}`);
15145
15279
  const exitCode = await launchClaude(
15146
15280
  childEnv,
15147
15281
  claudeCodeClientModelId(startingRoute.aliasId, contextWindow),
@@ -15161,10 +15295,10 @@ async function runModelsCommand(opts = {}) {
15161
15295
  const listItemLabel = subagentScope ? "Codex SubAgent model" : scope === "agy" ? "Antigravity favorite" : "favorite";
15162
15296
  const configKey = scope === "agy" ? "antigravityCliFavoriteModels" : scope === "codex-subagents" ? "codexSubagentModels" : "favoriteModels";
15163
15297
  relayIntro(scopeName);
15164
- const spinner9 = p14.spinner();
15165
- spinner9.start("Loading providers...");
15298
+ const spinner10 = p15.spinner();
15299
+ spinner10.start("Loading providers...");
15166
15300
  const catalog = await fetchProviderCatalog();
15167
- spinner9.stop("");
15301
+ spinner10.stop("");
15168
15302
  const pickedProviders = providersForPicker(catalog);
15169
15303
  const allProviders = scope === "agy" ? providersForTarget(pickedProviders, "antigravity") : scope === "codex-subagents" ? providersForCodexSubagents(pickedProviders) : pickedProviders;
15170
15304
  const favoriteProviders = allProviders.map((provider) => ({
@@ -15172,8 +15306,8 @@ async function runModelsCommand(opts = {}) {
15172
15306
  name: favoriteProviderDisplayName(provider)
15173
15307
  }));
15174
15308
  if (favoriteProviders.length === 0) {
15175
- p14.log.warn("No providers found.");
15176
- p14.log.info(`${pc12.dim("OpenCode Zen/Go is always available. Add providers with ")}${pc12.cyan("relay-ai providers")}${pc12.dim(".")}`);
15309
+ p15.log.warn("No providers found.");
15310
+ p15.log.info(`${pc12.dim("OpenCode Zen/Go is always available. Add providers with ")}${pc12.cyan("relay-ai providers")}${pc12.dim(".")}`);
15177
15311
  relayOutro("Done");
15178
15312
  return 0;
15179
15313
  }
@@ -15202,19 +15336,19 @@ async function runModelsCommand(opts = {}) {
15202
15336
  });
15203
15337
  options.push({ value: "__done__", label: "Done", hint: "" });
15204
15338
  const header = favorites.length === 0 ? `${scopeName} (0/${maxFavorites})` : `${scopeName} (${favorites.length}/${maxFavorites}) \u2014 select to remove`;
15205
- const choice = await p14.select({
15339
+ const choice = await p15.select({
15206
15340
  message: header,
15207
15341
  options,
15208
15342
  initialValue: "__done__"
15209
15343
  });
15210
- if (p14.isCancel(choice) || choice === "__done__") break;
15344
+ if (p15.isCancel(choice) || choice === "__done__") break;
15211
15345
  if (choice === "__add__") {
15212
15346
  if (atCap) {
15213
- p14.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 remove one first.`);
15347
+ p15.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 remove one first.`);
15214
15348
  continue;
15215
15349
  }
15216
15350
  const globalCount = buildGlobalFavoriteIndex(favoriteProviders).length;
15217
- const addPath = await p14.select({
15351
+ const addPath = await p15.select({
15218
15352
  message: subagentScope ? "Add a Codex SubAgent model" : "Add a favorite",
15219
15353
  options: [
15220
15354
  {
@@ -15234,7 +15368,7 @@ async function runModelsCommand(opts = {}) {
15234
15368
  }
15235
15369
  ]
15236
15370
  });
15237
- if (p14.isCancel(addPath)) continue;
15371
+ if (p15.isCancel(addPath)) continue;
15238
15372
  let provider;
15239
15373
  let browsedMultiple = [];
15240
15374
  if (addPath === "global") {
@@ -15257,26 +15391,26 @@ async function runModelsCommand(opts = {}) {
15257
15391
  let currentInitialProvider = void 0;
15258
15392
  while (true) {
15259
15393
  const providerOptions = favoriteProviders.map((ap) => providerSelectOption(ap));
15260
- const pickedProviderId = await p14.select({
15394
+ const pickedProviderId = await p15.select({
15261
15395
  message: "Which provider?",
15262
15396
  options: providerOptions,
15263
15397
  initialValue: currentInitialProvider
15264
15398
  });
15265
- if (p14.isCancel(pickedProviderId)) break;
15399
+ if (p15.isCancel(pickedProviderId)) break;
15266
15400
  provider = favoriteProviders.find((ap) => ap.id === pickedProviderId);
15267
15401
  let modelsToPick = provider.models;
15268
15402
  if (provider.models.length > MODEL_SEARCH_THRESHOLD) {
15269
- const searchInput = await p14.text({
15403
+ const searchInput = await p15.text({
15270
15404
  message: `Search ${provider.name} models (${provider.models.length} available):`,
15271
15405
  placeholder: "e.g. flash 3.6, claude, llama"
15272
15406
  });
15273
- if (p14.isCancel(searchInput)) {
15407
+ if (p15.isCancel(searchInput)) {
15274
15408
  currentInitialProvider = provider.id;
15275
15409
  continue;
15276
15410
  }
15277
15411
  modelsToPick = filterModelsBySearch(provider.models, String(searchInput));
15278
15412
  if (modelsToPick.length === 0) {
15279
- p14.log.warn("No models match \u2014 try a different search");
15413
+ p15.log.warn("No models match \u2014 try a different search");
15280
15414
  currentInitialProvider = provider.id;
15281
15415
  continue;
15282
15416
  }
@@ -15290,12 +15424,12 @@ async function runModelsCommand(opts = {}) {
15290
15424
  hint: favorited ? pc12.yellow(`\u2605 already in ${listLabel}`) : ""
15291
15425
  };
15292
15426
  });
15293
- const pickedModelIds = await p14.multiselect({
15427
+ const pickedModelIds = await p15.multiselect({
15294
15428
  message: `Select models to add from ${provider.name} ${pc12.dim("(Space to select, Enter to confirm)")}`,
15295
15429
  options: options2,
15296
15430
  required: false
15297
15431
  });
15298
- if (p14.isCancel(pickedModelIds)) {
15432
+ if (p15.isCancel(pickedModelIds)) {
15299
15433
  currentInitialProvider = provider.id;
15300
15434
  continue;
15301
15435
  }
@@ -15330,27 +15464,27 @@ async function runModelsCommand(opts = {}) {
15330
15464
  if (addedModels.length > 0) {
15331
15465
  if (addedModels.length === 1) {
15332
15466
  const modelName = addedModels[0].name || addedModels[0].id;
15333
- p14.log.success(`Added ${modelName} (${provider.name}) to ${listLabel}.`);
15467
+ p15.log.success(`Added ${modelName} (${provider.name}) to ${listLabel}.`);
15334
15468
  } else {
15335
- p14.log.success(`Added ${addedModels.length} models from ${provider.name} to ${listLabel}.`);
15469
+ p15.log.success(`Added ${addedModels.length} models from ${provider.name} to ${listLabel}.`);
15336
15470
  }
15337
15471
  }
15338
15472
  if (duplicateCount > 0) {
15339
- p14.log.warn(`${duplicateCount} selected model(s) were already in ${listLabel}.`);
15473
+ p15.log.warn(`${duplicateCount} selected model(s) were already in ${listLabel}.`);
15340
15474
  }
15341
15475
  if (limitReached) {
15342
- p14.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 some selected models could not be added.`);
15476
+ p15.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 some selected models could not be added.`);
15343
15477
  }
15344
15478
  } else if (choice.startsWith("fav-")) {
15345
15479
  const idx = parseInt(choice.slice(4), 10);
15346
15480
  const fav = favorites[idx];
15347
15481
  const entry = modelLookup.get(`${fav.providerId}:${fav.modelId}`);
15348
15482
  const label = entry ? `${entry.modelName} (${entry.providerName})` : fav.modelId;
15349
- const confirmed = await p14.confirm({ message: `Remove ${label} from ${listLabel}?` });
15350
- if (p14.isCancel(confirmed) || !confirmed) continue;
15483
+ const confirmed = await p15.confirm({ message: `Remove ${label} from ${listLabel}?` });
15484
+ if (p15.isCancel(confirmed) || !confirmed) continue;
15351
15485
  favorites = removeFavorite(favorites, fav);
15352
15486
  favoritesDirty = true;
15353
- p14.log.success(`Removed ${label} from ${listLabel}.`);
15487
+ p15.log.success(`Removed ${label} from ${listLabel}.`);
15354
15488
  }
15355
15489
  }
15356
15490
  if (favoritesDirty) {
@@ -15402,7 +15536,7 @@ Error: ${launchPlan.error}
15402
15536
  }
15403
15537
  if (!agentStdout) relayIntro("Claude Code");
15404
15538
  if (setup && !dryRun && !agentStdout) {
15405
- p14.log.info("Provider setup now lives in relay-ai providers \u2014 opening that next is recommended.");
15539
+ p15.log.info("Provider setup now lives in relay-ai providers \u2014 opening that next is recommended.");
15406
15540
  }
15407
15541
  if (!httpProxyOnly && !dryRun && await needsFirstRunSetup()) {
15408
15542
  const firstRun = await runFirstRunWizard(trace);
@@ -15417,7 +15551,7 @@ Error: ${launchPlan.error}
15417
15551
  return 1;
15418
15552
  }
15419
15553
  } else {
15420
- const catalogSpinner = p14.spinner();
15554
+ const catalogSpinner = p15.spinner();
15421
15555
  catalogSpinner.start("Loading your providers...");
15422
15556
  try {
15423
15557
  catalog = await fetchProviderCatalog();
@@ -15430,8 +15564,8 @@ Error: ${launchPlan.error}
15430
15564
  }
15431
15565
  const allProviders = providersForTarget(providersForPicker(catalog), "claude");
15432
15566
  if (allProviders.length === 0 && !httpProxyOnly) {
15433
- p14.log.warn("No providers available.");
15434
- p14.log.info(pc12.dim("Run relay-ai providers add or import to get started."));
15567
+ p15.log.warn("No providers available.");
15568
+ p15.log.info(pc12.dim("Run relay-ai providers add or import to get started."));
15435
15569
  return 0;
15436
15570
  }
15437
15571
  const runTransparentProxy = async (selected) => {
@@ -15449,7 +15583,7 @@ Error: ${launchPlan.error}
15449
15583
  }
15450
15584
  const debugLogPath2 = prepareClaudeTraceLog();
15451
15585
  const traceArgs2 = trace ? ["--debug-file", debugLogPath2] : [];
15452
- if (trace && !agentStdout) p14.log.info(`Debug log: ${debugLogPath2}`);
15586
+ if (trace && !agentStdout) p15.log.info(`Debug log: ${debugLogPath2}`);
15453
15587
  try {
15454
15588
  const result = await launchClaudeWithHttpProxy({
15455
15589
  providers: catalog,
@@ -15461,26 +15595,26 @@ Error: ${launchPlan.error}
15461
15595
  onProxyReady: (proxy) => {
15462
15596
  if (agentStdout) return;
15463
15597
  const count = proxy.handle.modelIds.length;
15464
- p14.log.info(
15598
+ p15.log.info(
15465
15599
  count === 0 ? "Secure Anthropic passthrough ready; no compatible Relay models were added." : `Secure Anthropic passthrough ready with ${count} Relay model${count === 1 ? "" : "s"}.`
15466
15600
  );
15467
- for (const modelId of proxy.handle.modelIds) p14.log.message(pc12.dim(` /model ${modelId}`));
15601
+ for (const modelId of proxy.handle.modelIds) p15.log.message(pc12.dim(` /model ${modelId}`));
15468
15602
  }
15469
15603
  });
15470
15604
  if (!agentStdout && result.proxy.loaded.unavailable.length > 0) {
15471
- p14.log.warn(
15605
+ p15.log.warn(
15472
15606
  `${result.proxy.loaded.unavailable.length} favorite${result.proxy.loaded.unavailable.length === 1 ? "" : "s"} unavailable or missing credentials.`
15473
15607
  );
15474
15608
  }
15475
15609
  if (!agentStdout && result.proxy.loaded.unsupported.length > 0) {
15476
- p14.log.warn(
15610
+ p15.log.warn(
15477
15611
  `${result.proxy.loaded.unsupported.length} incompatible favorite${result.proxy.loaded.unsupported.length === 1 ? "" : "s"} skipped.`
15478
15612
  );
15479
15613
  }
15480
15614
  if (trace) printTraceLog(debugLogPath2);
15481
15615
  return result.exitCode;
15482
15616
  } catch (error) {
15483
- p14.log.error(
15617
+ p15.log.error(
15484
15618
  `Could not start secure Anthropic + Relay mode: ${error instanceof Error ? error.message : String(error)}`
15485
15619
  );
15486
15620
  return 1;
@@ -15501,7 +15635,7 @@ Error: ${launchPlan.error}
15501
15635
  if (launchPlan.skip && launchPlan.target) {
15502
15636
  const resolved = findProviderAndModel(allProviders, launchPlan.target);
15503
15637
  if (!resolved) {
15504
- p14.log.error(
15638
+ p15.log.error(
15505
15639
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
15506
15640
  );
15507
15641
  return 1;
@@ -15509,19 +15643,19 @@ Error: ${launchPlan.error}
15509
15643
  activeProvider = resolved.provider;
15510
15644
  selectedModel = resolved.model;
15511
15645
  if (!agentStdout) {
15512
- p14.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
15646
+ p15.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
15513
15647
  }
15514
15648
  if (!dryRun) recordLaunchSelection("claude", activeProvider.id, selectedModel.id, prefs);
15515
15649
  } else {
15516
15650
  let currentInitialProvider = initialProvider;
15517
15651
  while (true) {
15518
- const chosen = await p14.select({
15652
+ const chosen = await p15.select({
15519
15653
  message: "Which provider?",
15520
15654
  options: providerOptions,
15521
15655
  initialValue: currentInitialProvider
15522
15656
  });
15523
- if (p14.isCancel(chosen)) {
15524
- p14.cancel("Cancelled.");
15657
+ if (p15.isCancel(chosen)) {
15658
+ p15.cancel("Cancelled.");
15525
15659
  return 0;
15526
15660
  }
15527
15661
  const providerChoice = chosen;
@@ -15533,7 +15667,7 @@ Error: ${launchPlan.error}
15533
15667
  if (prov && mod) available.push({ provider: prov, model: mod });
15534
15668
  }
15535
15669
  if (available.length === 0) {
15536
- p14.log.warn("No saved favorites are currently available.");
15670
+ p15.log.warn("No saved favorites are currently available.");
15537
15671
  return 0;
15538
15672
  }
15539
15673
  const favOptions = available.map((f, i) => ({
@@ -15541,13 +15675,13 @@ Error: ${launchPlan.error}
15541
15675
  label: `${f.model.name || f.model.id} \u2014 ${f.provider.name}`,
15542
15676
  hint: f.model.id
15543
15677
  }));
15544
- const pickedIdx = await p14.select({
15678
+ const pickedIdx = await p15.select({
15545
15679
  message: "Starting model?",
15546
15680
  options: favOptions,
15547
15681
  initialValue: "0"
15548
15682
  });
15549
- if (p14.isCancel(pickedIdx)) {
15550
- p14.cancel("Cancelled.");
15683
+ if (p15.isCancel(pickedIdx)) {
15684
+ p15.cancel("Cancelled.");
15551
15685
  return 0;
15552
15686
  }
15553
15687
  const sel = available[Number(pickedIdx)];
@@ -15571,13 +15705,13 @@ Error: ${launchPlan.error}
15571
15705
  }
15572
15706
  let useHttpProxy = Boolean(httpProxy);
15573
15707
  if (!httpProxy && !launchPlan.skip && supportsClaudeTransparentMode(selectedModel)) {
15574
- const transparentChoice = await p14.select({
15708
+ const transparentChoice = await p15.select({
15575
15709
  message: "Keep your normal Claude models available too?",
15576
15710
  options: claudeTransparentModeOptions(selectedModel.name || selectedModel.id),
15577
15711
  initialValue: prefs.lastClaudeTransparentMode ?? true
15578
15712
  });
15579
- if (p14.isCancel(transparentChoice)) {
15580
- p14.cancel("Cancelled.");
15713
+ if (p15.isCancel(transparentChoice)) {
15714
+ p15.cancel("Cancelled.");
15581
15715
  return 0;
15582
15716
  }
15583
15717
  useHttpProxy = transparentChoice;
@@ -15596,12 +15730,12 @@ Error: ${launchPlan.error}
15596
15730
  );
15597
15731
  const startingRoute = resolveRoute(activeProvider.id, selectedModel.id) ?? null;
15598
15732
  if (!startingRoute) {
15599
- p14.log.error("Could not resolve a proxy route for the selected model.");
15733
+ p15.log.error("Could not resolve a proxy route for the selected model.");
15600
15734
  return 1;
15601
15735
  }
15602
15736
  const { routes: catalogRoutes, droppedFavorites } = buildCatalogRoutes(startingRoute, favorites, resolveRoute);
15603
15737
  if (droppedFavorites.length > 0) {
15604
- p14.log.warn(
15738
+ p15.log.warn(
15605
15739
  `Skipping ${droppedFavorites.length} favorite${droppedFavorites.length === 1 ? "" : "s"} that are no longer available in /model`
15606
15740
  );
15607
15741
  }
@@ -15646,7 +15780,7 @@ Error: ${launchPlan.error}
15646
15780
  }
15647
15781
  const launchApiKey = await resolveLocalProviderApiKey(activeProvider);
15648
15782
  if (!launchApiKey?.trim()) {
15649
- p14.log.error(
15783
+ p15.log.error(
15650
15784
  `No credential found for ${activeProvider.name}. Add a key with relay-ai providers or set OPENCODE_API_KEY.`
15651
15785
  );
15652
15786
  return 1;
@@ -15670,9 +15804,9 @@ Error: ${launchPlan.error}
15670
15804
  },
15671
15805
  launchApiKey
15672
15806
  );
15673
- if (!isAgentStdoutMode()) p14.log.info(`Cloud Code proxy started on port ${proxyHandle.port}`);
15807
+ if (!isAgentStdoutMode()) p15.log.info(`Cloud Code proxy started on port ${proxyHandle.port}`);
15674
15808
  } catch (err) {
15675
- p14.log.error(`Failed to start Cloud Code proxy: ${err instanceof Error ? err.message : String(err)}`);
15809
+ p15.log.error(`Failed to start Cloud Code proxy: ${err instanceof Error ? err.message : String(err)}`);
15676
15810
  return 1;
15677
15811
  }
15678
15812
  childEnv = buildChildEnv(
@@ -15698,9 +15832,9 @@ Error: ${launchPlan.error}
15698
15832
  },
15699
15833
  launchApiKey
15700
15834
  );
15701
- if (!isAgentStdoutMode()) p14.log.info(`OAuth proxy started on port ${proxyHandle.port}`);
15835
+ if (!isAgentStdoutMode()) p15.log.info(`OAuth proxy started on port ${proxyHandle.port}`);
15702
15836
  } catch (err) {
15703
- p14.log.error(`Failed to start OAuth proxy: ${err instanceof Error ? err.message : String(err)}`);
15837
+ p15.log.error(`Failed to start OAuth proxy: ${err instanceof Error ? err.message : String(err)}`);
15704
15838
  return 1;
15705
15839
  }
15706
15840
  childEnv = buildChildEnv(
@@ -15743,12 +15877,12 @@ Error: ${launchPlan.error}
15743
15877
  launchApiKey
15744
15878
  );
15745
15879
  if (!isAgentStdoutMode()) {
15746
- p14.log.info(
15880
+ p15.log.info(
15747
15881
  `SDK adapter proxy started on port ${proxyHandle.port}` + (selectedModel.npm ? pc12.dim(` (${selectedModel.npm})`) : "")
15748
15882
  );
15749
15883
  }
15750
15884
  } catch (err) {
15751
- p14.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
15885
+ p15.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
15752
15886
  return 1;
15753
15887
  }
15754
15888
  childEnv = buildChildEnv(
@@ -15764,7 +15898,7 @@ Error: ${launchPlan.error}
15764
15898
  }
15765
15899
  const debugLogPath = prepareClaudeTraceLog();
15766
15900
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
15767
- if (trace) p14.log.info(`Debug log: ${debugLogPath}`);
15901
+ if (trace) p15.log.info(`Debug log: ${debugLogPath}`);
15768
15902
  const exitCode = await launchClaude(
15769
15903
  childEnv,
15770
15904
  claudeCodeClientModelId(selectedModel.id, selectedModel.contextWindow),
@@ -15791,7 +15925,7 @@ Error: ${parsed.error}
15791
15925
  printHelp(rootHelpText());
15792
15926
  return 1;
15793
15927
  }
15794
- if (!parsed.showVersion && !parsed.showAi) {
15928
+ if (shouldRefreshModelsDev(parsed)) {
15795
15929
  refreshModelsDevCacheAsync();
15796
15930
  }
15797
15931
  if (parsed.command === "root") {
@@ -15846,7 +15980,7 @@ Options:
15846
15980
  --trace Write debug logs under ~/.relay-ai/logs/`);
15847
15981
  return 0;
15848
15982
  }
15849
- const { runUiCommand } = await import("./ui-command-RJVKDGGG.js");
15983
+ const { runUiCommand } = await import("./ui-command-J7PTTCM5.js");
15850
15984
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15851
15985
  }
15852
15986
  if (parsed.command === "models") {
@@ -15984,6 +16118,9 @@ Options:
15984
16118
  }
15985
16119
  return runClaudeCommand(parsed);
15986
16120
  }
16121
+ function shouldRefreshModelsDev(parsed) {
16122
+ return !parsed.showVersion && !parsed.showAi && !parsed.showHelp && !parsed.claudeArgs.includes("--restore");
16123
+ }
15987
16124
  function isCliEntryPoint() {
15988
16125
  if (!process.argv[1]) return false;
15989
16126
  try {
@@ -16019,6 +16156,7 @@ export {
16019
16156
  rootHelpText,
16020
16157
  runClaudeCommand,
16021
16158
  runModelsCommand,
16022
- serverHelpText
16159
+ serverHelpText,
16160
+ shouldRefreshModelsDev
16023
16161
  };
16024
16162
  //# sourceMappingURL=cli.js.map