@jacobbd/relay-ai 0.11.0 → 0.11.1

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-VXQ7ZVTW.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-YD6A3ZB3.js";
206
152
  import {
207
153
  filterTemplates,
208
154
  getTemplateById,
@@ -210,11 +156,77 @@ import {
210
156
  listAddableTemplates,
211
157
  listSupportedTemplates,
212
158
  listVisibleOAuthTemplates
213
- } from "./chunk-SFN33VAE.js";
159
+ } from "./chunk-TRM2WGI6.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
+ oauthCredentialToKeychainJson,
200
+ parseDsmlToolCalls,
201
+ parseToolArguments,
202
+ preferredRelayCredentialAuthRef,
203
+ readFromCredentialStore,
204
+ readGlobalOpencodeCredential,
205
+ readOpencodeAuthFile,
206
+ readStoredProviderCredential,
207
+ resolveApiKey,
208
+ resolveContextWindow,
209
+ resolveProviderCredential,
210
+ routeLookupIds,
211
+ saveProviderCredential,
212
+ saveRegistry,
213
+ saveToCredentialStore,
214
+ selectBetaFlags,
215
+ serializeToolResultContent,
216
+ silenceSdkWarnings,
217
+ splitToolUseId,
218
+ sseChunk,
219
+ supportsManualModels,
220
+ supportsNativeOAuth,
221
+ thinkingProviderOptions,
222
+ upstreamHttpStatus,
223
+ validateCustomEndpointUrl
224
+ } from "./chunk-O2XZFXHG.js";
225
+ import "./chunk-JIDIH7DS.js";
214
226
 
215
227
  // src/cli.ts
216
228
  import pc12 from "picocolors";
217
- import * as p14 from "@clack/prompts";
229
+ import * as p15 from "@clack/prompts";
218
230
  import { realpathSync } from "fs";
219
231
  import { fileURLToPath } from "url";
220
232
 
@@ -507,7 +519,7 @@ async function importFromOpencode(options = {}) {
507
519
  }
508
520
  continue;
509
521
  }
510
- const existingIdx = registry.providers.findIndex((p15) => p15.id === entry.id);
522
+ const existingIdx = registry.providers.findIndex((p16) => p16.id === entry.id);
511
523
  const existing = existingIdx >= 0 ? registry.providers[existingIdx] : void 0;
512
524
  if (existing && options.resolveConflict) {
513
525
  const choice = await options.resolveConflict({
@@ -535,7 +547,7 @@ async function importFromOpencode(options = {}) {
535
547
  continue;
536
548
  }
537
549
  if (existingIdx >= 0) {
538
- registry.providers[existingIdx] = { ...entry, addedAt: registry.providers[existingIdx].addedAt };
550
+ registry.providers[existingIdx] = { ...entry, addedAt: registry.providers[existingIdx].addedAt, manualModels: registry.providers[existingIdx].manualModels };
539
551
  } else {
540
552
  registry.providers.push(entry);
541
553
  }
@@ -545,7 +557,7 @@ async function importFromOpencode(options = {}) {
545
557
  if (isOAuth) oauthImported += 1;
546
558
  }
547
559
  const alreadyReportedIds = new Set(skipped.map((s) => s.id));
548
- const registryProviderIds = new Set(registry.providers.map((p15) => p15.id));
560
+ const registryProviderIds = new Set(registry.providers.map((p16) => p16.id));
549
561
  for (const provider of listCredentialSkippedProviders(
550
562
  raw,
551
563
  authEntries,
@@ -800,10 +812,10 @@ async function runFirstRunWizard(trace = false) {
800
812
  p2.log.error("OpenCode CLI not found. Install from https://opencode.ai \u2014 or use Quick start / providers add instead.");
801
813
  return runFirstRunWizard(trace);
802
814
  }
803
- const spinner9 = p2.spinner();
804
- spinner9.start("Importing from OpenCode CLI...");
815
+ const spinner10 = p2.spinner();
816
+ spinner10.start("Importing from OpenCode CLI...");
805
817
  const result = await importFromOpencode();
806
- spinner9.stop("");
818
+ spinner10.stop("");
807
819
  if (result.error) {
808
820
  p2.log.error(result.error);
809
821
  return runFirstRunWizard(trace);
@@ -1236,8 +1248,62 @@ async function pickGlobalFavoriteModel(providers, favorites, opts) {
1236
1248
 
1237
1249
  // src/providers-command.ts
1238
1250
  import pc4 from "picocolors";
1239
- import * as p5 from "@clack/prompts";
1251
+ import * as p6 from "@clack/prompts";
1240
1252
  init_provider_templates();
1253
+
1254
+ // src/manual-model-wizard.ts
1255
+ import * as p5 from "@clack/prompts";
1256
+ async function runManualModelAddFlow(provider) {
1257
+ const modelId = await p5.text({ message: "Exact model ID from your provider", placeholder: "e.g. deepseek-v4.1-flash-expires-on-0910", validate: modelIdError });
1258
+ if (p5.isCancel(modelId)) return 0;
1259
+ const displayName = await p5.text({
1260
+ message: "Display name (optional)",
1261
+ defaultValue: modelId,
1262
+ validate: (value) => value && (value.length > 200 || /[\u0000-\u001f\u007f]/u.test(value)) ? "Use at most 200 characters without control characters." : void 0
1263
+ });
1264
+ if (p5.isCancel(displayName)) return 0;
1265
+ const context = await p5.text({
1266
+ message: "Context size in tokens (optional \u2014 leave blank if unknown)",
1267
+ validate: (value) => contextWindowError(value?.trim() ? Number(value) : void 0)
1268
+ });
1269
+ if (p5.isCancel(context)) return 0;
1270
+ const contextText = typeof context === "string" ? context.trim() : "";
1271
+ const confirmed = await p5.confirm({ message: "Test & add? Sends three small requests to this provider; API charges may apply.", initialValue: true });
1272
+ if (p5.isCancel(confirmed) || !confirmed) return 0;
1273
+ const spinner10 = p5.spinner();
1274
+ spinner10.start("Testing generation, streaming and tool round-trip (up to 90 seconds)\u2026");
1275
+ const result = await addManualModel({
1276
+ providerId: provider.id,
1277
+ modelId: String(modelId),
1278
+ displayName: String(displayName),
1279
+ ...contextText ? { contextWindow: Number(contextText) } : {}
1280
+ });
1281
+ spinner10.stop(result.ok ? "Validation passed" : "Validation failed");
1282
+ if (!result.ok) {
1283
+ p5.log.error(result.error ?? "Model was not added.");
1284
+ return 1;
1285
+ }
1286
+ p5.log.success(`${result.model.name} added. Manual models are preserved when you refresh the catalog.`);
1287
+ p5.log.info("Generation, streaming and tools passed. Pricing and vision support are unknown; context size is user-supplied if set.");
1288
+ return 0;
1289
+ }
1290
+ async function runManualModelRemoveFlow(provider) {
1291
+ const models = provider.manualModels ?? [];
1292
+ if (!models.length) return 0;
1293
+ const id = await p5.select({ message: "Remove which manual model?", options: models.map((m) => ({ value: m.id, label: m.name, hint: m.id })) });
1294
+ if (p5.isCancel(id)) return 0;
1295
+ const confirmed = await p5.confirm({ message: `Remove the manual entry for ${id}?`, initialValue: false });
1296
+ if (p5.isCancel(confirmed) || !confirmed) return 0;
1297
+ const result = removeManualModel(provider.id, String(id));
1298
+ if (!result.ok) {
1299
+ p5.log.error(result.error ?? "Could not remove model.");
1300
+ return 1;
1301
+ }
1302
+ p5.log.success("Manual entry removed. If the provider now lists this ID, its discovered entry remains available.");
1303
+ return 0;
1304
+ }
1305
+
1306
+ // src/providers-command.ts
1241
1307
  function parseProvidersArgs(args) {
1242
1308
  if (args.length === 0) return { subcommand: "hub", showHelp: false };
1243
1309
  const [first, ...rest] = args;
@@ -1313,7 +1379,7 @@ async function runProvidersImport() {
1313
1379
  const hasExisting = registry.providers.length > 0;
1314
1380
  const resolveConflict = hasExisting ? async (ctx) => {
1315
1381
  printImportConflictPanel(ctx.existing.name, ctx.existingKeyHint, ctx.incomingKeyHint);
1316
- const choice = await p5.select({
1382
+ const choice = await p6.select({
1317
1383
  message: "Which configuration should we keep?",
1318
1384
  options: [
1319
1385
  { value: "keep", label: pc4.cyan("Keep mine"), hint: "Leave your current relay-ai config unchanged" },
@@ -1321,46 +1387,46 @@ async function runProvidersImport() {
1321
1387
  { value: "skip", label: pc4.dim("Skip this provider"), hint: "" }
1322
1388
  ]
1323
1389
  });
1324
- if (p5.isCancel(choice)) return "skip";
1390
+ if (p6.isCancel(choice)) return "skip";
1325
1391
  return choice;
1326
1392
  } : void 0;
1327
- const spinner9 = p5.spinner();
1328
- spinner9.start("Importing from OpenCode...");
1393
+ const spinner10 = p6.spinner();
1394
+ spinner10.start("Importing from OpenCode...");
1329
1395
  const result = await importFromOpencode({ resolveConflict });
1330
- spinner9.stop("");
1396
+ spinner10.stop("");
1331
1397
  if (result.error) {
1332
- p5.log.error(result.error);
1398
+ p6.log.error(result.error);
1333
1399
  return 1;
1334
1400
  }
1335
1401
  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.");
1402
+ p6.log.warn("No configured providers found in OpenCode.");
1403
+ p6.log.info("Add providers in OpenCode first, or use relay-ai providers add.");
1338
1404
  return 0;
1339
1405
  }
1340
1406
  if (result.authFileWarning) {
1341
- p5.log.warn(result.authFileWarning);
1407
+ p6.log.warn(result.authFileWarning);
1342
1408
  }
1343
1409
  const importedNames = result.imported.map((pr) => pr.name).join(", ");
1344
1410
  const modelTotal = result.imported.reduce((n, pr) => n + (pr.modelsCache?.models.length ?? 0), 0);
1345
1411
  const credNote = result.oauthImported > 0 ? ` (${result.oauthImported} via OAuth)` : "";
1346
- p5.log.success(
1412
+ p6.log.success(
1347
1413
  `Imported ${importedNames} \u2014 ${modelTotal} model${modelTotal === 1 ? "" : "s"}, ${result.keysSaved} credential${result.keysSaved === 1 ? "" : "s"} saved to Keychain${credNote}.`
1348
1414
  );
1349
1415
  if (result.skipped.length > 0) {
1350
1416
  for (const s of result.skipped) {
1351
1417
  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}`);
1418
+ p6.log.warn(`Skipped ${s.name} (${s.id}): ${reason}`);
1353
1419
  }
1354
1420
  }
1355
1421
  if (result.keysSkipped.length > 0) {
1356
1422
  for (const k of result.keysSkipped) {
1357
1423
  if (k.detail) {
1358
- p5.log.info(`${k.name} (${k.id}): ${k.detail}`);
1424
+ p6.log.info(`${k.name} (${k.id}): ${k.detail}`);
1359
1425
  }
1360
1426
  }
1361
1427
  }
1362
1428
  if (result.imported.length > 0) {
1363
- const refreshSpinner = p5.spinner();
1429
+ const refreshSpinner = p6.spinner();
1364
1430
  refreshSpinner.start("Fetching model capabilities from providers...");
1365
1431
  const registry2 = loadRegistry();
1366
1432
  for (const provider of result.imported) {
@@ -1377,14 +1443,14 @@ async function runProvidersImport() {
1377
1443
  async function runProvidersAuth(providerId, method) {
1378
1444
  try {
1379
1445
  const result = await authenticateProvider(providerId, { method });
1380
- p5.log.success(`Signed in to ${result.registryProvider.name} \u2014 credential saved to Keychain.`);
1446
+ p6.log.success(`Signed in to ${result.registryProvider.name} \u2014 credential saved to Keychain.`);
1381
1447
  return 0;
1382
1448
  } catch (err) {
1383
1449
  if (err instanceof Error && err.message === "Cancelled") {
1384
- p5.cancel("Cancelled.");
1450
+ p6.cancel("Cancelled.");
1385
1451
  return 0;
1386
1452
  }
1387
- p5.log.error(err instanceof Error ? err.message : String(err));
1453
+ p6.log.error(err instanceof Error ? err.message : String(err));
1388
1454
  return 1;
1389
1455
  }
1390
1456
  }
@@ -1392,67 +1458,67 @@ async function runProvidersRefreshModels(providerId) {
1392
1458
  const resolveKey = async (provider) => resolveProviderCredential(provider.id, provider.authRef);
1393
1459
  if (providerId) {
1394
1460
  const registry = loadRegistry();
1395
- const provider = registry.providers.find((p15) => p15.id === providerId);
1461
+ const provider = registry.providers.find((p16) => p16.id === providerId);
1396
1462
  if (!provider) {
1397
- p5.log.error(`Provider not found: ${providerId}`);
1463
+ p6.log.error(`Provider not found: ${providerId}`);
1398
1464
  return 1;
1399
1465
  }
1400
- const spinner10 = p5.spinner();
1401
- spinner10.start(`Refreshing ${provider.name}...`);
1466
+ const spinner11 = p6.spinner();
1467
+ spinner11.start(`Refreshing ${provider.name}...`);
1402
1468
  const key = await resolveRefreshCredential(
1403
1469
  provider,
1404
- async (p15) => resolveProviderCredential(p15.id, p15.authRef)
1470
+ async (p16) => resolveProviderCredential(p16.id, p16.authRef)
1405
1471
  );
1406
1472
  const result = await refreshProviderModels(providerId, key);
1407
- spinner10.stop("");
1473
+ spinner11.stop("");
1408
1474
  if (result.skipped) {
1409
1475
  const countNote = result.modelCount ? ` (${result.modelCount} cached models kept)` : "";
1410
- p5.log.warn(`${result.name}: ${result.reason}${countNote}`);
1476
+ p6.log.warn(`${result.name}: ${result.reason}${countNote}`);
1411
1477
  return 0;
1412
1478
  }
1413
1479
  if (!result.ok) {
1414
- p5.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
1480
+ p6.log.error(`${result.name}: ${result.reason ?? "Refresh failed."}`);
1415
1481
  return 1;
1416
1482
  }
1417
1483
  const diff = result.previousModelCount === void 0 ? 0 : (result.modelCount ?? 0) - result.previousModelCount;
1418
1484
  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}.`);
1485
+ p6.log.success(`${result.name}: ${result.modelCount} model${result.modelCount === 1 ? "" : "s"} updated${diffStr}.`);
1420
1486
  if (result.reason) {
1421
- p5.log.warn(result.reason);
1487
+ p6.log.warn(result.reason);
1422
1488
  }
1423
1489
  return 0;
1424
1490
  }
1425
- const spinner9 = p5.spinner();
1426
- spinner9.start("Refreshing model lists...");
1491
+ const spinner10 = p6.spinner();
1492
+ spinner10.start("Refreshing model lists...");
1427
1493
  const { refreshed } = await refreshAllProviderModels(resolveKey);
1428
- spinner9.stop("");
1494
+ spinner10.stop("");
1429
1495
  const ok = refreshed.filter((r) => r.ok && !r.skipped);
1430
1496
  const skipped = refreshed.filter((r) => r.skipped);
1431
1497
  const failed = refreshed.filter((r) => !r.ok);
1432
1498
  if (ok.length > 0) {
1433
- p5.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
1499
+ p6.log.success(`Updated ${ok.length} provider${ok.length === 1 ? "" : "s"}.`);
1434
1500
  for (const r of ok) {
1435
1501
  const diff = r.previousModelCount === void 0 ? 0 : (r.modelCount ?? 0) - r.previousModelCount;
1436
1502
  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}`);
1503
+ p6.log.info(` ${r.name}: ${r.modelCount} model${r.modelCount === 1 ? "" : "s"}${diffStr}`);
1438
1504
  if (r.reason) {
1439
- p5.log.warn(` ${r.reason}`);
1505
+ p6.log.warn(` ${r.reason}`);
1440
1506
  }
1441
1507
  }
1442
1508
  }
1443
1509
  for (const r of skipped) {
1444
1510
  const countNote = r.modelCount ? ` (${r.modelCount} cached models kept)` : "";
1445
- p5.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
1511
+ p6.log.warn(`Skipped ${r.name}: ${r.reason}${countNote}`);
1446
1512
  }
1447
1513
  for (const r of failed) {
1448
- p5.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
1514
+ p6.log.error(`${r.name}: ${r.reason ?? "Refresh failed."}`);
1449
1515
  }
1450
1516
  return failed.length > 0 ? 1 : 0;
1451
1517
  }
1452
1518
  async function runProvidersList() {
1453
1519
  const entries = await resolveProvidersForDisplay();
1454
1520
  if (entries.length === 0) {
1455
- p5.log.info("No providers configured. Run relay-ai providers add or import.");
1521
+ p6.log.info("No providers configured. Run relay-ai providers add or import.");
1456
1522
  return 0;
1457
1523
  }
1458
1524
  console.log("");
@@ -1468,10 +1534,10 @@ async function runProvidersList() {
1468
1534
  async function pickTemplateFromCatalog() {
1469
1535
  while (true) {
1470
1536
  const registry = loadRegistry();
1471
- const configuredIds = new Set(registry.providers.map((p15) => p15.id));
1537
+ const configuredIds = new Set(registry.providers.map((p16) => p16.id));
1472
1538
  const templates = listAddableTemplates(configuredIds);
1473
1539
  if (templates.length === 0) return null;
1474
- const method = await p5.select({
1540
+ const method = await p6.select({
1475
1541
  message: `Choose a provider (${templates.length} available)`,
1476
1542
  options: [
1477
1543
  { value: "search", label: "Search providers", hint: "e.g. gro, mistral, together" },
@@ -1479,32 +1545,32 @@ async function pickTemplateFromCatalog() {
1479
1545
  { value: "back", label: "Back", hint: "" }
1480
1546
  ]
1481
1547
  });
1482
- if (p5.isCancel(method) || method === "back") return null;
1548
+ if (p6.isCancel(method) || method === "back") return null;
1483
1549
  if (method === "browse") {
1484
1550
  const options2 = templates.map((t) => ({
1485
1551
  value: t.id,
1486
1552
  label: t.name,
1487
1553
  hint: t.npm
1488
1554
  }));
1489
- const picked2 = await p5.select({ message: "Select a provider", options: options2 });
1490
- if (p5.isCancel(picked2)) continue;
1555
+ const picked2 = await p6.select({ message: "Select a provider", options: options2 });
1556
+ if (p6.isCancel(picked2)) continue;
1491
1557
  const template2 = templates.find((t) => t.id === picked2);
1492
1558
  if (template2) return template2;
1493
1559
  continue;
1494
1560
  }
1495
- const searchInput = await p5.text({
1561
+ const searchInput = await p6.text({
1496
1562
  message: "Search providers:",
1497
1563
  placeholder: "e.g. groq, mistral, openrouter"
1498
1564
  });
1499
- if (p5.isCancel(searchInput)) continue;
1565
+ if (p6.isCancel(searchInput)) continue;
1500
1566
  const query = String(searchInput);
1501
1567
  const matched = filterTemplates(templates, query);
1502
1568
  if (matched.length === 0) {
1503
1569
  const alreadyAdded = filterTemplates(listSupportedTemplates(), query).filter((t) => configuredIds.has(t.id));
1504
1570
  if (alreadyAdded.length > 0) {
1505
- p5.log.info(`Already configured: ${alreadyAdded.map((t) => t.name).join(", ")}`);
1571
+ p6.log.info(`Already configured: ${alreadyAdded.map((t) => t.name).join(", ")}`);
1506
1572
  } else {
1507
- p5.log.warn("No providers match \u2014 try a different search");
1573
+ p6.log.warn("No providers match \u2014 try a different search");
1508
1574
  }
1509
1575
  continue;
1510
1576
  }
@@ -1513,11 +1579,11 @@ async function pickTemplateFromCatalog() {
1513
1579
  label: t.name,
1514
1580
  hint: t.npm
1515
1581
  }));
1516
- const picked = await p5.select({
1582
+ const picked = await p6.select({
1517
1583
  message: matched.length === 1 ? "Match found" : `Select provider (${matched.length} matches)`,
1518
1584
  options
1519
1585
  });
1520
- if (p5.isCancel(picked)) continue;
1586
+ if (p6.isCancel(picked)) continue;
1521
1587
  const template = matched.find((t) => t.id === picked);
1522
1588
  if (template) return template;
1523
1589
  }
@@ -1534,7 +1600,7 @@ function showProviderAddFailure(error, hint, fallback) {
1534
1600
  ]);
1535
1601
  }
1536
1602
  async function runDualAuthTemplateFlow(template, existing) {
1537
- const method = await p5.select({
1603
+ const method = await p6.select({
1538
1604
  message: existing ? `Change ${template.name} authentication` : `How would you like to connect to ${template.name}?`,
1539
1605
  options: [
1540
1606
  {
@@ -1550,7 +1616,7 @@ async function runDualAuthTemplateFlow(template, existing) {
1550
1616
  { value: "back", label: "Back", hint: "" }
1551
1617
  ]
1552
1618
  });
1553
- if (p5.isCancel(method) || method === "back") return 0;
1619
+ if (p6.isCancel(method) || method === "back") return 0;
1554
1620
  if (method === "oauth") {
1555
1621
  return runProvidersAuth(template.id);
1556
1622
  }
@@ -1559,12 +1625,12 @@ async function runDualAuthTemplateFlow(template, existing) {
1559
1625
  `${pc4.white("Get an API key at:")} ${fmtUrl(template.signupUrl)}`
1560
1626
  ]);
1561
1627
  }
1562
- const apiKeyInput = await p5.password({
1628
+ const apiKeyInput = await p6.password({
1563
1629
  message: `Paste your ${template.name} API key:`,
1564
1630
  validate: (value) => value.trim() ? void 0 : "Key cannot be empty"
1565
1631
  });
1566
- if (p5.isCancel(apiKeyInput)) {
1567
- p5.cancel("Cancelled.");
1632
+ if (p6.isCancel(apiKeyInput)) {
1633
+ p6.cancel("Cancelled.");
1568
1634
  return 0;
1569
1635
  }
1570
1636
  const apiKey = String(apiKeyInput).trim();
@@ -1572,12 +1638,12 @@ async function runDualAuthTemplateFlow(template, existing) {
1572
1638
  showProviderAddFailure("Key cannot be empty.", void 0, "Could not add provider.");
1573
1639
  return 1;
1574
1640
  }
1575
- const spinner9 = p5.spinner();
1576
- spinner9.start(`Testing connection to ${template.name}...`);
1641
+ const spinner10 = p6.spinner();
1642
+ spinner10.start(`Testing connection to ${template.name}...`);
1577
1643
  const result = await addProviderFromTemplate(template, apiKey, {
1578
1644
  replaceExisting: Boolean(existing)
1579
1645
  });
1580
- spinner9.stop("");
1646
+ spinner10.stop("");
1581
1647
  if (!result.added) {
1582
1648
  showProviderAddFailure(result.error, result.hint, "Could not add provider.");
1583
1649
  return 1;
@@ -1586,8 +1652,8 @@ async function runDualAuthTemplateFlow(template, existing) {
1586
1652
  return 0;
1587
1653
  }
1588
1654
  async function runTemplateAddFlow() {
1589
- if (listAddableTemplates(loadRegistry().providers.map((p15) => p15.id)).length === 0) {
1590
- p5.log.info("All catalog providers are already configured.");
1655
+ if (listAddableTemplates(loadRegistry().providers.map((p16) => p16.id)).length === 0) {
1656
+ p6.log.info("All catalog providers are already configured.");
1591
1657
  return 0;
1592
1658
  }
1593
1659
  const template = await pickTemplateFromCatalog();
@@ -1605,25 +1671,25 @@ async function runTemplateAddFlow() {
1605
1671
  ]);
1606
1672
  const collected = await resolveOrCollectApiKey(false, false);
1607
1673
  if (!collected) {
1608
- p5.cancel("Cancelled.");
1674
+ p6.cancel("Cancelled.");
1609
1675
  return 0;
1610
1676
  }
1611
1677
  apiKey2 = collected;
1612
1678
  }
1613
1679
  await migrateGlobalOpencodeCredential();
1614
- const spinner10 = p5.spinner();
1615
- spinner10.start(`Adding ${template.name}...`);
1680
+ const spinner11 = p6.spinner();
1681
+ spinner11.start(`Adding ${template.name}...`);
1616
1682
  const result2 = await addOpencodeCloudFromApiKey(apiKey2);
1617
- spinner10.stop("");
1683
+ spinner11.stop("");
1618
1684
  if (!result2.added) {
1619
- p5.log.warn(result2.error ?? "OpenCode Zen / Go is already configured.");
1620
- if (result2.hint) p5.log.info(result2.hint);
1685
+ p6.log.warn(result2.error ?? "OpenCode Zen / Go is already configured.");
1686
+ if (result2.hint) p6.log.info(result2.hint);
1621
1687
  return 0;
1622
1688
  }
1623
1689
  if (result2.hint) {
1624
- p5.log.warn(`Added ${template.name}. ${result2.hint}`);
1690
+ p6.log.warn(`Added ${template.name}. ${result2.hint}`);
1625
1691
  } else {
1626
- p5.log.success(`Added ${template.name} \u2014 ${fmtCount(result2.modelCount ?? 0, "model")} updated.`);
1692
+ p6.log.success(`Added ${template.name} \u2014 ${fmtCount(result2.modelCount ?? 0, "model")} updated.`);
1627
1693
  }
1628
1694
  return 0;
1629
1695
  }
@@ -1634,48 +1700,48 @@ async function runTemplateAddFlow() {
1634
1700
  }
1635
1701
  let baseUrlOverride;
1636
1702
  if (template.accountIdPrompt) {
1637
- const accountInput = await p5.text({
1703
+ const accountInput = await p6.text({
1638
1704
  message: template.accountIdPrompt,
1639
1705
  placeholder: "e.g. 4ff191dac2d0bd7538cb1c9126594de3",
1640
1706
  validate: (v) => v.trim() ? void 0 : "Account ID is required"
1641
1707
  });
1642
- if (p5.isCancel(accountInput)) return 0;
1708
+ if (p6.isCancel(accountInput)) return 0;
1643
1709
  const accountId = String(accountInput).trim();
1644
1710
  baseUrlOverride = template.defaultBaseUrl?.replace("{ACCOUNT_ID}", accountId);
1645
1711
  } else if (template.urlPrompt) {
1646
- const urlInput = await p5.text({
1712
+ const urlInput = await p6.text({
1647
1713
  message: template.urlPrompt,
1648
1714
  initialValue: template.defaultBaseUrl,
1649
1715
  validate: (v) => v.trim() ? void 0 : "URL is required"
1650
1716
  });
1651
- if (p5.isCancel(urlInput)) return 0;
1717
+ if (p6.isCancel(urlInput)) return 0;
1652
1718
  baseUrlOverride = String(urlInput).trim();
1653
1719
  const usesHttp = /^http:\/\//i.test(baseUrlOverride);
1654
1720
  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.");
1721
+ p6.log.warn("HTTP is not encrypted. Use it only for trusted local or LAN servers, like Ollama on your own network.");
1656
1722
  }
1657
1723
  const valid = await validateCustomEndpointUrl(baseUrlOverride, { allowInsecureLocal: usesHttp });
1658
1724
  if (!valid.ok) {
1659
- p5.log.error(valid.error ?? "Invalid URL");
1660
- if (valid.hint) p5.log.info(valid.hint);
1725
+ p6.log.error(valid.error ?? "Invalid URL");
1726
+ if (valid.hint) p6.log.info(valid.hint);
1661
1727
  return 1;
1662
1728
  }
1663
1729
  }
1664
1730
  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({
1731
+ const apiKeyInput = await p6.password({
1666
1732
  message: apiKeyMsg,
1667
1733
  validate: (val) => template.apiKeyOptional ? void 0 : val.trim() ? void 0 : "Key cannot be empty"
1668
1734
  });
1669
- if (p5.isCancel(apiKeyInput)) {
1670
- p5.cancel("Cancelled.");
1735
+ if (p6.isCancel(apiKeyInput)) {
1736
+ p6.cancel("Cancelled.");
1671
1737
  return 0;
1672
1738
  }
1673
1739
  const rawKey = String(apiKeyInput).trim();
1674
1740
  const apiKey = template.apiKeyOptional && !rawKey && !template.anonymousFreeModels ? template.id : rawKey;
1675
- const spinner9 = p5.spinner();
1676
- spinner9.start(`Testing connection to ${template.name}...`);
1741
+ const spinner10 = p6.spinner();
1742
+ spinner10.start(`Testing connection to ${template.name}...`);
1677
1743
  const result = await addProviderFromTemplate(template, apiKey, { baseUrl: baseUrlOverride });
1678
- spinner9.stop("");
1744
+ spinner10.stop("");
1679
1745
  if (!result.added) {
1680
1746
  showProviderAddFailure(result.error, result.hint, "Could not add provider.");
1681
1747
  return 1;
@@ -1684,7 +1750,7 @@ async function runTemplateAddFlow() {
1684
1750
  return 0;
1685
1751
  }
1686
1752
  async function runCustomEndpointAddFlow() {
1687
- const kindChoice = await p5.select({
1753
+ const kindChoice = await p6.select({
1688
1754
  message: "Custom server type",
1689
1755
  options: [
1690
1756
  {
@@ -1700,52 +1766,52 @@ async function runCustomEndpointAddFlow() {
1700
1766
  { value: "back", label: "Back", hint: "" }
1701
1767
  ]
1702
1768
  });
1703
- if (p5.isCancel(kindChoice) || kindChoice === "back") return 0;
1704
- const displayName = await p5.text({
1769
+ if (p6.isCancel(kindChoice) || kindChoice === "back") return 0;
1770
+ const displayName = await p6.text({
1705
1771
  message: "Display name:",
1706
1772
  placeholder: "My Work LLM",
1707
1773
  validate: (v) => v.trim() ? void 0 : "Name is required"
1708
1774
  });
1709
- if (p5.isCancel(displayName)) return 0;
1710
- const baseUrl = await p5.text({
1775
+ if (p6.isCancel(displayName)) return 0;
1776
+ const baseUrl = await p6.text({
1711
1777
  message: "Base URL:",
1712
1778
  placeholder: kindChoice === "openai" ? "https://api.together.xyz/v1" : "https://api.anthropic.com",
1713
1779
  validate: (v) => v.trim() ? void 0 : "URL is required"
1714
1780
  });
1715
- if (p5.isCancel(baseUrl)) return 0;
1781
+ if (p6.isCancel(baseUrl)) return 0;
1716
1782
  const usesHttp = /^http:\/\//i.test(String(baseUrl).trim());
1717
1783
  let allowInsecureHttp = false;
1718
1784
  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({
1785
+ p6.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1786
+ const allowLocal = await p6.confirm({
1721
1787
  message: "Allow insecure HTTP for this local/LAN server?",
1722
1788
  initialValue: true
1723
1789
  });
1724
- if (p5.isCancel(allowLocal)) return 0;
1790
+ if (p6.isCancel(allowLocal)) return 0;
1725
1791
  allowInsecureHttp = allowLocal === true;
1726
1792
  }
1727
- const apiKey = await p5.password({
1793
+ const apiKey = await p6.password({
1728
1794
  message: "API key (leave empty for local servers without auth):"
1729
1795
  });
1730
- if (p5.isCancel(apiKey)) return 0;
1731
- const wantsHeaders = await p5.confirm({
1796
+ if (p6.isCancel(apiKey)) return 0;
1797
+ const wantsHeaders = await p6.confirm({
1732
1798
  message: "Does this endpoint need extra custom headers? (e.g. a plan/auth-tracking header)",
1733
1799
  initialValue: false
1734
1800
  });
1735
- if (p5.isCancel(wantsHeaders)) return 0;
1801
+ if (p6.isCancel(wantsHeaders)) return 0;
1736
1802
  const headers = {};
1737
1803
  if (wantsHeaders) {
1738
1804
  for (; ; ) {
1739
- const headerLine = await p5.text({
1805
+ const headerLine = await p6.text({
1740
1806
  message: "Header (leave empty when done):",
1741
1807
  placeholder: "X-Plan: coding"
1742
1808
  });
1743
- if (p5.isCancel(headerLine)) return 0;
1809
+ if (p6.isCancel(headerLine)) return 0;
1744
1810
  const trimmed = String(headerLine).trim();
1745
1811
  if (!trimmed) break;
1746
1812
  const idx = trimmed.indexOf(":");
1747
1813
  if (idx < 1) {
1748
- p5.log.warn('Use the format "Name: Value" \u2014 skipped.');
1814
+ p6.log.warn('Use the format "Name: Value" \u2014 skipped.');
1749
1815
  continue;
1750
1816
  }
1751
1817
  const name = trimmed.slice(0, idx).trim();
@@ -1761,22 +1827,22 @@ async function runCustomEndpointAddFlow() {
1761
1827
  allowInsecureLocal: allowInsecureHttp,
1762
1828
  headers: Object.keys(headers).length > 0 ? headers : void 0
1763
1829
  };
1764
- const spinner9 = p5.spinner();
1765
- spinner9.start("Testing connection...");
1830
+ const spinner10 = p6.spinner();
1831
+ spinner10.start("Testing connection...");
1766
1832
  let result = await addCustomEndpointProvider(addInput);
1767
- spinner9.stop("");
1833
+ spinner10.stop("");
1768
1834
  if (!result.added && result.duplicateOf) {
1769
- const addAnyway = await p5.confirm({
1835
+ const addAnyway = await p6.confirm({
1770
1836
  message: `You already have a backend with the same URL, key and headers (${result.duplicateOf}). Add another?`,
1771
1837
  initialValue: false
1772
1838
  });
1773
- if (p5.isCancel(addAnyway) || !addAnyway) {
1774
- p5.cancel("Cancelled.");
1839
+ if (p6.isCancel(addAnyway) || !addAnyway) {
1840
+ p6.cancel("Cancelled.");
1775
1841
  return 0;
1776
1842
  }
1777
- spinner9.start("Testing connection...");
1843
+ spinner10.start("Testing connection...");
1778
1844
  result = await addCustomEndpointProvider({ ...addInput, confirmDuplicate: true });
1779
- spinner9.stop("");
1845
+ spinner10.stop("");
1780
1846
  }
1781
1847
  if (!result.added) {
1782
1848
  showProviderAddFailure(result.error, result.hint, "Could not add custom provider.");
@@ -1788,70 +1854,70 @@ async function runCustomEndpointAddFlow() {
1788
1854
  async function runCustomEndpointEditFlow(provider) {
1789
1855
  const currentHeaders = provider.api.headers ?? {};
1790
1856
  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)"}
1857
+ p6.log.info(`Base URL: ${provider.api.url ?? "(none)"}
1792
1858
  Headers: ${headerSummary}`);
1793
- const displayName = await p5.text({
1859
+ const displayName = await p6.text({
1794
1860
  message: "Display name:",
1795
1861
  initialValue: provider.name,
1796
1862
  validate: (v) => v.trim() ? void 0 : "Name is required"
1797
1863
  });
1798
- if (p5.isCancel(displayName)) {
1799
- p5.cancel("Cancelled.");
1864
+ if (p6.isCancel(displayName)) {
1865
+ p6.cancel("Cancelled.");
1800
1866
  return 0;
1801
1867
  }
1802
- const baseUrl = await p5.text({
1868
+ const baseUrl = await p6.text({
1803
1869
  message: "Base URL:",
1804
1870
  initialValue: provider.api.url ?? "",
1805
1871
  validate: (v) => v.trim() ? void 0 : "URL is required"
1806
1872
  });
1807
- if (p5.isCancel(baseUrl)) {
1808
- p5.cancel("Cancelled.");
1873
+ if (p6.isCancel(baseUrl)) {
1874
+ p6.cancel("Cancelled.");
1809
1875
  return 0;
1810
1876
  }
1811
1877
  const usesHttp = /^http:\/\//i.test(String(baseUrl).trim());
1812
1878
  let allowInsecureHttp = false;
1813
1879
  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({
1880
+ p6.log.warn("HTTP is not encrypted. Only use it for a trusted local or LAN server, like Ollama on your own network.");
1881
+ const allowLocal = await p6.confirm({
1816
1882
  message: "Allow insecure HTTP for this local/LAN server?",
1817
1883
  initialValue: true
1818
1884
  });
1819
- if (p5.isCancel(allowLocal)) return 0;
1885
+ if (p6.isCancel(allowLocal)) return 0;
1820
1886
  allowInsecureHttp = allowLocal === true;
1821
1887
  }
1822
- const apiKey = await p5.password({
1888
+ const apiKey = await p6.password({
1823
1889
  message: "API key (leave empty to keep the current key):"
1824
1890
  });
1825
- if (p5.isCancel(apiKey)) {
1826
- p5.cancel("Cancelled.");
1891
+ if (p6.isCancel(apiKey)) {
1892
+ p6.cancel("Cancelled.");
1827
1893
  return 0;
1828
1894
  }
1829
- const editHeaders = await p5.confirm({
1895
+ const editHeaders = await p6.confirm({
1830
1896
  message: `Replace custom headers? (current: ${headerSummary})`,
1831
1897
  initialValue: false
1832
1898
  });
1833
- if (p5.isCancel(editHeaders)) {
1834
- p5.cancel("Cancelled.");
1899
+ if (p6.isCancel(editHeaders)) {
1900
+ p6.cancel("Cancelled.");
1835
1901
  return 0;
1836
1902
  }
1837
1903
  let headers;
1838
1904
  if (editHeaders) {
1839
1905
  headers = {};
1840
- p5.log.info("Enter the full header set. Leave the first one empty to remove all headers.");
1906
+ p6.log.info("Enter the full header set. Leave the first one empty to remove all headers.");
1841
1907
  for (; ; ) {
1842
- const headerLine = await p5.text({
1908
+ const headerLine = await p6.text({
1843
1909
  message: "Header (leave empty when done):",
1844
1910
  placeholder: "X-Plan: coding"
1845
1911
  });
1846
- if (p5.isCancel(headerLine)) {
1847
- p5.cancel("Cancelled.");
1912
+ if (p6.isCancel(headerLine)) {
1913
+ p6.cancel("Cancelled.");
1848
1914
  return 0;
1849
1915
  }
1850
1916
  const trimmed = String(headerLine).trim();
1851
1917
  if (!trimmed) break;
1852
1918
  const idx = trimmed.indexOf(":");
1853
1919
  if (idx < 1) {
1854
- p5.log.warn('Use the format "Name: Value" \u2014 skipped.');
1920
+ p6.log.warn('Use the format "Name: Value" \u2014 skipped.');
1855
1921
  continue;
1856
1922
  }
1857
1923
  const name = trimmed.slice(0, idx).trim();
@@ -1868,22 +1934,22 @@ Headers: ${headerSummary}`);
1868
1934
  allowInsecureLocal: allowInsecureHttp,
1869
1935
  saveAnyway
1870
1936
  });
1871
- const spinner9 = p5.spinner();
1872
- spinner9.start("Testing connection...");
1937
+ const spinner10 = p6.spinner();
1938
+ spinner10.start("Testing connection...");
1873
1939
  let result = await runUpdate(false);
1874
- spinner9.stop("");
1940
+ spinner10.stop("");
1875
1941
  if (!result.updated && result.error === "Nothing to change.") {
1876
- p5.log.info("No changes made.");
1942
+ p6.log.info("No changes made.");
1877
1943
  return 0;
1878
1944
  }
1879
1945
  if (!result.updated) {
1880
1946
  showProviderAddFailure(result.error, result.hint, "Could not update backend.");
1881
1947
  if (!result.canSaveAnyway) return 1;
1882
- const saveAnyway = await p5.confirm({
1948
+ const saveAnyway = await p6.confirm({
1883
1949
  message: "Save these settings anyway? The model list will not be refreshed.",
1884
1950
  initialValue: false
1885
1951
  });
1886
- if (p5.isCancel(saveAnyway) || !saveAnyway) return 1;
1952
+ if (p6.isCancel(saveAnyway) || !saveAnyway) return 1;
1887
1953
  result = await runUpdate(true);
1888
1954
  if (!result.updated) {
1889
1955
  showProviderAddFailure(result.error, result.hint, "Could not update backend.");
@@ -1891,7 +1957,7 @@ Headers: ${headerSummary}`);
1891
1957
  }
1892
1958
  }
1893
1959
  if (result.modelsStale) {
1894
- p5.log.warn(`${result.provider?.name ?? provider.name} saved, but the model list may be out of date.`);
1960
+ p6.log.warn(`${result.provider?.name ?? provider.name} saved, but the model list may be out of date.`);
1895
1961
  } else {
1896
1962
  logConnected(result.provider?.name ?? provider.name, result.modelCount ?? 0);
1897
1963
  }
@@ -1901,7 +1967,7 @@ async function runProvidersAdd() {
1901
1967
  const registry = loadRegistry();
1902
1968
  const hasOpencode = findOpencodeBinary() !== null;
1903
1969
  const options = [];
1904
- const addableTemplates = listAddableTemplates(registry.providers.map((p15) => p15.id));
1970
+ const addableTemplates = listAddableTemplates(registry.providers.map((p16) => p16.id));
1905
1971
  if (addableTemplates.length > 0) {
1906
1972
  options.push({
1907
1973
  value: "templates",
@@ -1919,14 +1985,14 @@ async function runProvidersAdd() {
1919
1985
  label: "Import providers from OpenCode CLI",
1920
1986
  hint: hasOpencode ? "Import Groq, OpenAI, etc. from your OpenCode config" : "Requires OpenCode CLI"
1921
1987
  });
1922
- const choice = await p5.select({ message: "Add a provider", options });
1923
- if (p5.isCancel(choice)) {
1924
- p5.cancel("Cancelled.");
1988
+ const choice = await p6.select({ message: "Add a provider", options });
1989
+ if (p6.isCancel(choice)) {
1990
+ p6.cancel("Cancelled.");
1925
1991
  return 0;
1926
1992
  }
1927
1993
  if (choice === "import") {
1928
1994
  if (!hasOpencode) {
1929
- p5.log.error("OpenCode CLI not found. Install from https://opencode.ai");
1995
+ p6.log.error("OpenCode CLI not found. Install from https://opencode.ai");
1930
1996
  return 1;
1931
1997
  }
1932
1998
  return runProvidersImport();
@@ -1939,27 +2005,27 @@ async function runProvidersRemove(id, interactive = false) {
1939
2005
  const registry = loadRegistry();
1940
2006
  const provider = registry.providers.find((pr) => pr.id === id);
1941
2007
  if (!provider) {
1942
- p5.log.error(`Provider not found: ${id}`);
2008
+ p6.log.error(`Provider not found: ${id}`);
1943
2009
  return 1;
1944
2010
  }
1945
2011
  if (interactive) {
1946
- const confirm9 = await p5.confirm({
2012
+ const confirm10 = await p6.confirm({
1947
2013
  message: `Remove ${provider.name} (${id})?`,
1948
2014
  initialValue: false
1949
2015
  });
1950
- if (p5.isCancel(confirm9) || !confirm9) {
1951
- p5.cancel("Cancelled.");
2016
+ if (p6.isCancel(confirm10) || !confirm10) {
2017
+ p6.cancel("Cancelled.");
1952
2018
  return 0;
1953
2019
  }
1954
2020
  }
1955
2021
  const result = await removeProviderFromRegistry(id);
1956
2022
  if (!result.removed) {
1957
- p5.log.error(result.error ?? `Could not remove ${id}`);
2023
+ p6.log.error(result.error ?? `Could not remove ${id}`);
1958
2024
  return 1;
1959
2025
  }
1960
- p5.log.success(`Removed ${result.name ?? id}.`);
2026
+ p6.log.success(`Removed ${result.name ?? id}.`);
1961
2027
  if (result.credentialDeleted) {
1962
- p5.log.info("Provider API key removed from Keychain.");
2028
+ p6.log.info("Provider API key removed from Keychain.");
1963
2029
  }
1964
2030
  return 0;
1965
2031
  }
@@ -1968,7 +2034,7 @@ async function runOpenCodeCloudDetail() {
1968
2034
  const routes = registry.providers.filter((provider) => provider.id === "zen" || provider.id === "go");
1969
2035
  printCloudProviderPanel("OpenCode Zen / Go");
1970
2036
  if (routes.length === 0) return "back";
1971
- const choice = await p5.select({
2037
+ const choice = await p6.select({
1972
2038
  message: "Manage an OpenCode catalog",
1973
2039
  options: [
1974
2040
  ...routes.map((provider) => ({
@@ -1979,7 +2045,7 @@ async function runOpenCodeCloudDetail() {
1979
2045
  { value: "back", label: "Back", hint: "" }
1980
2046
  ]
1981
2047
  });
1982
- if (!p5.isCancel(choice) && choice !== "back") {
2048
+ if (!p6.isCancel(choice) && choice !== "back") {
1983
2049
  await runProviderDetail(String(choice));
1984
2050
  }
1985
2051
  return "back";
@@ -1991,12 +2057,18 @@ async function runProviderDetail(id) {
1991
2057
  const registry = loadRegistry();
1992
2058
  const provider = registry.providers.find((pr) => pr.id === id);
1993
2059
  if (!provider) return "back";
1994
- const modelCount = provider.modelsCache?.models.length ?? 0;
2060
+ const modelCount = getProviderModels(provider).length;
1995
2061
  const authLabel = formatRegistryAuthLabel(provider);
1996
2062
  printProviderDetailPanel(provider.name, modelCount, authLabel);
1997
2063
  const template = getTemplateById(provider.templateId) ?? getTemplateById(id);
1998
2064
  const hasDualAuth = template ? hasApiAndOAuth(template) : false;
1999
2065
  const detailOptions = [];
2066
+ if (supportsManualModels(provider) && provider.enabled) {
2067
+ detailOptions.push({ value: "add-model", label: "Add model manually", hint: "Test an exact model ID, then save it" });
2068
+ }
2069
+ if (provider.manualModels?.length) {
2070
+ detailOptions.push({ value: "remove-model", label: "Remove manual model", hint: "Keep discovered models" });
2071
+ }
2000
2072
  if (modelCount > 0) {
2001
2073
  detailOptions.push({
2002
2074
  value: "browse",
@@ -2045,13 +2117,15 @@ async function runProviderDetail(id) {
2045
2117
  { value: "remove", label: "Remove provider", hint: "Delete from registry and Keychain when safe" },
2046
2118
  { value: "back", label: "Back", hint: "" }
2047
2119
  );
2048
- const action = await p5.select({
2120
+ const action = await p6.select({
2049
2121
  message: "What would you like to do?",
2050
2122
  options: detailOptions
2051
2123
  });
2052
- if (p5.isCancel(action) || action === "back") return "back";
2124
+ if (p6.isCancel(action) || action === "back") return "back";
2125
+ if (action === "add-model") return await runManualModelAddFlow(provider) === 0 ? "back" : "failed";
2126
+ if (action === "remove-model") return await runManualModelRemoveFlow(provider) === 0 ? "back" : "failed";
2053
2127
  if (action === "browse") {
2054
- const cachedModels = provider.modelsCache?.models ?? [];
2128
+ const cachedModels = getProviderModels(provider);
2055
2129
  const localModels = cachedModels.map((m) => cachedModelToLocal(m, provider)).filter((m) => m !== null);
2056
2130
  const localProvider = {
2057
2131
  id: provider.id,
@@ -2080,7 +2154,7 @@ async function runProviderDetail(id) {
2080
2154
  if (action === "toggle") {
2081
2155
  const result = toggleProviderEnabled(id);
2082
2156
  if (result.toggled) {
2083
- p5.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
2157
+ p6.log.success(`${provider.name} ${result.enabled ? "enabled" : "disabled"}.`);
2084
2158
  }
2085
2159
  return "back";
2086
2160
  }
@@ -2088,17 +2162,17 @@ async function runProviderDetail(id) {
2088
2162
  return code === 0 ? "removed" : "failed";
2089
2163
  }
2090
2164
  async function runProviderApiKeyChange(provider, registry) {
2091
- const entered = await p5.password({
2165
+ const entered = await p6.password({
2092
2166
  message: `Enter the new API key for ${provider.name}`,
2093
2167
  mask: "\u2022"
2094
2168
  });
2095
- if (p5.isCancel(entered)) {
2096
- p5.cancel("Cancelled.");
2169
+ if (p6.isCancel(entered)) {
2170
+ p6.cancel("Cancelled.");
2097
2171
  return 0;
2098
2172
  }
2099
2173
  const key = String(entered).trim();
2100
2174
  if (!key) {
2101
- p5.log.warn("API key cannot be empty.");
2175
+ p6.log.warn("API key cannot be empty.");
2102
2176
  return 1;
2103
2177
  }
2104
2178
  const targetAuthRef = preferredRelayCredentialAuthRef(provider.id, provider.authRef);
@@ -2107,29 +2181,29 @@ async function runProviderApiKeyChange(provider, registry) {
2107
2181
  existing = await readStoredProviderCredential(provider.authRef);
2108
2182
  }
2109
2183
  if (existing && existing !== key) {
2110
- const confirmed = await p5.confirm({
2184
+ const confirmed = await p6.confirm({
2111
2185
  message: "A different key is already stored. Replace it?",
2112
2186
  initialValue: false
2113
2187
  });
2114
- if (p5.isCancel(confirmed) || !confirmed) {
2115
- p5.log.info("Kept the existing stored key.");
2188
+ if (p6.isCancel(confirmed) || !confirmed) {
2189
+ p6.log.info("Kept the existing stored key.");
2116
2190
  return 0;
2117
2191
  }
2118
2192
  }
2119
- const spinner9 = p5.spinner();
2120
- spinner9.start(`Testing ${provider.name} and refreshing models...`);
2193
+ const spinner10 = p6.spinner();
2194
+ spinner10.start(`Testing ${provider.name} and refreshing models...`);
2121
2195
  const result = await refreshProviderModels(provider.id, key, registry);
2122
- spinner9.stop("");
2196
+ spinner10.stop("");
2123
2197
  if (!result.ok) {
2124
- p5.log.error(`${provider.name}: ${result.reason ?? "The new key was rejected."}`);
2198
+ p6.log.error(`${provider.name}: ${result.reason ?? "The new key was rejected."}`);
2125
2199
  return 1;
2126
2200
  }
2127
2201
  const saved = await saveProviderCredential(targetAuthRef, key);
2128
2202
  if (!saved) {
2129
- p5.log.error("The new key works, but the credential store was unavailable \u2014 key was not saved.");
2203
+ p6.log.error("The new key works, but the credential store was unavailable \u2014 key was not saved.");
2130
2204
  return 1;
2131
2205
  }
2132
- p5.log.success(`${provider.name}: key updated and ${result.modelCount ?? 0} model${result.modelCount === 1 ? "" : "s"} available.`);
2206
+ p6.log.success(`${provider.name}: key updated and ${result.modelCount ?? 0} model${result.modelCount === 1 ? "" : "s"} available.`);
2133
2207
  return 0;
2134
2208
  }
2135
2209
  async function runProvidersHub() {
@@ -2157,11 +2231,11 @@ async function runProvidersHub() {
2157
2231
  options.push({ value: "import", label: "\u2192 Import providers from OpenCode CLI", hint: "One-time import" });
2158
2232
  }
2159
2233
  options.push({ value: "done", label: "Done", hint: "" });
2160
- const choice = await p5.select({
2234
+ const choice = await p6.select({
2161
2235
  message: entries.length > 0 ? "Your AI providers" : "Get started",
2162
2236
  options
2163
2237
  });
2164
- if (p5.isCancel(choice) || choice === "done") {
2238
+ if (p6.isCancel(choice) || choice === "done") {
2165
2239
  return lastOperationFailed ? 1 : 0;
2166
2240
  }
2167
2241
  if (choice === "add") {
@@ -2180,10 +2254,10 @@ async function runProvidersHub() {
2180
2254
  const configuredIds = loadRegistry().providers.map((provider) => provider.id);
2181
2255
  const oauthTemplates = listVisibleOAuthTemplates(configuredIds);
2182
2256
  if (oauthTemplates.length === 0) {
2183
- p5.log.info("All visible OAuth providers are already configured.");
2257
+ p6.log.info("All visible OAuth providers are already configured.");
2184
2258
  continue;
2185
2259
  }
2186
- const providerId = await p5.select({
2260
+ const providerId = await p6.select({
2187
2261
  message: "Which provider?",
2188
2262
  options: oauthTemplates.map((template) => ({
2189
2263
  value: template.id,
@@ -2191,7 +2265,7 @@ async function runProvidersHub() {
2191
2265
  hint: "device code"
2192
2266
  }))
2193
2267
  });
2194
- if (!p5.isCancel(providerId)) {
2268
+ if (!p6.isCancel(providerId)) {
2195
2269
  lastOperationFailed = await runProvidersAuth(providerId) !== 0;
2196
2270
  }
2197
2271
  continue;
@@ -2212,7 +2286,7 @@ async function runProvidersHub() {
2212
2286
  async function runProvidersCommand(args) {
2213
2287
  const parsed = parseProvidersArgs(args);
2214
2288
  if (parsed.error) {
2215
- p5.log.error(parsed.error);
2289
+ p6.log.error(parsed.error);
2216
2290
  return 1;
2217
2291
  }
2218
2292
  if (parsed.showHelp) {
@@ -2237,7 +2311,7 @@ async function runProvidersCommand(args) {
2237
2311
 
2238
2312
  // src/codex.ts
2239
2313
  import pc7 from "picocolors";
2240
- import * as p8 from "@clack/prompts";
2314
+ import * as p9 from "@clack/prompts";
2241
2315
  import { join as join6 } from "path";
2242
2316
 
2243
2317
  // src/codex-proxy.ts
@@ -2342,15 +2416,28 @@ function liftAdditionalToolsInput(input, tools) {
2342
2416
  }
2343
2417
  function messageText(content) {
2344
2418
  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("");
2419
+ return (content ?? []).map((p16) => p16.type === "output_text" || p16.type === "input_text" || p16.type === "text" ? p16.text ?? "" : "").join("");
2420
+ }
2421
+ function messageContent(content) {
2422
+ if (typeof content === "string") return content;
2423
+ const parts = (content ?? []).flatMap((part) => {
2424
+ if (part.type === "output_text" || part.type === "input_text" || part.type === "text") {
2425
+ return typeof part.text === "string" ? [{ type: "text", text: part.text }] : [];
2426
+ }
2427
+ if (part.type === "input_image" && typeof part.image_url === "string" && part.image_url.trim()) {
2428
+ return [{ type: "image", image: part.image_url }];
2429
+ }
2430
+ return [];
2431
+ });
2432
+ return parts.length > 0 ? parts : [{ type: "text", text: "" }];
2346
2433
  }
2347
2434
  function extractDeveloperAndInstructions(items, instructions) {
2348
2435
  const developerParts = [];
2349
2436
  const remaining = [];
2350
2437
  for (const item of items) {
2351
2438
  if ("role" in item && item.role === "developer") {
2352
- const text5 = messageText(item.content);
2353
- if (text5.trim()) developerParts.push(text5.trim());
2439
+ const text6 = messageText(item.content);
2440
+ if (text6.trim()) developerParts.push(text6.trim());
2354
2441
  } else {
2355
2442
  remaining.push(item);
2356
2443
  }
@@ -2423,11 +2510,11 @@ function ensureUserFirst(messages) {
2423
2510
  function reasoningSummaryText(item) {
2424
2511
  return (item.summary ?? []).map((part) => part.type === "summary_text" ? part.text ?? "" : "").join("");
2425
2512
  }
2426
- function makeReasoningOutputItem(id, text5) {
2513
+ function makeReasoningOutputItem(id, text6) {
2427
2514
  return {
2428
2515
  id,
2429
2516
  type: "reasoning",
2430
- summary: text5.trim() ? [{ type: "summary_text", text: text5 }] : []
2517
+ summary: text6.trim() ? [{ type: "summary_text", text: text6 }] : []
2431
2518
  };
2432
2519
  }
2433
2520
  function translateResponsesInput(input, instructions, npm, toolContext = createCodexToolContext()) {
@@ -2528,9 +2615,9 @@ function translateResponsesInput(input, instructions, npm, toolContext = createC
2528
2615
  }]
2529
2616
  });
2530
2617
  } 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 }] });
2618
+ const text6 = messageText(item.content);
2619
+ if (text6.trim()) {
2620
+ messages.push({ role: "user", content: [{ type: "text", text: text6 }] });
2534
2621
  }
2535
2622
  } else if (item.type === "compaction" || item.type === "context_compaction") {
2536
2623
  const summary = decodeCompactionContent(item.encrypted_content) ?? "";
@@ -2540,8 +2627,8 @@ ${summary}` }] });
2540
2627
  }
2541
2628
  } else if ("role" in item) {
2542
2629
  const role = item.role === "assistant" ? "assistant" : "user";
2543
- const text5 = messageText(item.content);
2544
- messages.push({ role, content: [{ type: "text", text: text5 }] });
2630
+ const content = messageContent(item.content);
2631
+ messages.push({ role, content });
2545
2632
  }
2546
2633
  }
2547
2634
  return {
@@ -2873,8 +2960,8 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2873
2960
  loopDetected,
2874
2961
  aborted: true
2875
2962
  });
2876
- emit("response.completed", {
2877
- type: "response.completed",
2963
+ emit("response.failed", {
2964
+ type: "response.failed",
2878
2965
  response: {
2879
2966
  id: responseId,
2880
2967
  object: "response",
@@ -2904,8 +2991,8 @@ async function writeResponsesStream(fullStream, modelId, write, onDone, onProgre
2904
2991
  if (is429) {
2905
2992
  writeResponsesRateLimitStream(modelId, msg, write);
2906
2993
  } else {
2907
- emit("response.completed", {
2908
- type: "response.completed",
2994
+ emit("response.failed", {
2995
+ type: "response.failed",
2909
2996
  response: {
2910
2997
  id: responseId,
2911
2998
  object: "response",
@@ -3251,8 +3338,8 @@ function responsesErrorBody(modelId, message, statusCode = 401) {
3251
3338
  };
3252
3339
  }
3253
3340
  function writeResponsesErrorStream(modelId, message, write, statusCode = 401) {
3254
- write(sseChunk("response.completed", {
3255
- type: "response.completed",
3341
+ write(sseChunk("response.failed", {
3342
+ type: "response.failed",
3256
3343
  response: responsesErrorBody(modelId, message, statusCode)
3257
3344
  }));
3258
3345
  }
@@ -3470,16 +3557,20 @@ function allowlistedNativeHeaders(inboundHeaders) {
3470
3557
  function prepareNativeCodexBody(body) {
3471
3558
  if (!Array.isArray(body.input)) return body;
3472
3559
  let changed = false;
3473
- const input = body.input.map((item) => {
3474
- if (!item || typeof item !== "object" || Array.isArray(item)) return item;
3560
+ const input = body.input.flatMap((item) => {
3561
+ if (!item || typeof item !== "object" || Array.isArray(item)) return [item];
3475
3562
  const record = item;
3476
- if (record.type !== "compaction" && record.type !== "context_compaction") return item;
3563
+ if (record.type === "reasoning" && typeof record.encrypted_content !== "string") {
3564
+ changed = true;
3565
+ return [];
3566
+ }
3567
+ if (record.type !== "compaction" && record.type !== "context_compaction") return [item];
3477
3568
  const summary = decodeCompactionContent(
3478
3569
  typeof record.encrypted_content === "string" ? record.encrypted_content : void 0
3479
3570
  );
3480
- if (summary === null) return item;
3571
+ if (summary === null) return [item];
3481
3572
  changed = true;
3482
- return {
3573
+ return [{
3483
3574
  type: "message",
3484
3575
  role: "user",
3485
3576
  content: [{
@@ -3487,14 +3578,14 @@ function prepareNativeCodexBody(body) {
3487
3578
  text: `[Summary of earlier conversation]
3488
3579
  ${summary}`
3489
3580
  }]
3490
- };
3581
+ }];
3491
3582
  });
3492
3583
  return changed ? { ...body, input } : body;
3493
3584
  }
3494
3585
  function prepareNativeHttpBody(body) {
3495
- const text5 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3586
+ const text6 = typeof body === "string" ? body : Buffer.from(body).toString("utf8");
3496
3587
  try {
3497
- const parsed = JSON.parse(text5);
3588
+ const parsed = JSON.parse(text6);
3498
3589
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return body;
3499
3590
  const prepared = prepareNativeCodexBody(parsed);
3500
3591
  return prepared === parsed ? body : JSON.stringify(prepared);
@@ -3651,11 +3742,11 @@ function parseFunctionCalls(value) {
3651
3742
  (v) => v && typeof v === "object" && v.type === "function_call"
3652
3743
  );
3653
3744
  }
3654
- function parsePayloadResponse(text5) {
3745
+ function parsePayloadResponse(text6) {
3655
3746
  const relayIds = /* @__PURE__ */ new Set();
3656
3747
  const completedPayloads = [];
3657
3748
  let argumentDeltas = "";
3658
- for (const line of text5.split(/\r?\n/)) {
3749
+ for (const line of text6.split(/\r?\n/)) {
3659
3750
  if (!line.startsWith("data:")) continue;
3660
3751
  const data = line.slice(5).trim();
3661
3752
  if (!data || data === "[DONE]") continue;
@@ -3681,7 +3772,7 @@ function parsePayloadResponse(text5) {
3681
3772
  }
3682
3773
  }
3683
3774
  try {
3684
- for (const call of parseFunctionCalls(JSON.parse(text5))) {
3775
+ for (const call of parseFunctionCalls(JSON.parse(text6))) {
3685
3776
  if (call.name !== AGENT_PAYLOAD_RELAY_TOOL) continue;
3686
3777
  const payload = parsePayloadArguments(call.arguments);
3687
3778
  if (payload !== void 0) completedPayloads.push(payload);
@@ -3746,9 +3837,9 @@ function createNativePayloadRelay(options) {
3746
3837
  });
3747
3838
  const response = await fetchImpl(url, { method: "POST", headers: nativeHeaders(context.headers), body, redirect: "manual", signal: context.signal });
3748
3839
  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);
3840
+ const text6 = await response.text();
3841
+ if (Buffer.byteLength(text6, "utf8") > NATIVE_PAYLOAD_MAX_BYTES) throw new Error("Native collaboration relay response exceeded its size limit");
3842
+ const payload = parsePayloadResponse(text6);
3752
3843
  cacheValue(key, payload, Date.now() + 15 * 6e4);
3753
3844
  return payload;
3754
3845
  },
@@ -3864,9 +3955,9 @@ function estimateCodexRequestChars(params) {
3864
3955
  if (Array.isArray(msg.content)) {
3865
3956
  for (const part of msg.content) {
3866
3957
  if (!part || typeof part !== "object") continue;
3867
- const p15 = part;
3868
- if (typeof p15["text"] === "string") {
3869
- chars += p15["text"].length;
3958
+ const p16 = part;
3959
+ if (typeof p16["text"] === "string") {
3960
+ chars += p16["text"].length;
3870
3961
  } else {
3871
3962
  chars += JSON.stringify(part).length;
3872
3963
  }
@@ -3877,15 +3968,15 @@ function estimateCodexRequestChars(params) {
3877
3968
  }
3878
3969
  return chars;
3879
3970
  }
3880
- function clipTextForContext(text5, maxChars) {
3881
- if (text5.length <= maxChars) return text5;
3971
+ function clipTextForContext(text6, maxChars) {
3972
+ if (text6.length <= maxChars) return text6;
3882
3973
  const marker = `
3883
3974
 
3884
- [... ${text5.length} chars clipped from oversized context item ...]
3975
+ [... ${text6.length} chars clipped from oversized context item ...]
3885
3976
 
3886
3977
  `;
3887
3978
  const edge = Math.max(1, Math.floor((maxChars - marker.length) / 2));
3888
- return `${text5.slice(0, edge)}${marker}${text5.slice(-edge)}`;
3979
+ return `${text6.slice(0, edge)}${marker}${text6.slice(-edge)}`;
3889
3980
  }
3890
3981
  function clipLargeTextParts(params, maxCharsPerPart) {
3891
3982
  const messages = params.messages.map((msg) => {
@@ -3897,9 +3988,9 @@ function clipLargeTextParts(params, maxCharsPerPart) {
3897
3988
  ...msg,
3898
3989
  content: msg.content.map((part) => {
3899
3990
  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) };
3991
+ const p16 = part;
3992
+ if (typeof p16.text !== "string") return part;
3993
+ return { ...p16, text: clipTextForContext(p16.text, maxCharsPerPart) };
3903
3994
  })
3904
3995
  };
3905
3996
  });
@@ -3933,7 +4024,7 @@ var COMPACTION_PROMPT_MARKER = "You are performing a CONTEXT CHECKPOINT COMPACTI
3933
4024
  function inputItemText(content) {
3934
4025
  if (typeof content === "string") return content;
3935
4026
  if (!Array.isArray(content)) return "";
3936
- return content.map((p15) => p15 && typeof p15 === "object" && typeof p15.text === "string" ? p15.text : "").join("");
4027
+ return content.map((p16) => p16 && typeof p16 === "object" && typeof p16.text === "string" ? p16.text : "").join("");
3937
4028
  }
3938
4029
  function isLikelyCodexCompactionRequest(body) {
3939
4030
  if (!Array.isArray(body.input)) return false;
@@ -4104,11 +4195,11 @@ async function startCodexProxy(routes, options = {}) {
4104
4195
  }));
4105
4196
  }
4106
4197
  return new Promise((resolve2, reject2) => {
4107
- const log14 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
4198
+ const log15 = debug ? makeTraceLogger(getCodexProxyDebugLogPath()) : () => {
4108
4199
  };
4109
4200
  if (debug) resetCodexBodyDumpLog();
4110
4201
  const onRejection = (reason) => {
4111
- if (debug) log14(`unhandled-rejection: ${formatUpstreamError(reason)}`);
4202
+ if (debug) log15(`unhandled-rejection: ${formatUpstreamError(reason)}`);
4112
4203
  };
4113
4204
  process.on("unhandledRejection", onRejection);
4114
4205
  const server = createServer(async (req, res) => {
@@ -4125,7 +4216,7 @@ async function startCodexProxy(routes, options = {}) {
4125
4216
  }
4126
4217
  const effectivePath = mixedPath?.suffix ?? url;
4127
4218
  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)"}`);
4219
+ 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
4220
  }
4130
4221
  if (!requireAuth && req.method === "POST") {
4131
4222
  const origin = req.headers.origin;
@@ -4210,7 +4301,7 @@ async function startCodexProxy(routes, options = {}) {
4210
4301
  rawBody = await readBody(req);
4211
4302
  } catch (err) {
4212
4303
  if (debug) {
4213
- log14(`Error: failed to read/decode request body on POST ${url}: ${formatUpstreamError(err)} content-encoding=${req.headers["content-encoding"] ?? "(none)"}`);
4304
+ log15(`Error: failed to read/decode request body on POST ${url}: ${formatUpstreamError(err)} content-encoding=${req.headers["content-encoding"] ?? "(none)"}`);
4214
4305
  }
4215
4306
  sendJson(res, 400, { error: { message: "Invalid request body", type: "invalid_request_error" } });
4216
4307
  return;
@@ -4221,7 +4312,7 @@ async function startCodexProxy(routes, options = {}) {
4221
4312
  } catch (err) {
4222
4313
  if (debug) {
4223
4314
  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))}`);
4315
+ log15(`Error: Invalid JSON body on POST ${url}: ${formatUpstreamError(err)} headers=${headers} rawBody=${JSON.stringify(rawBody.slice(0, 2e3))}`);
4225
4316
  }
4226
4317
  sendJson(res, 400, { error: { message: "Invalid JSON body", type: "invalid_request_error" } });
4227
4318
  return;
@@ -4231,7 +4322,7 @@ async function startCodexProxy(routes, options = {}) {
4231
4322
  const inputItems = Array.isArray(body.input) ? body.input.length : typeof body.input === "string" ? 1 : 0;
4232
4323
  const tools = Array.isArray(body.tools) ? body.tools : [];
4233
4324
  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"}]`);
4325
+ log15(`request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${rawBody.length} tools=[${toolNames || "none"}]`);
4235
4326
  appendCodexBodyDump({
4236
4327
  ts: (/* @__PURE__ */ new Date()).toISOString(),
4237
4328
  transport: "http",
@@ -4245,14 +4336,14 @@ async function startCodexProxy(routes, options = {}) {
4245
4336
  for (const t of mcpTools) {
4246
4337
  const mt = t;
4247
4338
  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}`);
4339
+ log15(` mcp-tool: name=${mt.name} type=${mt.type} desc=${JSON.stringify(String(mt.description ?? "")).slice(0, 120)}${subTools}`);
4249
4340
  }
4250
4341
  }
4251
4342
  const modelId = String(body.model ?? "");
4252
4343
  const markedSubagent = Boolean(mixedNative && isCodexSubagentRequest(body, req.headers));
4253
4344
  const subagentRoute = mixedNative && markedSubagent ? resolveCodexSubagentRoute(routes, mixedNative.subagentRouteModelId, body, req.headers) : void 0;
4254
4345
  if (debug && markedSubagent) {
4255
- log14(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4346
+ log15(`subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4256
4347
  }
4257
4348
  if (mixedNative && markedSubagent && !subagentRoute) {
4258
4349
  audit({ transport: "http", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
@@ -4330,12 +4421,12 @@ async function startCodexProxy(routes, options = {}) {
4330
4421
  const fallbackLm = fallbackRoute ? models.get(fallbackRoute.modelId) : void 0;
4331
4422
  if (fallbackRoute && fallbackLm) {
4332
4423
  if (debug) {
4333
- log14(`resolveModel fallback: requested="${modelId}" \u2192 ${fallbackRoute.modelId}`);
4424
+ log15(`resolveModel fallback: requested="${modelId}" \u2192 ${fallbackRoute.modelId}`);
4334
4425
  }
4335
4426
  resolved = { route: fallbackRoute, languageModel: fallbackLm };
4336
4427
  } else {
4337
4428
  if (debug) {
4338
- log14(`resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
4429
+ log15(`resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
4339
4430
  }
4340
4431
  sendJson(res, 404, { error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } });
4341
4432
  return;
@@ -4384,20 +4475,20 @@ async function startCodexProxy(routes, options = {}) {
4384
4475
  const before = params.messages.length;
4385
4476
  const estimatedChars = estimateCodexRequestChars(params);
4386
4477
  const compaction = isLikelyCodexCompactionRequest(body);
4387
- if (debug) log14(`context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before}`);
4478
+ if (debug) log15(`context check: model=${route.modelId} window=${route.contextWindow} chars=${estimatedChars} compaction=${compaction ? "yes" : "no"} messages=${before}`);
4388
4479
  params = protectCodexCompactionParams(body, params, route.contextWindow);
4389
4480
  if (debug && params.messages.length < before) {
4390
- log14(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
4481
+ log15(`context trim: model=${route.modelId} window=${route.contextWindow} kept=${params.messages.length}/${before} messages`);
4391
4482
  }
4392
4483
  }
4393
4484
  const v2Compaction = isCodexV2CompactionRequest(body);
4394
4485
  if (v2Compaction) {
4395
4486
  params = appendCompactionInstruction(params);
4396
- if (debug) log14(`compaction v2: synthesizing single compaction item for model=${route.modelId}`);
4487
+ if (debug) log15(`compaction v2: synthesizing single compaction item for model=${route.modelId}`);
4397
4488
  }
4398
4489
  if (debug) {
4399
4490
  const effort = body.reasoning?.effort;
4400
- log14(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
4491
+ log15(`model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
4401
4492
  }
4402
4493
  if (body.stream) {
4403
4494
  res.writeHead(200, {
@@ -4427,11 +4518,11 @@ async function startCodexProxy(routes, options = {}) {
4427
4518
  await streamResponsesResponse(languageModel, params, modelId, write, (summary) => {
4428
4519
  if (debug) {
4429
4520
  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)}`);
4521
+ 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
4522
  }
4432
4523
  }, (progress) => {
4433
4524
  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)}`);
4525
+ log15(`response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
4435
4526
  }
4436
4527
  });
4437
4528
  audit({
@@ -4459,7 +4550,7 @@ async function startCodexProxy(routes, options = {}) {
4459
4550
  outcome: "error",
4460
4551
  status
4461
4552
  });
4462
- if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4553
+ if (debug) log15(`sdk error: ${route.modelId}: ${msg}`);
4463
4554
  if (status === 429) {
4464
4555
  writeResponsesRateLimitStream(modelId, msg, write);
4465
4556
  } else {
@@ -4505,7 +4596,7 @@ async function startCodexProxy(routes, options = {}) {
4505
4596
  outcome: "error",
4506
4597
  status
4507
4598
  });
4508
- if (debug) log14(`sdk error: ${route.modelId}: ${msg}`);
4599
+ if (debug) log15(`sdk error: ${route.modelId}: ${msg}`);
4509
4600
  if (status === 429) {
4510
4601
  sendJson(res, 200, responsesRateLimitBody(modelId, msg));
4511
4602
  } else {
@@ -4515,7 +4606,7 @@ async function startCodexProxy(routes, options = {}) {
4515
4606
  }
4516
4607
  } catch (err) {
4517
4608
  const msg = formatUpstreamError(err);
4518
- log14(`handler error: ${msg}`);
4609
+ log15(`handler error: ${msg}`);
4519
4610
  sendJson(res, 500, { error: { message: msg, type: "api_error" } });
4520
4611
  }
4521
4612
  return;
@@ -4533,6 +4624,7 @@ async function startCodexProxy(routes, options = {}) {
4533
4624
  if (buf.length < 2) return null;
4534
4625
  const b0 = buf[0];
4535
4626
  const b1 = buf[1];
4627
+ const fin = (b0 & 128) !== 0;
4536
4628
  const masked = (b1 & 128) !== 0;
4537
4629
  let payloadLen = b1 & 127;
4538
4630
  let offset = 2;
@@ -4543,11 +4635,11 @@ async function startCodexProxy(routes, options = {}) {
4543
4635
  } else if (payloadLen === 127) {
4544
4636
  if (buf.length < 10) return null;
4545
4637
  const declaredLength = buf.readBigUInt64BE(2);
4546
- if (declaredLength > BigInt(MAX_CODEX_REQUEST_BYTES)) return { text: "", complete: true, opcode: -1 };
4638
+ if (declaredLength > BigInt(MAX_CODEX_REQUEST_BYTES)) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: buf.length };
4547
4639
  payloadLen = Number(declaredLength);
4548
4640
  offset = 10;
4549
4641
  }
4550
- if (payloadLen > MAX_CODEX_REQUEST_BYTES) return { text: "", complete: true, opcode: -1 };
4642
+ if (payloadLen > MAX_CODEX_REQUEST_BYTES) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: buf.length };
4551
4643
  const maskLen = masked ? 4 : 0;
4552
4644
  if (buf.length < offset + maskLen + payloadLen) return null;
4553
4645
  const mask = masked ? buf.slice(offset, offset + 4) : null;
@@ -4557,11 +4649,11 @@ async function startCodexProxy(routes, options = {}) {
4557
4649
  payload[i] = buf[offset + i] ^ (mask ? mask[i % 4] : 0);
4558
4650
  }
4559
4651
  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 };
4652
+ if (![0, 1, 8, 9, 10].includes(opcode)) return { payload: Buffer.alloc(0), complete: true, opcode: -1, fin, consumed: offset + payloadLen };
4653
+ return { payload, complete: true, opcode, fin, consumed: offset + payloadLen };
4562
4654
  }
4563
- function wsEncodeTextFrame(text5) {
4564
- const payload = Buffer.from(text5, "utf8");
4655
+ function wsEncodeTextFrame(text6) {
4656
+ const payload = Buffer.from(text6, "utf8");
4565
4657
  const len = payload.length;
4566
4658
  let header;
4567
4659
  if (len < 126) {
@@ -4629,6 +4721,7 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4629
4721
  `
4630
4722
  );
4631
4723
  let frameBuf = Buffer.alloc(0);
4724
+ let fragmentedText;
4632
4725
  let externalActive = false;
4633
4726
  let nativeActive = false;
4634
4727
  let nativeUpstream;
@@ -4693,397 +4786,425 @@ Sec-WebSocket-Accept: ${wsAcceptKey(clientKey)}\r
4693
4786
  };
4694
4787
  const onData = (chunk) => {
4695
4788
  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
- `);
4789
+ while (!socketClosing) {
4790
+ const frame = wsDecodeFrame(frameBuf);
4791
+ if (!frame) return;
4792
+ frameBuf = frameBuf.slice(frame.consumed);
4793
+ if (frame.opcode === 9) {
4794
+ if (!frame.fin || frame.payload.length > 125) {
4795
+ socket.write(wsCloseFrame(1002));
4796
+ socket.end();
4797
+ return;
4798
+ }
4799
+ socket.write(wsPongFrame(frame.payload.toString("utf8")));
4800
+ continue;
4801
+ }
4802
+ if (frame.opcode === 10) continue;
4803
+ if (frame.opcode === 8) {
4731
4804
  closeSocket();
4732
4805
  return;
4733
4806
  }
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
- });
4807
+ if (frame.opcode === -1) {
4808
+ socket.write(wsCloseFrame(1009));
4809
+ socket.end();
4810
+ return;
4752
4811
  }
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)"}`);
4812
+ if (frame.opcode === 1) {
4813
+ if (fragmentedText) {
4814
+ socket.write(wsCloseFrame(1002));
4815
+ socket.end();
4816
+ return;
4817
+ }
4818
+ if (!frame.fin) {
4819
+ fragmentedText = [frame.payload];
4820
+ continue;
4821
+ }
4822
+ } else if (frame.opcode === 0) {
4823
+ if (!fragmentedText) {
4824
+ socket.write(wsCloseFrame(1002));
4825
+ socket.end();
4826
+ return;
4827
+ }
4828
+ fragmentedText.push(frame.payload);
4829
+ if (!frame.fin) continue;
4830
+ } else {
4831
+ socket.write(wsCloseFrame(1003));
4832
+ socket.end();
4833
+ return;
4834
+ }
4835
+ const frameText = frame.opcode === 0 ? Buffer.concat(fragmentedText ?? []).toString("utf8") : frame.payload.toString("utf8");
4836
+ fragmentedText = void 0;
4837
+ if (externalActive) {
4838
+ closeSocket(1008);
4839
+ return;
4759
4840
  }
4760
- if (mixedNative && markedSubagent && !subagentRoute) {
4761
- audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4762
- sendWsEvent(`event: error
4841
+ void (async () => {
4842
+ let body;
4843
+ try {
4844
+ body = JSON.parse(frameText);
4845
+ } catch {
4846
+ if (debug) log15(`WS Error: Invalid JSON body: rawBody=${JSON.stringify(frameText.slice(0, 2e3))}`);
4847
+ sendWsEvent(`event: error
4848
+ data: ${JSON.stringify({ error: { message: "Invalid JSON", type: "invalid_request_error" } })}
4849
+
4850
+ `);
4851
+ closeSocket();
4852
+ return;
4853
+ }
4854
+ if (debug) {
4855
+ const prevId = body.previous_response_id ?? null;
4856
+ const inputItems = Array.isArray(body.input) ? body.input.length : typeof body.input === "string" ? 1 : 0;
4857
+ const tools = Array.isArray(body.tools) ? body.tools : [];
4858
+ const toolNames = tools.map((t) => t && typeof t === "object" && "name" in t ? t.name : "?").join(",");
4859
+ log15(`WS request: model=${String(body.model ?? "")} previous_response_id=${prevId ?? "(none)"} input_items=${inputItems} body_bytes=${frameText.length} tools=[${toolNames || "none"}]`);
4860
+ const reasoning = body.reasoning && typeof body.reasoning === "object" ? Object.keys(body.reasoning).join(",") : typeof body.reasoning;
4861
+ const clientMetadata = body.client_metadata && typeof body.client_metadata === "object" ? Object.keys(body.client_metadata).join(",") : typeof body.client_metadata;
4862
+ 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"}]`);
4863
+ appendCodexBodyDump({
4864
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4865
+ transport: "ws",
4866
+ direction: "request",
4867
+ model: String(body.model ?? ""),
4868
+ previous_response_id: prevId,
4869
+ tools: body.tools,
4870
+ input: body.input
4871
+ });
4872
+ }
4873
+ const modelId = String(body.model ?? "");
4874
+ currentRequestModel = modelId;
4875
+ const markedSubagent = Boolean(mixedNative && isCodexSubagentRequest(body, req.headers));
4876
+ const subagentRoute = mixedNative && markedSubagent ? resolveCodexSubagentRoute(routes, mixedNative.subagentRouteModelId, body, req.headers) : void 0;
4877
+ if (debug && markedSubagent) {
4878
+ log15(`WS subagent dispatch: requested=${modelId} route=${subagentRoute?.modelId ?? "(none)"}`);
4879
+ }
4880
+ if (mixedNative && markedSubagent && !subagentRoute) {
4881
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "relay-subagent", phase: "complete", outcome: "error", status: 503 });
4882
+ sendWsEvent(`event: error
4763
4883
  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
- } })}
4884
+ message: "Codex marked this request as a Sub-agent, but no configured Codex Sub-agent route is available.",
4885
+ type: "service_unavailable"
4886
+ } })}
4767
4887
 
4768
4888
  `);
4769
- closeSocket();
4770
- return;
4771
- }
4772
- if (mixedNative) {
4773
- if (!markedSubagent) {
4774
- const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4775
- if (dispatch.kind === "unknown") {
4776
- audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4777
- sendWsEvent(`event: error
4889
+ closeSocket();
4890
+ return;
4891
+ }
4892
+ if (mixedNative) {
4893
+ if (!markedSubagent) {
4894
+ const dispatch = classifyCodexDispatch(modelId, routes, mixedNative.nativeModelIds);
4895
+ if (dispatch.kind === "unknown") {
4896
+ audit({ transport: "ws", requestedModel: modelId, dispatch: "unknown", phase: "complete", outcome: "error", status: 404 });
4897
+ sendWsEvent(`event: error
4778
4898
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}`, type: "invalid_request_error" } })}
4779
4899
 
4780
4900
  `);
4781
- closeSocket();
4782
- return;
4783
- }
4784
- if (dispatch.kind === "native") {
4785
- audit({
4786
- transport: "ws",
4787
- requestedModel: modelId,
4788
- dispatch: "native",
4789
- phase: "dispatch",
4790
- provider: "openai-native",
4791
- routeModel: modelId,
4792
- upstreamModel: modelId
4793
- });
4794
- const nativeBody = prepareNativeCodexBody(body);
4795
- if (debug && nativeBody !== body) {
4796
- log14(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4797
- }
4798
- if (nativeActive && nativeUpstream) {
4799
- if (nativeUpstream.readyState === WebSocket.OPEN) {
4800
- if (debug) log14(`WS native forwarding next turn: model=${modelId}`);
4801
- nativeSendTurn?.(nativeBody, modelId);
4802
- } else if (debug) {
4803
- log14(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4804
- }
4901
+ closeSocket();
4805
4902
  return;
4806
4903
  }
4807
- const wsTarget = mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "")}/responses` : void 0;
4808
- const target = nativeResponsesWebSocketOptions({ headers: req.headers, wsUrl: wsTarget });
4809
- let upstream;
4810
- let nativeOpened = false;
4811
- let nativeCompleted = false;
4812
- let nativeTurnModelId = modelId;
4813
- let nativeFrameCount = 0;
4814
- let finished = false;
4815
- let connectTimer;
4816
- let firstFrameTimer;
4817
- const clearTimers = () => {
4818
- if (connectTimer) clearTimeout(connectTimer);
4819
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
4820
- };
4821
- const sendNativeError = (message) => {
4822
- if (socket.destroyed) return;
4823
- socket.write(wsEncodeTextFrame(JSON.stringify({
4824
- type: "error",
4825
- error: { type: "upstream_error", message }
4826
- })));
4827
- };
4828
- const closeBoth = (message, closeCode = 1011) => {
4829
- if (finished) return;
4830
- finished = true;
4831
- nativeActive = false;
4832
- nativeSendTurn = void 0;
4833
- if (nativeUpstream === upstream) nativeUpstream = void 0;
4834
- clearTimers();
4835
- if (debug && message) {
4836
- log14(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4837
- }
4838
- if (message && !nativeCompleted) {
4839
- audit({
4840
- transport: "ws",
4841
- requestedModel: nativeTurnModelId,
4842
- dispatch: "native",
4843
- phase: "complete",
4844
- provider: "openai-native",
4845
- routeModel: nativeTurnModelId,
4846
- upstreamModel: nativeTurnModelId,
4847
- outcome: "error",
4848
- status: "upstream-failed"
4849
- });
4850
- }
4851
- if (message && !nativeCompleted) sendNativeError(message);
4852
- try {
4853
- upstream?.close();
4854
- } catch {
4904
+ if (dispatch.kind === "native") {
4905
+ audit({
4906
+ transport: "ws",
4907
+ requestedModel: modelId,
4908
+ dispatch: "native",
4909
+ phase: "dispatch",
4910
+ provider: "openai-native",
4911
+ routeModel: modelId,
4912
+ upstreamModel: modelId
4913
+ });
4914
+ const nativeBody = prepareNativeCodexBody(body);
4915
+ if (debug && nativeBody !== body) {
4916
+ log15(`WS native history normalized: model=${modelId} converted Relay compaction for native verification`);
4855
4917
  }
4856
- closeSocket(closeCode);
4857
- };
4858
- const sendNativeTurn = (turnBody, turnModelId) => {
4859
- if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4860
- if (debug) log14(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4918
+ if (nativeActive && nativeUpstream) {
4919
+ if (nativeUpstream.readyState === WebSocket.OPEN) {
4920
+ if (debug) log15(`WS native forwarding next turn: model=${modelId}`);
4921
+ nativeSendTurn?.(nativeBody, modelId);
4922
+ } else if (debug) {
4923
+ log15(`WS native cannot forward next turn: upstream_state=${nativeUpstream.readyState}`);
4924
+ }
4861
4925
  return;
4862
4926
  }
4863
- nativeTurnModelId = turnModelId;
4864
- nativeCompleted = false;
4865
- if (firstFrameTimer) clearTimeout(firstFrameTimer);
4866
- upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4867
- firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4868
- };
4869
- try {
4870
- if (debug) {
4871
- log14(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4872
- }
4873
- upstream = new WebSocket(target.url, { headers: target.headers });
4874
- nativeUpstream = upstream;
4875
- nativeSendTurn = sendNativeTurn;
4876
- nativeActive = true;
4877
- connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
4878
- upstream.once("open", () => {
4879
- nativeOpened = true;
4927
+ const wsTarget = mixedNative.nativeBaseUrl ? `${mixedNative.nativeBaseUrl.replace(/^http:/, "ws:").replace(/^https:/, "wss:").replace(/\/$/, "")}/responses` : void 0;
4928
+ const target = nativeResponsesWebSocketOptions({ headers: req.headers, wsUrl: wsTarget });
4929
+ let upstream;
4930
+ let nativeOpened = false;
4931
+ let nativeCompleted = false;
4932
+ let nativeTurnModelId = modelId;
4933
+ let nativeFrameCount = 0;
4934
+ let finished = false;
4935
+ let connectTimer;
4936
+ let firstFrameTimer;
4937
+ const clearTimers = () => {
4880
4938
  if (connectTimer) clearTimeout(connectTimer);
4881
- if (debug) log14(`WS native upstream open: model=${modelId}`);
4882
- sendNativeTurn(nativeBody, modelId);
4883
- });
4884
- upstream.once("unexpected-response", (_request, response) => {
4885
- if (debug) log14(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
4886
- response.resume();
4887
- closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
4888
- });
4889
- upstream.on("message", (data) => {
4890
- if (socket.destroyed) return;
4891
- nativeFrameCount += 1;
4892
4939
  if (firstFrameTimer) clearTimeout(firstFrameTimer);
4893
- const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
4894
- let eventType = "non-json";
4895
- try {
4896
- const parsed = JSON.parse(text5);
4897
- if (typeof parsed.type === "string") eventType = parsed.type;
4898
- if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
4899
- nativeCompleted = true;
4900
- audit({
4901
- transport: "ws",
4902
- requestedModel: modelId,
4903
- dispatch: "native",
4904
- phase: "complete",
4905
- provider: "openai-native",
4906
- routeModel: modelId,
4907
- upstreamModel: modelId,
4908
- outcome: eventType === "response.completed" ? "ok" : "error",
4909
- status: eventType
4910
- });
4911
- }
4912
- } catch {
4913
- }
4914
- if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
4915
- log14(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text5.length}`);
4916
- }
4917
- socket.write(wsEncodeTextFrame(text5));
4918
- });
4919
- upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
4920
- upstream.once("close", (code, reason) => {
4921
- 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}`);
4923
- if (nativeUpstream === upstream) nativeUpstream = void 0;
4924
- nativeActive = false;
4925
- if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
4926
- });
4927
- socket.once("close", () => {
4928
- if (debug) log14(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
4940
+ };
4941
+ const sendNativeError = (message) => {
4942
+ if (socket.destroyed) return;
4943
+ socket.write(wsEncodeTextFrame(JSON.stringify({
4944
+ type: "error",
4945
+ error: { type: "upstream_error", message }
4946
+ })));
4947
+ };
4948
+ const closeBoth = (message, closeCode = 1011) => {
4949
+ if (finished) return;
4929
4950
  finished = true;
4930
4951
  nativeActive = false;
4931
4952
  nativeSendTurn = void 0;
4932
4953
  if (nativeUpstream === upstream) nativeUpstream = void 0;
4933
4954
  clearTimers();
4955
+ if (debug && message) {
4956
+ log15(`WS native upstream failed: model=${nativeTurnModelId} opened=${nativeOpened} frames=${nativeFrameCount} message=${message}`);
4957
+ }
4958
+ if (message && !nativeCompleted) {
4959
+ audit({
4960
+ transport: "ws",
4961
+ requestedModel: nativeTurnModelId,
4962
+ dispatch: "native",
4963
+ phase: "complete",
4964
+ provider: "openai-native",
4965
+ routeModel: nativeTurnModelId,
4966
+ upstreamModel: nativeTurnModelId,
4967
+ outcome: "error",
4968
+ status: "upstream-failed"
4969
+ });
4970
+ }
4971
+ if (message && !nativeCompleted) sendNativeError(message);
4934
4972
  try {
4935
4973
  upstream?.close();
4936
4974
  } catch {
4937
4975
  }
4938
- });
4939
- } catch (err) {
4940
- closeBoth(`Native Codex WebSocket setup failed: ${err instanceof Error ? err.message : String(err)}`);
4976
+ closeSocket(closeCode);
4977
+ };
4978
+ const sendNativeTurn = (turnBody, turnModelId) => {
4979
+ if (!upstream || upstream.readyState !== WebSocket.OPEN) {
4980
+ if (debug) log15(`WS native cannot send turn: model=${turnModelId} upstream_state=${upstream?.readyState ?? "missing"}`);
4981
+ return;
4982
+ }
4983
+ nativeTurnModelId = turnModelId;
4984
+ nativeCompleted = false;
4985
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
4986
+ upstream.send(JSON.stringify({ type: "response.create", ...turnBody }));
4987
+ firstFrameTimer = setTimeout(() => closeBoth("Native Codex WebSocket response timed out"), 6e4);
4988
+ };
4989
+ try {
4990
+ if (debug) {
4991
+ log15(`WS native connecting: model=${modelId} url=${target.url} headers=[${Object.keys(target.headers).join(",")}]`);
4992
+ }
4993
+ upstream = new WebSocket(target.url, { headers: target.headers });
4994
+ nativeUpstream = upstream;
4995
+ nativeSendTurn = sendNativeTurn;
4996
+ nativeActive = true;
4997
+ connectTimer = setTimeout(() => closeBoth("Native Codex WebSocket connection timed out"), 15e3);
4998
+ upstream.once("open", () => {
4999
+ nativeOpened = true;
5000
+ if (connectTimer) clearTimeout(connectTimer);
5001
+ if (debug) log15(`WS native upstream open: model=${modelId}`);
5002
+ sendNativeTurn(nativeBody, modelId);
5003
+ });
5004
+ upstream.once("unexpected-response", (_request, response) => {
5005
+ if (debug) log15(`WS native upstream HTTP rejection: model=${modelId} status=${response.statusCode}`);
5006
+ response.resume();
5007
+ closeBoth(`Native Codex WebSocket rejected (${response.statusCode})`);
5008
+ });
5009
+ upstream.on("message", (data) => {
5010
+ if (socket.destroyed) return;
5011
+ nativeFrameCount += 1;
5012
+ if (firstFrameTimer) clearTimeout(firstFrameTimer);
5013
+ const text6 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
5014
+ let eventType = "non-json";
5015
+ try {
5016
+ const parsed = JSON.parse(text6);
5017
+ if (typeof parsed.type === "string") eventType = parsed.type;
5018
+ if (eventType === "response.completed" || eventType === "response.failed" || eventType === "response.incomplete") {
5019
+ nativeCompleted = true;
5020
+ audit({
5021
+ transport: "ws",
5022
+ requestedModel: modelId,
5023
+ dispatch: "native",
5024
+ phase: "complete",
5025
+ provider: "openai-native",
5026
+ routeModel: modelId,
5027
+ upstreamModel: modelId,
5028
+ outcome: eventType === "response.completed" ? "ok" : "error",
5029
+ status: eventType
5030
+ });
5031
+ }
5032
+ } catch {
5033
+ }
5034
+ if (debug && (nativeFrameCount <= 3 || nativeCompleted || eventType === "error" || nativeFrameCount % 25 === 0)) {
5035
+ log15(`WS native frame#${nativeFrameCount}: model=${modelId} type=${eventType} bytes=${text6.length}`);
5036
+ }
5037
+ socket.write(wsEncodeTextFrame(text6));
5038
+ });
5039
+ upstream.once("error", (err) => closeBoth(`Native Codex WebSocket error: ${err.message}`));
5040
+ upstream.once("close", (code, reason) => {
5041
+ const detail = reason?.length ? ` reason=${reason.toString("utf8").slice(0, 200)}` : "";
5042
+ if (debug) log15(`WS native upstream close: model=${modelId} code=${code}${detail} frames=${nativeFrameCount}`);
5043
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
5044
+ nativeActive = false;
5045
+ if (!finished) closeBoth(nativeCompleted ? void 0 : `Native Codex WebSocket closed before completion (${code})`);
5046
+ });
5047
+ socket.once("close", () => {
5048
+ if (debug) log15(`WS native downstream close: model=${modelId} frames=${nativeFrameCount} completed=${nativeCompleted}`);
5049
+ finished = true;
5050
+ nativeActive = false;
5051
+ nativeSendTurn = void 0;
5052
+ if (nativeUpstream === upstream) nativeUpstream = void 0;
5053
+ clearTimers();
5054
+ try {
5055
+ upstream?.close();
5056
+ } catch {
5057
+ }
5058
+ });
5059
+ } catch (err) {
5060
+ closeBoth(`Native Codex WebSocket setup failed: ${err instanceof Error ? err.message : String(err)}`);
5061
+ }
5062
+ return;
4941
5063
  }
4942
- return;
4943
5064
  }
4944
5065
  }
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
5066
+ externalActive = true;
5067
+ let resolved = subagentRoute ? resolveModel(routes, models, subagentRoute.modelId) : resolveModel(routes, models, modelId);
5068
+ if (!resolved) {
5069
+ const fb = routes[0];
5070
+ const fbLm = fb ? models.get(fb.modelId) : void 0;
5071
+ if (fb && fbLm) {
5072
+ if (debug) log15(`WS resolveModel fallback: requested="${modelId}" \u2192 ${fb.modelId}`);
5073
+ resolved = { route: fb, languageModel: fbLm };
5074
+ } else {
5075
+ if (debug) log15(`WS resolveModel failed: requested="${modelId}" known=[${routes.map((r) => r.modelId).join(", ")}]`);
5076
+ sendWsEvent(`event: error
4957
5077
  data: ${JSON.stringify({ error: { message: `Unknown model: ${modelId}` } })}
4958
5078
 
4959
5079
  `);
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);
5080
+ closeSocket();
5081
+ return;
5050
5082
  }
5051
- rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
5052
5083
  }
5084
+ const { route, languageModel } = resolved;
5085
+ const relayDispatch = markedSubagent ? "relay-subagent" : "relay";
5053
5086
  audit({
5054
5087
  transport: "ws",
5055
5088
  requestedModel: modelId,
5056
5089
  dispatch: relayDispatch,
5057
- phase: "complete",
5090
+ phase: "dispatch",
5058
5091
  provider: route.providerId ?? "relay",
5059
5092
  routeModel: route.modelId,
5060
- upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5061
- outcome: "ok",
5062
- status: "response.completed"
5093
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId
5063
5094
  });
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);
5095
+ currentExternalCompletedResponse = void 0;
5096
+ currentExternalStateInput = void 0;
5097
+ currentExternalConsumedResponseId = void 0;
5098
+ const continuation = resolveExternalContinuation(body);
5099
+ if (continuation.orphanedResponseId) {
5100
+ if (debug) log15(`WS continuation rejected: unknown previous_response_id=${continuation.orphanedResponseId}`);
5101
+ writeResponsesErrorStream(modelId, "Unknown or expired previous_response_id", sendWsEvent, 400);
5102
+ externalActive = false;
5103
+ return;
5083
5104
  }
5084
- }
5085
- externalActive = false;
5086
- })();
5105
+ try {
5106
+ const routedBody = await prepareExternalCodexBody(continuation.body, {
5107
+ relay: nativePayloadRelay,
5108
+ mixedNative,
5109
+ headers: req.headers
5110
+ });
5111
+ const requestHeaders = openCodeGoHeaders(
5112
+ route.providerId,
5113
+ route.baseURL,
5114
+ extractConversationId(req.headers, routedBody),
5115
+ route.headers
5116
+ );
5117
+ currentExternalStateInput = responsesInputItems(routedBody.input);
5118
+ currentExternalConsumedResponseId = continuation.consumedResponseId;
5119
+ let params = applyClaudeCodeOAuthIdentity(route, applyExternalCodexRuntimeIdentity(translateResponsesRequest(
5120
+ routedBody,
5121
+ route.npm,
5122
+ {
5123
+ providerId: route.providerId,
5124
+ apiBaseUrl: route.baseURL,
5125
+ supportedParameters: route.supportedParameters,
5126
+ reasoning: route.reasoning,
5127
+ interleavedReasoningField: route.interleavedReasoningField,
5128
+ upstreamModelId: route.upstreamModelId
5129
+ },
5130
+ {
5131
+ maxTools: maxToolsForNpm(route.npm),
5132
+ ...requestHeaders ? { requestHeaders } : {}
5133
+ }
5134
+ ), route));
5135
+ if (route.contextWindow && route.contextWindow > 0) {
5136
+ const before = params.messages.length;
5137
+ const estimatedChars = estimateCodexRequestChars(params);
5138
+ const compaction = isLikelyCodexCompactionRequest(body);
5139
+ 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}`);
5140
+ params = protectCodexCompactionParams(body, params, route.contextWindow);
5141
+ if (debug && params.messages.length < before) {
5142
+ 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}`);
5143
+ }
5144
+ }
5145
+ const v2Compaction = isCodexV2CompactionRequest(body);
5146
+ if (v2Compaction) {
5147
+ params = appendCompactionInstruction(params);
5148
+ if (debug) log15(`WS compaction v2: synthesizing single compaction item for model=${route.modelId}`);
5149
+ }
5150
+ if (debug) {
5151
+ const effort = body.reasoning?.effort;
5152
+ log15(`WS model=${route.modelId} effort=${effort ?? "(none)"} providerOptions=${JSON.stringify(params.providerOptions)}`);
5153
+ }
5154
+ if (v2Compaction) {
5155
+ await streamCompactionResponse(languageModel, params, modelId, sendWsEvent);
5156
+ } else
5157
+ await streamResponsesResponse(languageModel, params, modelId, sendWsEvent, (summary) => {
5158
+ if (debug) {
5159
+ const failure = `${summary.aborted ? " aborted=yes" : ""}${summary.errorMessage ? ` error=${JSON.stringify(summary.errorMessage)}` : ""}`;
5160
+ 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)}`);
5161
+ }
5162
+ }, (progress) => {
5163
+ if (debug) {
5164
+ log15(`WS response progress: model=${route.modelId} elapsedMs=${progress.elapsedMs} reasoningChars=${progress.reasoningChars} textChars=${progress.textChars} toolCalls=${progress.toolCallCount} reasoningTail=${JSON.stringify(progress.reasoningTail)}`);
5165
+ }
5166
+ });
5167
+ if (currentExternalCompletedResponse && currentExternalStateInput) {
5168
+ if (currentExternalConsumedResponseId) {
5169
+ externalResponseStates.delete(currentExternalConsumedResponseId);
5170
+ }
5171
+ rememberExternalResponse(currentExternalCompletedResponse, currentExternalStateInput);
5172
+ }
5173
+ audit({
5174
+ transport: "ws",
5175
+ requestedModel: modelId,
5176
+ dispatch: relayDispatch,
5177
+ phase: "complete",
5178
+ provider: route.providerId ?? "relay",
5179
+ routeModel: route.modelId,
5180
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5181
+ outcome: "ok",
5182
+ status: "response.completed"
5183
+ });
5184
+ } catch (err) {
5185
+ const msg = formatUpstreamError(err);
5186
+ const status = upstreamHttpStatus(err, msg);
5187
+ audit({
5188
+ transport: "ws",
5189
+ requestedModel: modelId,
5190
+ dispatch: relayDispatch,
5191
+ phase: "complete",
5192
+ provider: route.providerId ?? "relay",
5193
+ routeModel: route.modelId,
5194
+ upstreamModel: route.auditUpstreamModelId ?? route.upstreamModelId,
5195
+ outcome: "error",
5196
+ status
5197
+ });
5198
+ if (debug) log15(`WS sdk error: ${route.modelId}: ${msg}`);
5199
+ if (status === 429) {
5200
+ writeResponsesRateLimitStream(modelId, msg, sendWsEvent);
5201
+ } else {
5202
+ writeResponsesErrorStream(modelId, msg, sendWsEvent, status);
5203
+ }
5204
+ }
5205
+ externalActive = false;
5206
+ })();
5207
+ }
5087
5208
  };
5088
5209
  socket.on("error", () => socket.destroy());
5089
5210
  socket.once("close", () => externalResponseStates.clear());
@@ -5242,7 +5363,7 @@ function restoreCodexOverlay(env = process.env) {
5242
5363
  return removed;
5243
5364
  }
5244
5365
  function remainingOverlayPaths(env = process.env) {
5245
- return ownedOverlayPaths(env).filter((p15) => existsSync3(p15));
5366
+ return ownedOverlayPaths(env).filter((p16) => existsSync3(p16));
5246
5367
  }
5247
5368
  function recoverInterruptedCodexSession(env = process.env) {
5248
5369
  const before = remainingOverlayPaths(env);
@@ -5461,7 +5582,7 @@ function launchCodex(modelId, env, extraArgs) {
5461
5582
 
5462
5583
  // src/codex/prompts.ts
5463
5584
  import pc5 from "picocolors";
5464
- import * as p6 from "@clack/prompts";
5585
+ import * as p7 from "@clack/prompts";
5465
5586
  function codexLaunchModeOptions() {
5466
5587
  return [
5467
5588
  {
@@ -5477,13 +5598,13 @@ function codexLaunchModeOptions() {
5477
5598
  ];
5478
5599
  }
5479
5600
  async function pickCodexLaunchMode() {
5480
- const choice = await p6.select({
5601
+ const choice = await p7.select({
5481
5602
  message: "Load native Codex models alongside Relay models?",
5482
5603
  options: codexLaunchModeOptions(),
5483
5604
  initialValue: "relay-only"
5484
5605
  });
5485
- if (p6.isCancel(choice)) {
5486
- p6.cancel("Cancelled.");
5606
+ if (p7.isCancel(choice)) {
5607
+ p7.cancel("Cancelled.");
5487
5608
  return null;
5488
5609
  }
5489
5610
  return choice;
@@ -5499,13 +5620,13 @@ async function pickCodexProvider(providers, prefs, hasFavorites = false, initial
5499
5620
  });
5500
5621
  }
5501
5622
  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({
5623
+ const chosen = await p7.select({
5503
5624
  message: "Which provider for Codex?",
5504
5625
  options,
5505
5626
  initialValue: initial
5506
5627
  });
5507
- if (p6.isCancel(chosen)) {
5508
- p6.cancel("Cancelled.");
5628
+ if (p7.isCancel(chosen)) {
5629
+ p7.cancel("Cancelled.");
5509
5630
  return null;
5510
5631
  }
5511
5632
  if (chosen === "__favorites__") return "__favorites__";
@@ -5522,12 +5643,12 @@ async function pickCodexModel(provider, prefs) {
5522
5643
  navOption("__browse_all__", "Browse all models \u2192", `${provider.models.length} available`),
5523
5644
  navOption("__back__", "\u2190 Go back", "Select a different provider")
5524
5645
  ];
5525
- const picked = await p6.select({
5646
+ const picked = await p7.select({
5526
5647
  message: `Model for ${provider.name}?`,
5527
5648
  options,
5528
5649
  initialValue: recentModels[0].id
5529
5650
  });
5530
- if (p6.isCancel(picked) || String(picked) === "__back__") {
5651
+ if (p7.isCancel(picked) || String(picked) === "__back__") {
5531
5652
  return "back";
5532
5653
  }
5533
5654
  if (String(picked) === "__browse_all__") {
@@ -5556,12 +5677,12 @@ async function pickCodexModel(provider, prefs) {
5556
5677
  }
5557
5678
  function confirmCodexLaunch(providerName, modelLabel, modelId, route) {
5558
5679
  const via = route.tier === "direct" ? pc5.green("direct") : `${pc5.dim("via")} ${pc5.yellow("relay-ai proxy")}`;
5559
- return p6.confirm({
5680
+ return p7.confirm({
5560
5681
  message: `${confirmLaunchMessage("Codex", modelLabel, modelId, providerName)} ${pc5.dim("(")}${via}${pc5.dim(")")}`,
5561
5682
  initialValue: true
5562
5683
  }).then((answer) => {
5563
- if (p6.isCancel(answer)) {
5564
- p6.cancel("Cancelled.");
5684
+ if (p7.isCancel(answer)) {
5685
+ p7.cancel("Cancelled.");
5565
5686
  return false;
5566
5687
  }
5567
5688
  return answer;
@@ -5671,7 +5792,7 @@ function buildFavoritesAppCatalog(resolved) {
5671
5792
  }
5672
5793
 
5673
5794
  // src/codex/favorites-launch.ts
5674
- import * as p7 from "@clack/prompts";
5795
+ import * as p8 from "@clack/prompts";
5675
5796
 
5676
5797
  // src/favorites-resolver.ts
5677
5798
  async function resolveFavorite(fav, ctx) {
@@ -5748,7 +5869,7 @@ async function pickFavoriteStartingModel(compatible, favorites, agent, productLa
5748
5869
  if (provider && model) available.push({ provider, model });
5749
5870
  }
5750
5871
  if (available.length === 0) {
5751
- p7.log.warn(`No saved ${productLabel} favorites are currently available.`);
5872
+ p8.log.warn(`No saved ${productLabel} favorites are currently available.`);
5752
5873
  return "unavailable";
5753
5874
  }
5754
5875
  const favOptions = available.map((f, i) => ({
@@ -5756,13 +5877,13 @@ async function pickFavoriteStartingModel(compatible, favorites, agent, productLa
5756
5877
  label: `${f.model.name || f.model.id} \u2014 ${f.provider.name}`,
5757
5878
  hint: f.model.id
5758
5879
  }));
5759
- const pickedIdx = await p7.select({
5880
+ const pickedIdx = await p8.select({
5760
5881
  message: "Starting model?",
5761
5882
  options: favOptions,
5762
5883
  initialValue: "0"
5763
5884
  });
5764
- if (p7.isCancel(pickedIdx)) {
5765
- p7.cancel("Cancelled.");
5885
+ if (p8.isCancel(pickedIdx)) {
5886
+ p8.cancel("Cancelled.");
5766
5887
  return "cancelled";
5767
5888
  }
5768
5889
  return available[Number(pickedIdx)] ?? "unavailable";
@@ -5805,7 +5926,7 @@ function buildCodexProxyRoutesFromResolved(resolved, providersById) {
5805
5926
  };
5806
5927
  }).filter((r) => r !== void 0);
5807
5928
  if (skippedOAuth.length > 0) {
5808
- p7.log.warn(
5929
+ p8.log.warn(
5809
5930
  `Skipped ${skippedOAuth.length} OAuth favorite(s) (OAuth auth not supported in favorites catalog): ${skippedOAuth.join(", ")}`
5810
5931
  );
5811
5932
  }
@@ -5831,7 +5952,7 @@ async function resolveCodexFavorites(activeProvider, selectedModel, compatible,
5831
5952
  ctx
5832
5953
  );
5833
5954
  if (droppedFavorites.length > 0) {
5834
- p7.log.warn(
5955
+ p8.log.warn(
5835
5956
  `Skipped ${droppedFavorites.length} stale/unauthorized favorite(s): ${droppedFavorites.map((f) => `${f.providerId}:${f.modelId}`).join(", ")}`
5836
5957
  );
5837
5958
  }
@@ -6241,7 +6362,7 @@ function resolveLaunchTarget(explicit, prefs, agent) {
6241
6362
  }
6242
6363
  function findProviderAndModel(providers, target) {
6243
6364
  if (!target.providerId || !target.modelId) return null;
6244
- const provider = providers.find((p15) => p15.id === target.providerId);
6365
+ const provider = providers.find((p16) => p16.id === target.providerId);
6245
6366
  if (!provider) return null;
6246
6367
  const model = provider.models.find((m) => m.id === target.modelId);
6247
6368
  if (!model) return null;
@@ -6428,14 +6549,14 @@ function printCodexCleanupReminder(hadProxy) {
6428
6549
  if (isAgentStdoutMode()) return;
6429
6550
  const left = remainingOverlayPaths();
6430
6551
  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");
6552
+ p9.log.warn("Temporary Codex overlay files may still be on disk.");
6553
+ p9.log.info("Run: relay-ai codex --restore");
6433
6554
  return;
6434
6555
  }
6435
6556
  const parts = ["Temporary Codex profile removed."];
6436
6557
  if (hadProxy) parts.push("Local Responses proxy stopped.");
6437
6558
  parts.push("If a future session acts stuck: relay-ai codex --restore");
6438
- p8.log.info(parts.join(" "));
6559
+ p9.log.info(parts.join(" "));
6439
6560
  }
6440
6561
  function vertexEntryToLocalModel(entry) {
6441
6562
  return {
@@ -6452,26 +6573,26 @@ function vertexEntryToLocalModel(entry) {
6452
6573
  }
6453
6574
  async function runCodexVertexLaunch(passthroughArgs, trace) {
6454
6575
  if (!hasApplicationDefaultCredentials()) {
6455
- p8.log.error("Google Application Default Credentials not found.");
6456
- p8.log.info("Run: gcloud auth application-default login");
6576
+ p9.log.error("Google Application Default Credentials not found.");
6577
+ p9.log.info("Run: gcloud auth application-default login");
6457
6578
  return 1;
6458
6579
  }
6459
6580
  const config = buildVertexRuntimeConfig();
6460
6581
  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");
6582
+ p9.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
6583
+ p9.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
6463
6584
  return 1;
6464
6585
  }
6465
6586
  let selectedEntry;
6466
6587
  if (config.models.length === 1) {
6467
6588
  selectedEntry = config.models[0];
6468
6589
  } else {
6469
- const choice = await p8.select({
6590
+ const choice = await p9.select({
6470
6591
  message: "Select a Vertex AI model:",
6471
6592
  options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
6472
6593
  });
6473
- if (p8.isCancel(choice)) {
6474
- p8.cancel("Cancelled.");
6594
+ if (p9.isCancel(choice)) {
6595
+ p9.cancel("Cancelled.");
6475
6596
  return 0;
6476
6597
  }
6477
6598
  selectedEntry = choice;
@@ -6499,7 +6620,7 @@ async function runCodexVertexLaunch(passthroughArgs, trace) {
6499
6620
  const debugLogPath = getCodexProxyDebugLogPath();
6500
6621
  let proxyHandle = null;
6501
6622
  try {
6502
- p8.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
6623
+ p9.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
6503
6624
  proxyHandle = await startCodexProxy(allRoutes, { debug: trace });
6504
6625
  const proxyPort = proxyHandle.port;
6505
6626
  const catalogPath = getCatalogOutputPath("vertex");
@@ -6562,7 +6683,7 @@ async function runCodexCommand2(codexArgs, trace = false, launch = {}) {
6562
6683
  setAgentStdoutMode(agentStdout);
6563
6684
  const debugLogPath = getCodexProxyDebugLogPath();
6564
6685
  if (trace && !configOnly) {
6565
- p8.log.info(`Debug log: ${debugLogPath}`);
6686
+ p9.log.info(`Debug log: ${debugLogPath}`);
6566
6687
  }
6567
6688
  const isTty = Boolean(process.stdin.isTTY);
6568
6689
  if (launch.vertex) {
@@ -6611,10 +6732,10 @@ Error: ${launchPlan.error}
6611
6732
  if (!configOnly) {
6612
6733
  if (!agentStdout) codexCliIntro();
6613
6734
  if (interrupted.recovered && !agentStdout) {
6614
- p8.log.warn(
6735
+ p9.log.warn(
6615
6736
  "Found leftover Codex files from an interrupted session (closed terminal, crash, or force-quit)."
6616
6737
  );
6617
- p8.log.info(
6738
+ p9.log.info(
6618
6739
  `Removed ${interrupted.removedCount ?? "those"} file(s) automatically. If anything still looks wrong: relay-ai codex --restore`
6619
6740
  );
6620
6741
  }
@@ -6628,7 +6749,7 @@ Error: ${launchPlan.error}
6628
6749
  return 1;
6629
6750
  }
6630
6751
  } else {
6631
- const catalogSpinner = p8.spinner();
6752
+ const catalogSpinner = p9.spinner();
6632
6753
  catalogSpinner.start("Loading your providers...");
6633
6754
  try {
6634
6755
  catalog = await fetchProviderCatalog({ agent: "codex" });
@@ -6642,8 +6763,8 @@ Error: ${launchPlan.error}
6642
6763
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "codex");
6643
6764
  if (compatible.length === 0) {
6644
6765
  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.");
6766
+ p9.log.warn("No Codex-compatible providers in your registry.");
6767
+ p9.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
6647
6768
  }
6648
6769
  return 0;
6649
6770
  }
@@ -6656,17 +6777,17 @@ Error: ${launchPlan.error}
6656
6777
  }
6657
6778
  const favoritesActive = favorites.length > 0 && !launchPlan.skip && !mixedMode;
6658
6779
  if (favoritesActive && !configOnly) {
6659
- p8.log.info(
6780
+ p9.log.info(
6660
6781
  `Favorites mode active \u2014 Codex picker will show ${favorites.length + 1} models (1 starting + ${favorites.length} favorites).`
6661
6782
  );
6662
- p8.log.info("Edit with `relay-ai models`.");
6783
+ p9.log.info("Edit with `relay-ai models`.");
6663
6784
  }
6664
6785
  let activeProvider = compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0];
6665
6786
  let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastCodexModel) ?? activeProvider.models[0];
6666
6787
  if (!configOnly && launchPlan.skip && launchPlan.target) {
6667
6788
  const resolved = findProviderAndModel(compatible, launchPlan.target);
6668
6789
  if (!resolved) {
6669
- p8.log.error(
6790
+ p9.log.error(
6670
6791
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
6671
6792
  );
6672
6793
  return 1;
@@ -6674,7 +6795,7 @@ Error: ${launchPlan.error}
6674
6795
  activeProvider = resolved.provider;
6675
6796
  selectedModel = resolved.model;
6676
6797
  if (!agentStdout) {
6677
- p8.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
6798
+ p9.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
6678
6799
  }
6679
6800
  } else if (!configOnly) {
6680
6801
  let currentInitialProvider = prefs.lastCodexProvider && compatible.some((o) => o.id === prefs.lastCodexProvider) ? prefs.lastCodexProvider : compatible[0].id;
@@ -6722,7 +6843,7 @@ Error: ${launchPlan.error}
6722
6843
  const apiKey = await resolveLocalProviderApiKey(activeProvider);
6723
6844
  if (!apiKey) {
6724
6845
  if (!configOnly) {
6725
- p8.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id} or add an API key.`);
6846
+ p9.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id} or add an API key.`);
6726
6847
  }
6727
6848
  return 1;
6728
6849
  }
@@ -6914,7 +7035,7 @@ Mixed Codex mode is unavailable: ${err instanceof Error ? err.message : err}`));
6914
7035
  });
6915
7036
  if (configOnly) {
6916
7037
  const home = process.env["HOME"] ?? "";
6917
- const shortenPath = (p15) => home ? p15.replace(home, "~") : p15;
7038
+ const shortenPath = (p16) => home ? p16.replace(home, "~") : p16;
6918
7039
  console.log("");
6919
7040
  console.log(pc7.bold(pc7.cyan(" CONFIG PREVIEW \u2014 relay-ai codex")));
6920
7041
  console.log("");
@@ -6991,7 +7112,7 @@ Mixed Codex mode is unavailable: ${err instanceof Error ? err.message : err}`));
6991
7112
 
6992
7113
  // src/gemini.ts
6993
7114
  import pc8 from "picocolors";
6994
- import * as p10 from "@clack/prompts";
7115
+ import * as p11 from "@clack/prompts";
6995
7116
 
6996
7117
  // src/gemini/launch.ts
6997
7118
  import { spawn as spawn3 } from "child_process";
@@ -7080,7 +7201,7 @@ function launchGemini(geminiPath, modelId, env, extraArgs) {
7080
7201
  }
7081
7202
 
7082
7203
  // src/gemini/prompts.ts
7083
- import * as p9 from "@clack/prompts";
7204
+ import * as p10 from "@clack/prompts";
7084
7205
  async function pickGeminiProvider(providers, prefs, hasFavorites = false, initialProviderId) {
7085
7206
  if (providers.length === 0 && !hasFavorites) return null;
7086
7207
  const options = providers.map((lp) => providerSelectOption(lp));
@@ -7092,13 +7213,13 @@ async function pickGeminiProvider(providers, prefs, hasFavorites = false, initia
7092
7213
  });
7093
7214
  }
7094
7215
  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({
7216
+ const chosen = await p10.select({
7096
7217
  message: "Which provider for Gemini CLI?",
7097
7218
  options,
7098
7219
  initialValue: initial
7099
7220
  });
7100
- if (p9.isCancel(chosen)) {
7101
- p9.cancel("Cancelled.");
7221
+ if (p10.isCancel(chosen)) {
7222
+ p10.cancel("Cancelled.");
7102
7223
  return null;
7103
7224
  }
7104
7225
  if (chosen === "__favorites__") return "__favorites__";
@@ -7115,12 +7236,12 @@ async function pickGeminiModel(provider, prefs) {
7115
7236
  navOption("__browse_all__", "Browse all models \u2192", `${provider.models.length} available`),
7116
7237
  navOption("__back__", "\u2190 Go back", "Select a different provider")
7117
7238
  ];
7118
- const picked = await p9.select({
7239
+ const picked = await p10.select({
7119
7240
  message: `Model for ${provider.name}?`,
7120
7241
  options,
7121
7242
  initialValue: recentModels[0].id
7122
7243
  });
7123
- if (p9.isCancel(picked) || String(picked) === "__back__") {
7244
+ if (p10.isCancel(picked) || String(picked) === "__back__") {
7124
7245
  return "back";
7125
7246
  }
7126
7247
  if (String(picked) === "__browse_all__") {
@@ -7148,12 +7269,12 @@ async function pickGeminiModel(provider, prefs) {
7148
7269
  return selectedModel;
7149
7270
  }
7150
7271
  function confirmGeminiLaunch(providerName, modelLabel, modelId) {
7151
- return p9.confirm({
7272
+ return p10.confirm({
7152
7273
  message: confirmLaunchMessage("Gemini CLI", modelLabel, modelId, providerName),
7153
7274
  initialValue: true
7154
7275
  }).then((answer) => {
7155
- if (p9.isCancel(answer)) {
7156
- p9.cancel("Cancelled.");
7276
+ if (p10.isCancel(answer)) {
7277
+ p10.cancel("Cancelled.");
7157
7278
  return false;
7158
7279
  }
7159
7280
  return answer;
@@ -7167,7 +7288,7 @@ async function pickGeminiFavoriteModel(providers, favorites) {
7167
7288
  if (provider2 && model2) favList.push({ provider: provider2, model: model2 });
7168
7289
  }
7169
7290
  if (favList.length === 0) {
7170
- p9.log.warn("None of your saved favorites are available in the current registry.");
7291
+ p10.log.warn("None of your saved favorites are available in the current registry.");
7171
7292
  return null;
7172
7293
  }
7173
7294
  const options = [
@@ -7178,12 +7299,12 @@ async function pickGeminiFavoriteModel(providers, favorites) {
7178
7299
  })),
7179
7300
  { value: "__back__", label: "\u2190 Go back", hint: "Select a different provider" }
7180
7301
  ];
7181
- const picked = await p9.select({
7302
+ const picked = await p10.select({
7182
7303
  message: "Pick a favorite model for Gemini CLI:",
7183
7304
  options,
7184
7305
  initialValue: options[0].value
7185
7306
  });
7186
- if (p9.isCancel(picked) || String(picked) === "__back__") return "back";
7307
+ if (p10.isCancel(picked) || String(picked) === "__back__") return "back";
7187
7308
  const [pickedProviderId, pickedModelId] = picked.split("::");
7188
7309
  const provider = providers.find((lp) => lp.id === pickedProviderId);
7189
7310
  const model = provider?.models.find((m) => m.id === pickedModelId);
@@ -7253,13 +7374,13 @@ function mergeConsecutiveMessages2(messages) {
7253
7374
  }
7254
7375
  return merged;
7255
7376
  }
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");
7377
+ function stripGeminiIdentity(text6) {
7378
+ 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
7379
  }
7259
7380
  function translateGeminiRequest(body, options = {}) {
7260
7381
  let system;
7261
7382
  if (body.systemInstruction?.parts) {
7262
- const rawSystem = body.systemInstruction.parts.map((p15) => p15.text || "").join("\n");
7383
+ const rawSystem = body.systemInstruction.parts.map((p16) => p16.text || "").join("\n");
7263
7384
  system = stripGeminiIdentity(rawSystem).trim();
7264
7385
  }
7265
7386
  const messages = [];
@@ -7270,38 +7391,38 @@ function translateGeminiRequest(body, options = {}) {
7270
7391
  const parts = [];
7271
7392
  const toolResults = [];
7272
7393
  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>/);
7394
+ for (const p16 of turnParts) {
7395
+ if (p16.text !== void 0) {
7396
+ const text6 = stripGeminiIdentity(p16.text);
7397
+ if (text6.includes("<thinking>")) {
7398
+ const tokens = text6.split(/<thinking>([\s\S]*?)<\/thinking>/);
7278
7399
  for (let i = 0; i < tokens.length; i++) {
7279
7400
  const token = tokens[i].trim();
7280
7401
  if (!token) continue;
7281
7402
  parts.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
7282
7403
  }
7283
7404
  } else {
7284
- parts.push({ type: "text", text: text5 });
7405
+ parts.push({ type: "text", text: text6 });
7285
7406
  }
7286
- } else if (p15.inlineData) {
7407
+ } else if (p16.inlineData) {
7287
7408
  parts.push({
7288
7409
  type: "file",
7289
- data: Buffer.from(p15.inlineData.data, "base64"),
7290
- mediaType: p15.inlineData.mimeType
7410
+ data: Buffer.from(p16.inlineData.data, "base64"),
7411
+ mediaType: p16.inlineData.mimeType
7291
7412
  });
7292
- } else if (p15.functionCall) {
7413
+ } else if (p16.functionCall) {
7293
7414
  const id = "call_" + randomUUID().replace(/-/g, "");
7294
- const name = p15.functionCall.name;
7415
+ const name = p16.functionCall.name;
7295
7416
  if (!nameToIdList.has(name)) nameToIdList.set(name, []);
7296
7417
  nameToIdList.get(name).push(id);
7297
7418
  parts.push({
7298
7419
  type: "tool-call",
7299
7420
  toolCallId: id,
7300
7421
  toolName: name,
7301
- input: p15.functionCall.args || {}
7422
+ input: p16.functionCall.args || {}
7302
7423
  });
7303
- } else if (p15.functionResponse) {
7304
- const name = p15.functionResponse.name;
7424
+ } else if (p16.functionResponse) {
7425
+ const name = p16.functionResponse.name;
7305
7426
  const idList = nameToIdList.get(name) || [];
7306
7427
  const id = idList.shift() || "call_" + randomUUID().replace(/-/g, "");
7307
7428
  toolResults.push({
@@ -7310,7 +7431,7 @@ function translateGeminiRequest(body, options = {}) {
7310
7431
  toolName: name,
7311
7432
  output: {
7312
7433
  type: "text",
7313
- value: typeof p15.functionResponse.response === "string" ? p15.functionResponse.response : JSON.stringify(p15.functionResponse.response || {})
7434
+ value: typeof p16.functionResponse.response === "string" ? p16.functionResponse.response : JSON.stringify(p16.functionResponse.response || {})
7314
7435
  }
7315
7436
  });
7316
7437
  }
@@ -7467,14 +7588,14 @@ ${rawBody}`);
7467
7588
  const current = sessionRouteOverride ?? (lookupGeminiRoute(routes, requestedModel) ?? defaultRoute);
7468
7589
  const availableList = routes.map((r) => ` - ${r.aliasId} (${r.displayName})`).join("\n");
7469
7590
  const exampleId = routes.length > 1 ? routes[1].aliasId : routes[0]?.aliasId ?? "deepseek-v4";
7470
- const text5 = `Current model: ${current.displayName} (${current.aliasId})
7591
+ const text6 = `Current model: ${current.displayName} (${current.aliasId})
7471
7592
 
7472
7593
  Available models:
7473
7594
  ${availableList}
7474
7595
 
7475
7596
  \u{1F4A1} To switch models, type: .model <id>
7476
7597
  Example: .model ${exampleId}`;
7477
- sendMockGeminiResponse(res, text5, isStream, current.aliasId);
7598
+ sendMockGeminiResponse(res, text6, isStream, current.aliasId);
7478
7599
  return;
7479
7600
  }
7480
7601
  const targetRoute = lookupGeminiRoute(routes, modelCommand);
@@ -7530,9 +7651,9 @@ ${JSON.stringify(params, null, 2)}`);
7530
7651
  const toolCallBuffers = /* @__PURE__ */ new Map();
7531
7652
  let isThinking = false;
7532
7653
  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")) {
7654
+ const p16 = part;
7655
+ plog(`Stream chunk type: ${p16.type}`);
7656
+ if (isThinking && (p16.type === "tool-input-start" || p16.type === "tool-call" || p16.type === "finish")) {
7536
7657
  isThinking = false;
7537
7658
  const chunk = {
7538
7659
  candidates: [{ content: { role: "model", parts: [{ text: `
@@ -7545,34 +7666,34 @@ ${JSON.stringify(params, null, 2)}`);
7545
7666
 
7546
7667
  `);
7547
7668
  }
7548
- if (p15.type === "reasoning") {
7549
- let text5 = p15.textDelta ?? p15.text ?? "";
7669
+ if (p16.type === "reasoning") {
7670
+ let text6 = p16.textDelta ?? p16.text ?? "";
7550
7671
  if (!isThinking) {
7551
7672
  isThinking = true;
7552
- text5 = `<thinking>
7553
- ` + text5;
7673
+ text6 = `<thinking>
7674
+ ` + text6;
7554
7675
  }
7555
7676
  const chunk = {
7556
- candidates: [{ content: { role: "model", parts: [{ text: text5 }] } }],
7677
+ candidates: [{ content: { role: "model", parts: [{ text: text6 }] } }],
7557
7678
  modelVersion: route.aliasId
7558
7679
  };
7559
7680
  res.write(`data: ${JSON.stringify(chunk)}
7560
7681
 
7561
7682
  `);
7562
- } else if (p15.type === "text-delta") {
7563
- let text5 = p15.textDelta ?? p15.text ?? "";
7683
+ } else if (p16.type === "text-delta") {
7684
+ let text6 = p16.textDelta ?? p16.text ?? "";
7564
7685
  if (isThinking) {
7565
7686
  isThinking = false;
7566
- text5 = `
7687
+ text6 = `
7567
7688
  </thinking>
7568
7689
 
7569
- ` + text5;
7690
+ ` + text6;
7570
7691
  }
7571
7692
  const chunk = {
7572
7693
  candidates: [{
7573
7694
  content: {
7574
7695
  role: "model",
7575
- parts: [{ text: text5 }]
7696
+ parts: [{ text: text6 }]
7576
7697
  }
7577
7698
  }],
7578
7699
  modelVersion: route.aliasId
@@ -7580,17 +7701,17 @@ ${JSON.stringify(params, null, 2)}`);
7580
7701
  const data = `data: ${JSON.stringify(chunk)}
7581
7702
 
7582
7703
  `;
7583
- plog(`Streaming text delta: ${p15.textDelta}`);
7704
+ plog(`Streaming text delta: ${p16.textDelta}`);
7584
7705
  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;
7706
+ } else if (p16.type === "tool-input-start") {
7707
+ toolCallBuffers.set(p16.toolCallId, { name: p16.toolName, json: "" });
7708
+ } else if (p16.type === "tool-input-delta") {
7709
+ const buf = toolCallBuffers.get(p16.toolCallId);
7710
+ if (buf) buf.json += p16.delta;
7711
+ } else if (p16.type === "tool-call") {
7712
+ const buf = toolCallBuffers.get(p16.toolCallId);
7713
+ const args = buf ? JSON.parse(buf.json || "{}") : p16.input || {};
7714
+ const name = buf ? buf.name : p16.toolName;
7594
7715
  plog(`Streaming tool call: ${name} with args: ${JSON.stringify(args)}`);
7595
7716
  const chunk = {
7596
7717
  candidates: [{
@@ -7606,18 +7727,18 @@ ${JSON.stringify(params, null, 2)}`);
7606
7727
  res.write(`data: ${JSON.stringify(chunk)}
7607
7728
 
7608
7729
  `);
7609
- } else if (p15.type === "finish") {
7730
+ } else if (p16.type === "finish") {
7610
7731
  const chunk = {
7611
7732
  candidates: [{
7612
- finishReason: mapFinishReason(p15.finishReason ?? "")
7733
+ finishReason: mapFinishReason(p16.finishReason ?? "")
7613
7734
  }],
7614
7735
  usageMetadata: {
7615
- promptTokenCount: p15.totalUsage?.inputTokens || 0,
7616
- candidatesTokenCount: p15.totalUsage?.outputTokens || 0
7736
+ promptTokenCount: p16.totalUsage?.inputTokens || 0,
7737
+ candidatesTokenCount: p16.totalUsage?.outputTokens || 0
7617
7738
  },
7618
7739
  modelVersion: route.aliasId
7619
7740
  };
7620
- plog(`Stream finish. Reason: ${p15.finishReason}`);
7741
+ plog(`Stream finish. Reason: ${p16.finishReason}`);
7621
7742
  res.write(`data: ${JSON.stringify(chunk)}
7622
7743
 
7623
7744
  `);
@@ -7755,28 +7876,28 @@ function parseModelCommand(turn) {
7755
7876
  if (!turn || turn.role !== "user") return null;
7756
7877
  const parts = turn.parts || [];
7757
7878
  if (parts.length !== 1) return null;
7758
- const text5 = parts[0]?.text;
7759
- if (typeof text5 !== "string") return null;
7760
- const trimmed = text5.trim();
7879
+ const text6 = parts[0]?.text;
7880
+ if (typeof text6 !== "string") return null;
7881
+ const trimmed = text6.trim();
7761
7882
  if (!trimmed.startsWith(".model")) return null;
7762
7883
  if (trimmed === ".model") return "";
7763
7884
  if (trimmed.charAt(6) !== " ") return null;
7764
7885
  return trimmed.slice(7).trim();
7765
7886
  }
7766
- function sendMockGeminiResponse(res, text5, isStream, modelVersion) {
7887
+ function sendMockGeminiResponse(res, text6, isStream, modelVersion) {
7767
7888
  if (isStream) {
7768
7889
  res.writeHead(200, {
7769
7890
  "Content-Type": "text/event-stream",
7770
7891
  "Cache-Control": "no-cache",
7771
7892
  "Connection": "keep-alive"
7772
7893
  });
7773
- writeGeminiStreamText(res, text5, modelVersion);
7894
+ writeGeminiStreamText(res, text6, modelVersion);
7774
7895
  res.end();
7775
7896
  } else {
7776
7897
  res.writeHead(200, { "Content-Type": "application/json" });
7777
7898
  res.end(JSON.stringify({
7778
7899
  candidates: [{
7779
- content: { role: "model", parts: [{ text: text5 }] },
7900
+ content: { role: "model", parts: [{ text: text6 }] },
7780
7901
  finishReason: "STOP"
7781
7902
  }],
7782
7903
  usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0 },
@@ -7784,10 +7905,10 @@ function sendMockGeminiResponse(res, text5, isStream, modelVersion) {
7784
7905
  }));
7785
7906
  }
7786
7907
  }
7787
- function writeGeminiStreamText(res, text5, modelVersion) {
7908
+ function writeGeminiStreamText(res, text6, modelVersion) {
7788
7909
  const chunk = {
7789
7910
  candidates: [{
7790
- content: { role: "model", parts: [{ text: text5 }] },
7911
+ content: { role: "model", parts: [{ text: text6 }] },
7791
7912
  finishReason: "STOP"
7792
7913
  }],
7793
7914
  usageMetadata: { promptTokenCount: 0, candidatesTokenCount: 0 },
@@ -7944,7 +8065,7 @@ Error: ${launchPlan.error}
7944
8065
  return 1;
7945
8066
  }
7946
8067
  } else {
7947
- const catalogSpinner = p10.spinner();
8068
+ const catalogSpinner = p11.spinner();
7948
8069
  catalogSpinner.start("Loading your providers...");
7949
8070
  try {
7950
8071
  catalog = await fetchProviderCatalog({ agent: "gemini" });
@@ -7957,21 +8078,21 @@ Error: ${launchPlan.error}
7957
8078
  }
7958
8079
  const compatible = providersForTarget(providersForPicker(catalog), "gemini");
7959
8080
  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.");
8081
+ p11.log.warn("No Gemini-compatible providers in your registry.");
8082
+ p11.log.info("Add a provider with relay-ai providers add, or sign in with relay-ai providers auth openai-oauth.");
7962
8083
  return 0;
7963
8084
  }
7964
8085
  let activeProvider = compatible.find((lp) => lp.id === prefs.lastGeminiProvider) ?? compatible[0];
7965
8086
  let selectedModel = activeProvider.models.find((m) => m.id === prefs.lastGeminiModel) ?? activeProvider.models[0];
7966
8087
  if (!selectedModel) {
7967
- p10.log.error(`Provider "${activeProvider.name}" has no models available.`);
8088
+ p11.log.error(`Provider "${activeProvider.name}" has no models available.`);
7968
8089
  return 1;
7969
8090
  }
7970
8091
  ;
7971
8092
  if (launchPlan.skip && launchPlan.target) {
7972
8093
  const resolved = findProviderAndModel(compatible, launchPlan.target);
7973
8094
  if (!resolved) {
7974
- p10.log.error(
8095
+ p11.log.error(
7975
8096
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
7976
8097
  );
7977
8098
  return 1;
@@ -7979,12 +8100,12 @@ Error: ${launchPlan.error}
7979
8100
  activeProvider = resolved.provider;
7980
8101
  selectedModel = resolved.model;
7981
8102
  if (!agentStdout) {
7982
- p10.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
8103
+ p11.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
7983
8104
  }
7984
8105
  } else {
7985
8106
  if (!agentStdout) {
7986
8107
  console.log("");
7987
- p10.log.info(`Launching ${pc8.bold("Gemini CLI")} with relay-ai`);
8108
+ p11.log.info(`Launching ${pc8.bold("Gemini CLI")} with relay-ai`);
7988
8109
  }
7989
8110
  const chosenProvider = await pickGeminiProvider(
7990
8111
  compatible,
@@ -8016,7 +8137,7 @@ Error: ${launchPlan.error}
8016
8137
  recordLaunchSelection("gemini", activeProvider.id, selectedModel.id, prefs);
8017
8138
  const launchApiKey = await resolveLocalProviderApiKey(activeProvider);
8018
8139
  if (!launchApiKey?.trim()) {
8019
- p10.log.error(
8140
+ p11.log.error(
8020
8141
  `No API key found for ${activeProvider.name}. Set it with relay-ai providers add.`
8021
8142
  );
8022
8143
  return 1;
@@ -8112,14 +8233,14 @@ Error: ${launchPlan.error}
8112
8233
  oauthBackend = backendRoutes.backend;
8113
8234
  proxyHandle = await startGeminiProxy(finalRoutes, trace);
8114
8235
  } catch (err) {
8115
- p10.log.error(`Failed to start Gemini proxy: ${err instanceof Error ? err.message : String(err)}`);
8236
+ p11.log.error(`Failed to start Gemini proxy: ${err instanceof Error ? err.message : String(err)}`);
8116
8237
  oauthBackend?.handle.close();
8117
8238
  return 1;
8118
8239
  }
8119
8240
  const childEnv = prepareGeminiChildEnv(proxyHandle.port, proxyHandle.token);
8120
8241
  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.`);
8242
+ p11.log.info(`Gemini proxy started on port ${proxyHandle.port}`);
8243
+ p11.log.info(`\u{1F4A1} Type ${pc8.bold(".model <id>")} in the chat to switch models mid-session.`);
8123
8244
  }
8124
8245
  let exitCode = 1;
8125
8246
  try {
@@ -8130,7 +8251,7 @@ Error: ${launchPlan.error}
8130
8251
  oauthBackend?.handle.close();
8131
8252
  }
8132
8253
  if (!agentStdout) {
8133
- p10.log.info("Gemini proxy stopped.");
8254
+ p11.log.info("Gemini proxy stopped.");
8134
8255
  }
8135
8256
  if (trace) {
8136
8257
  printTraceLog(getGeminiProxyDebugLogPath());
@@ -8140,7 +8261,7 @@ Error: ${launchPlan.error}
8140
8261
 
8141
8262
  // src/antigravity.ts
8142
8263
  import pc9 from "picocolors";
8143
- import * as p11 from "@clack/prompts";
8264
+ import * as p12 from "@clack/prompts";
8144
8265
 
8145
8266
  // src/antigravity/cloud-code-gateway.ts
8146
8267
  import http from "http";
@@ -8235,18 +8356,18 @@ var JSON_SCHEMA_TYPES = /* @__PURE__ */ new Map([
8235
8356
  ["OBJECT", "object"],
8236
8357
  ["STRING", "string"]
8237
8358
  ]);
8238
- function expandTextWithThinking(text5) {
8239
- if (!text5.includes("<thinking>")) {
8240
- return [{ type: "text", text: text5 }];
8359
+ function expandTextWithThinking(text6) {
8360
+ if (!text6.includes("<thinking>")) {
8361
+ return [{ type: "text", text: text6 }];
8241
8362
  }
8242
8363
  const out = [];
8243
- const tokens = text5.split(/<thinking>([\s\S]*?)<\/thinking>/);
8364
+ const tokens = text6.split(/<thinking>([\s\S]*?)<\/thinking>/);
8244
8365
  for (let i = 0; i < tokens.length; i++) {
8245
8366
  const token = tokens[i] ?? "";
8246
8367
  if (!token.trim()) continue;
8247
8368
  out.push({ type: i % 2 === 1 ? "reasoning" : "text", text: token });
8248
8369
  }
8249
- return out.length > 0 ? out : [{ type: "text", text: text5 }];
8370
+ return out.length > 0 ? out : [{ type: "text", text: text6 }];
8250
8371
  }
8251
8372
  function normalizeSchemaType(value) {
8252
8373
  if (typeof value === "string") {
@@ -8316,9 +8437,9 @@ function translateRequest(ccReq, options = {}) {
8316
8437
  continue;
8317
8438
  }
8318
8439
  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);
8440
+ const hasFunctionCall = msg.parts.some((p16) => p16.functionCall);
8441
+ const hasAssistantReasoning = role === "model" && msg.parts.some((p16) => p16.thought || p16.text?.includes("<thinking>"));
8442
+ const hasComplexParts = msg.parts.some((p16) => p16.thought || p16.inlineData || p16.functionCall || p16.functionResponse);
8322
8443
  const singleText = msg.parts.length === 1 ? msg.parts[0]?.text : void 0;
8323
8444
  if (!hasComplexParts && singleText !== void 0 && !singleText.includes("<thinking>")) {
8324
8445
  sdkMessages.push({
@@ -9471,7 +9592,7 @@ async function startCloudCodeGateway(routes, opts = {}) {
9471
9592
  const templateKey = opts.templateKey ?? "gemini-3.5-flash-low";
9472
9593
  const trace = opts.trace ?? false;
9473
9594
  const trackActiveRoute = opts.trackActiveRoute ?? false;
9474
- const log14 = opts.logFn ?? (() => {
9595
+ const log15 = opts.logFn ?? (() => {
9475
9596
  });
9476
9597
  const catalogFixture = fetchAvailableModels_default;
9477
9598
  const injectedCatalog = injectRelayModels(catalogFixture, routes, templateKey);
@@ -9521,12 +9642,12 @@ async function startCloudCodeGateway(routes, opts = {}) {
9521
9642
  const contentType = (req.headers["content-type"] ?? "").toLowerCase();
9522
9643
  const lowerUrl = url.toLowerCase();
9523
9644
  if (trace) {
9524
- log14(`[gateway] ${method} ${url}`);
9525
- log14(`[gateway] content-type: ${contentType}`);
9526
- log14(`[gateway] body-size: ${bodyStr.length}`);
9645
+ log15(`[gateway] ${method} ${url}`);
9646
+ log15(`[gateway] content-type: ${contentType}`);
9647
+ log15(`[gateway] body-size: ${bodyStr.length}`);
9527
9648
  }
9528
9649
  if (contentType.includes("proto") || contentType.includes("grpc") && !contentType.includes("json")) {
9529
- log14(`[gateway] UNSUPPORTED content-type: ${contentType}`);
9650
+ log15(`[gateway] UNSUPPORTED content-type: ${contentType}`);
9530
9651
  respondJson(res, 415, {
9531
9652
  error: {
9532
9653
  code: 415,
@@ -9547,47 +9668,47 @@ async function startCloudCodeGateway(routes, opts = {}) {
9547
9668
  }
9548
9669
  return value;
9549
9670
  }).slice(0, 500);
9550
- log14(`[gateway] body-preview: ${preview}`);
9671
+ log15(`[gateway] body-preview: ${preview}`);
9551
9672
  }
9552
9673
  if (lowerUrl.includes("loadcodeassist")) {
9553
- if (trace) log14("[gateway] \u2192 loadCodeAssist");
9674
+ if (trace) log15("[gateway] \u2192 loadCodeAssist");
9554
9675
  respondJson(res, 200, loadCodeAssist_default);
9555
9676
  return;
9556
9677
  }
9557
9678
  if (lowerUrl.includes("fetchavailablemodels") || lowerUrl.includes("getavailablemodels")) {
9558
- if (trace) log14("[gateway] \u2192 fetchAvailableModels");
9679
+ if (trace) log15("[gateway] \u2192 fetchAvailableModels");
9559
9680
  respondJson(res, 200, injectedCatalog);
9560
9681
  return;
9561
9682
  }
9562
9683
  if (lowerUrl.includes("modelconfigs")) {
9563
- if (trace) log14("[gateway] \u2192 listModelConfigs");
9684
+ if (trace) log15("[gateway] \u2192 listModelConfigs");
9564
9685
  respondJson(res, 200, modelConfigsResponse);
9565
9686
  return;
9566
9687
  }
9567
9688
  if (lowerUrl.includes("generatecontent") || lowerUrl.includes("generatechat")) {
9568
9689
  const model = parsed?.model;
9569
- if (trace) log14(`[gateway] extracted model: ${model ?? "N/A"}`);
9690
+ if (trace) log15(`[gateway] extracted model: ${model ?? "N/A"}`);
9570
9691
  const route = resolveRouteForModel(model);
9571
9692
  if (route) {
9572
9693
  if (trace) {
9573
- log14(
9694
+ log15(
9574
9695
  `[gateway] resolved route: ${route.catalogId} (${route.providerId}/${route.upstreamModelId} via ${model})`
9575
9696
  );
9576
9697
  }
9577
9698
  const media = sanitizeUnsupportedInlineData(parsed);
9578
9699
  parsed = media.request;
9579
9700
  if (media.latestUserTurnHasUnsupportedMedia) {
9580
- if (trace) log14("[gateway] unsupported media in current user turn; provider call skipped");
9701
+ if (trace) log15("[gateway] unsupported media in current user turn; provider call skipped");
9581
9702
  respondUnsupportedMedia(res, route, lowerUrl.includes("stream"));
9582
9703
  return;
9583
9704
  }
9584
9705
  if (trackActiveRoute && selectedSlotIds.has(model ?? "") && isUserTurnRequest(parsed)) {
9585
9706
  activeRoute = route;
9586
- if (trace) log14(`[gateway] active route: ${route.catalogId} via ${model}`);
9707
+ if (trace) log15(`[gateway] active route: ${route.catalogId} via ${model}`);
9587
9708
  }
9588
9709
  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)}`);
9710
+ handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log15).catch((err) => {
9711
+ log15(`[gateway] cloud-code forward error: ${err instanceof Error ? err.stack || err.message : String(err)}`);
9591
9712
  if (!res.headersSent) {
9592
9713
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9593
9714
  } else if (!res.writableEnded) {
@@ -9614,13 +9735,13 @@ async function startCloudCodeGateway(routes, opts = {}) {
9614
9735
  rememberReasoningEcho(reasoningEchoesByConversation, conversationKey, reasoning);
9615
9736
  };
9616
9737
  if (isStream) {
9617
- handleStreamingRequest(res, route, baseProviderOptions, parsed, log14, {
9738
+ handleStreamingRequest(res, route, baseProviderOptions, parsed, log15, {
9618
9739
  requestOptions,
9619
9740
  onReasoningWithToolCall: rememberReasoning,
9620
9741
  trace
9621
9742
  }).catch((err) => {
9622
- log14(`[gateway] stream error: ${formatUpstreamError(err)}`);
9623
- if (trace) log14(`[gateway] stream error detail: ${formatUpstreamErrorTrace(err)}`);
9743
+ log15(`[gateway] stream error: ${formatUpstreamError(err)}`);
9744
+ if (trace) log15(`[gateway] stream error detail: ${formatUpstreamErrorTrace(err)}`);
9624
9745
  if (!res.headersSent) {
9625
9746
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9626
9747
  } else if (!res.writableEnded) {
@@ -9628,13 +9749,13 @@ async function startCloudCodeGateway(routes, opts = {}) {
9628
9749
  }
9629
9750
  });
9630
9751
  } else {
9631
- handleUnaryRequest(res, route, baseProviderOptions, parsed, log14, {
9752
+ handleUnaryRequest(res, route, baseProviderOptions, parsed, log15, {
9632
9753
  requestOptions,
9633
9754
  onReasoningWithToolCall: rememberReasoning,
9634
9755
  trace
9635
9756
  }).catch((err) => {
9636
- log14(`[gateway] unary error: ${formatUpstreamError(err)}`);
9637
- if (trace) log14(`[gateway] unary error detail: ${formatUpstreamErrorTrace(err)}`);
9757
+ log15(`[gateway] unary error: ${formatUpstreamError(err)}`);
9758
+ if (trace) log15(`[gateway] unary error detail: ${formatUpstreamErrorTrace(err)}`);
9638
9759
  if (!res.headersSent) {
9639
9760
  respondJson(res, 500, { error: { code: 500, message: formatUpstreamError(err) } });
9640
9761
  }
@@ -9730,7 +9851,7 @@ async function startCloudCodeGateway(routes, opts = {}) {
9730
9851
  return;
9731
9852
  }
9732
9853
  if (trace) {
9733
- log14(`[gateway] unknown endpoint: ${url}`);
9854
+ log15(`[gateway] unknown endpoint: ${url}`);
9734
9855
  }
9735
9856
  respondJson(res, 200, {});
9736
9857
  }).catch((err) => {
@@ -9824,7 +9945,7 @@ function rememberReasoningEcho(cache, key, reasoning) {
9824
9945
  existing.push(normalized);
9825
9946
  cache.set(key, existing.slice(-MAX_REASONING_ECHOES_PER_CONVERSATION));
9826
9947
  }
9827
- async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14) {
9948
+ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log15) {
9828
9949
  const projectId = typeof route.providerData?.projectId === "string" ? route.providerData.projectId : "";
9829
9950
  if (!projectId) {
9830
9951
  respondJson(res, 500, {
@@ -9853,7 +9974,7 @@ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14
9853
9974
  });
9854
9975
  if (!upstream.ok) {
9855
9976
  const errBody = await upstream.text();
9856
- log14(`[gateway] cloud-code upstream error ${upstream.status}: ${errBody}`);
9977
+ log15(`[gateway] cloud-code upstream error ${upstream.status}: ${errBody}`);
9857
9978
  respondJson(res, upstream.status >= 500 ? 502 : upstream.status, {
9858
9979
  error: { code: upstream.status, message: errBody || upstream.statusText }
9859
9980
  });
@@ -9883,11 +10004,11 @@ async function handleCloudCodeForwardRequest(res, route, parsed, lowerUrl, log14
9883
10004
  });
9884
10005
  res.end(body);
9885
10006
  }
9886
- function emitThinkingDelta(res, route, responseId, text5, startSse) {
9887
- if (!text5) return;
10007
+ function emitThinkingDelta(res, route, responseId, text6, startSse) {
10008
+ if (!text6) return;
9888
10009
  startSse();
9889
10010
  const chunk = formatCloudCodeChunk({
9890
- thought: text5,
10011
+ thought: text6,
9891
10012
  modelVersion: route.catalogId,
9892
10013
  responseId
9893
10014
  });
@@ -9895,9 +10016,9 @@ function emitThinkingDelta(res, route, responseId, text5, startSse) {
9895
10016
 
9896
10017
  `);
9897
10018
  }
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;
10019
+ function trailingPartial(text6, tag) {
10020
+ for (let len = Math.min(tag.length - 1, text6.length); len > 0; len--) {
10021
+ if (text6.endsWith(tag.slice(0, len))) return len;
9901
10022
  }
9902
10023
  return 0;
9903
10024
  }
@@ -9909,13 +10030,13 @@ function createThinkFilter() {
9909
10030
  let src = partial + chunk;
9910
10031
  partial = "";
9911
10032
  let thought = "";
9912
- let text5 = "";
10033
+ let text6 = "";
9913
10034
  while (src.length > 0) {
9914
10035
  if (state === "scanning") {
9915
10036
  const idx = src.indexOf("<think>");
9916
10037
  if (idx === -1) {
9917
10038
  const len = trailingPartial(src, "<think>");
9918
- text5 += src.slice(0, src.length - len);
10039
+ text6 += src.slice(0, src.length - len);
9919
10040
  if (len > 0) {
9920
10041
  partial = src.slice(src.length - len);
9921
10042
  } else {
@@ -9923,7 +10044,7 @@ function createThinkFilter() {
9923
10044
  }
9924
10045
  break;
9925
10046
  }
9926
- text5 += src.slice(0, idx);
10047
+ text6 += src.slice(0, idx);
9927
10048
  src = src.slice(idx + 7);
9928
10049
  state = "inside";
9929
10050
  } else {
@@ -9940,7 +10061,7 @@ function createThinkFilter() {
9940
10061
  state = "passthrough";
9941
10062
  }
9942
10063
  }
9943
- return { thought, text: text5 };
10064
+ return { thought, text: text6 };
9944
10065
  };
9945
10066
  }
9946
10067
  function emitStreamError(res, route, responseId, message, startSse) {
@@ -10002,8 +10123,8 @@ function respondUnsupportedMedia(res, route, streaming) {
10002
10123
  metadata: {}
10003
10124
  });
10004
10125
  }
10005
- function parsePseudoToolCall(text5, knownToolNames) {
10006
- const trimmed = text5.trim();
10126
+ function parsePseudoToolCall(text6, knownToolNames) {
10127
+ const trimmed = text6.trim();
10007
10128
  if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) return null;
10008
10129
  try {
10009
10130
  const obj = JSON.parse(trimmed);
@@ -10018,13 +10139,13 @@ function parsePseudoToolCall(text5, knownToolNames) {
10018
10139
  }
10019
10140
  return null;
10020
10141
  }
10021
- async function handleStreamingRequest(res, route, providerOptions, parsed, log14, options = {}) {
10142
+ async function handleStreamingRequest(res, route, providerOptions, parsed, log15, options = {}) {
10022
10143
  const sdkParams = applyClaudeCodeOAuthIdentity(route, translateRequest(parsed, {
10023
10144
  ...options.requestOptions,
10024
10145
  maxTools: maxToolsForNpm(route.npm)
10025
10146
  }));
10026
10147
  if (options.trace) {
10027
- log14(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10148
+ log15(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10028
10149
  }
10029
10150
  const effectiveProviderOptions = deepMergeProviderOptions(
10030
10151
  providerOptions,
@@ -10085,35 +10206,35 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10085
10206
  bufferingJsonText = false;
10086
10207
  };
10087
10208
  for await (const part of stream) {
10088
- const p15 = part;
10089
- if (p15.type === "reasoning-delta" || p15.type === "reasoning") {
10090
- const reasoning = reasoningDeltaText(p15);
10209
+ const p16 = part;
10210
+ if (p16.type === "reasoning-delta" || p16.type === "reasoning") {
10211
+ const reasoning = reasoningDeltaText(p16);
10091
10212
  responseReasoning += reasoning;
10092
10213
  emitThinkingDelta(res, route, responseId, reasoning, startSse);
10093
10214
  continue;
10094
10215
  }
10095
- if (p15.type === "text-delta") {
10096
- const { thought, text: text5 } = thinkFilter(reasoningDeltaText(p15));
10216
+ if (p16.type === "text-delta") {
10217
+ const { thought, text: text6 } = thinkFilter(reasoningDeltaText(p16));
10097
10218
  if (thought) {
10098
10219
  responseReasoning += thought;
10099
10220
  emitThinkingDelta(res, route, responseId, thought, startSse);
10100
10221
  }
10101
- if (text5) {
10102
- log14(`[gateway] text-delta: ${JSON.stringify(text5.slice(0, 500))}`);
10103
- if (!bufferingJsonText && (textBuffer + text5).trimStart().startsWith("{")) {
10222
+ if (text6) {
10223
+ log15(`[gateway] text-delta: ${JSON.stringify(text6.slice(0, 500))}`);
10224
+ if (!bufferingJsonText && (textBuffer + text6).trimStart().startsWith("{")) {
10104
10225
  bufferingJsonText = true;
10105
10226
  }
10106
10227
  if (bufferingJsonText) {
10107
- textBuffer += text5;
10228
+ textBuffer += text6;
10108
10229
  const pseudoTool = textBuffer.trimEnd().endsWith("}") ? parsePseudoToolCall(textBuffer, knownToolNames) : null;
10109
10230
  if (pseudoTool) {
10110
- log14(`[gateway] parsed pseudo tool-call from text: ${pseudoTool.name}`);
10231
+ log15(`[gateway] parsed pseudo tool-call from text: ${pseudoTool.name}`);
10111
10232
  emitPseudoToolCall(pseudoTool);
10112
10233
  }
10113
10234
  } else {
10114
10235
  startSse();
10115
10236
  const chunk = formatCloudCodeChunk({
10116
- text: text5,
10237
+ text: text6,
10117
10238
  modelVersion: route.catalogId,
10118
10239
  responseId
10119
10240
  });
@@ -10122,25 +10243,25 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10122
10243
  `);
10123
10244
  }
10124
10245
  }
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;
10246
+ } else if (p16.type === "tool-input-start") {
10247
+ const id = p16.id ?? p16.toolCallId;
10248
+ toolCallBuffers.set(id, { name: p16.toolName, json: "" });
10249
+ } else if (p16.type === "tool-input-delta") {
10250
+ const id = p16.id ?? p16.toolCallId;
10130
10251
  const buf = toolCallBuffers.get(id);
10131
- if (buf) buf.json += p15.delta;
10132
- } else if (p15.type === "tool-call") {
10252
+ if (buf) buf.json += p16.delta;
10253
+ } else if (p16.type === "tool-call") {
10133
10254
  sawToolCall = true;
10134
- const id = p15.toolCallId ?? p15.id;
10255
+ const id = p16.toolCallId ?? p16.id;
10135
10256
  const buf = toolCallBuffers.get(id);
10136
10257
  let args = {};
10137
10258
  try {
10138
- args = buf ? JSON.parse(buf.json || "{}") : p15.input || {};
10259
+ args = buf ? JSON.parse(buf.json || "{}") : p16.input || {};
10139
10260
  } catch {
10140
- args = p15.input || {};
10261
+ args = p16.input || {};
10141
10262
  }
10142
- const name = buf ? buf.name : p15.toolName;
10143
- log14(`[gateway] tool-call: ${name}`);
10263
+ const name = buf ? buf.name : p16.toolName;
10264
+ log15(`[gateway] tool-call: ${name}`);
10144
10265
  startSse();
10145
10266
  const chunk = formatCloudCodeChunk({
10146
10267
  functionCall: { name, args: normalizeFunctionCallArgs(args) },
@@ -10150,42 +10271,42 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10150
10271
  res.write(`data: ${JSON.stringify(chunk)}
10151
10272
 
10152
10273
  `);
10153
- } else if (p15.type === "finish") {
10154
- log14(`[gateway] finish: ${p15.finishReason ?? "unknown"}`);
10274
+ } else if (p16.type === "finish") {
10275
+ log15(`[gateway] finish: ${p16.finishReason ?? "unknown"}`);
10155
10276
  if (textBuffer) {
10156
10277
  const pseudoTool = parsePseudoToolCall(textBuffer, knownToolNames);
10157
10278
  if (pseudoTool) {
10158
- log14(`[gateway] parsed pseudo tool-call on finish: ${pseudoTool.name}`);
10279
+ log15(`[gateway] parsed pseudo tool-call on finish: ${pseudoTool.name}`);
10159
10280
  emitPseudoToolCall(pseudoTool);
10160
10281
  } else {
10161
10282
  flushBufferedText();
10162
10283
  }
10163
10284
  }
10164
10285
  startSse();
10165
- const reason = mapFinishReason2(p15.finishReason ?? "");
10286
+ const reason = mapFinishReason2(p16.finishReason ?? "");
10166
10287
  const chunk = formatCloudCodeChunk({
10167
10288
  modelVersion: route.catalogId,
10168
10289
  responseId,
10169
10290
  finishReason: reason,
10170
10291
  usage: {
10171
- promptTokens: p15.totalUsage?.inputTokens || 0,
10172
- completionTokens: p15.totalUsage?.outputTokens || 0
10292
+ promptTokens: p16.totalUsage?.inputTokens || 0,
10293
+ completionTokens: p16.totalUsage?.outputTokens || 0
10173
10294
  }
10174
10295
  });
10175
10296
  res.write(`data: ${JSON.stringify(chunk)}
10176
10297
 
10177
10298
  `);
10178
- } else if (p15.type === "error") {
10179
- const message = formatUpstreamError(p15.error);
10180
- log14(`[gateway] stream provider error: ${message}`);
10299
+ } else if (p16.type === "error") {
10300
+ const message = formatUpstreamError(p16.error);
10301
+ log15(`[gateway] stream provider error: ${message}`);
10181
10302
  if (options.trace) {
10182
- log14(`[gateway] stream provider error detail: ${formatUpstreamErrorTrace(p15.error)}`);
10303
+ log15(`[gateway] stream provider error detail: ${formatUpstreamErrorTrace(p16.error)}`);
10183
10304
  }
10184
10305
  flushBufferedText();
10185
10306
  emitStreamError(res, route, responseId, message, startSse);
10186
10307
  break;
10187
- } else if (p15.type === "reasoning-start" || p15.type === "reasoning-end") {
10188
- log14(`[gateway] ${p15.type}`);
10308
+ } else if (p16.type === "reasoning-start" || p16.type === "reasoning-end") {
10309
+ log15(`[gateway] ${p16.type}`);
10189
10310
  }
10190
10311
  }
10191
10312
  if (!res.headersSent) {
@@ -10196,13 +10317,13 @@ async function handleStreamingRequest(res, route, providerOptions, parsed, log14
10196
10317
  }
10197
10318
  res.end();
10198
10319
  }
10199
- async function handleUnaryRequest(res, route, providerOptions, parsed, log14, options = {}) {
10320
+ async function handleUnaryRequest(res, route, providerOptions, parsed, log15, options = {}) {
10200
10321
  const sdkParams = applyClaudeCodeOAuthIdentity(route, translateRequest(parsed, {
10201
10322
  ...options.requestOptions,
10202
10323
  maxTools: maxToolsForNpm(route.npm)
10203
10324
  }));
10204
10325
  if (options.trace) {
10205
- log14(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10326
+ log15(`[gateway] sdk request: ${JSON.stringify(summarizeSdkRequestForTrace(sdkParams))}`);
10206
10327
  }
10207
10328
  const effectiveProviderOptions = deepMergeProviderOptions(
10208
10329
  providerOptions,
@@ -10757,11 +10878,11 @@ function resolveAntigravityBootModel(provider, modelSelector) {
10757
10878
  async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10758
10879
  const resolved = favorites.map((favorite) => resolveFavoriteModel(favorite, allProviders)).filter((entry) => entry !== null);
10759
10880
  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`."));
10881
+ p12.log.warn("No Antigravity CLI favorites are available.");
10882
+ p12.log.info(pc9.dim("Manage them with `relay-ai favorites --agy`."));
10762
10883
  return null;
10763
10884
  }
10764
- const picked = await p11.select({
10885
+ const picked = await p12.select({
10765
10886
  message: "Launch from Antigravity CLI favorites",
10766
10887
  options: resolved.map(({ provider, model }) => ({
10767
10888
  value: `${provider.id}:${model.id}`,
@@ -10770,8 +10891,8 @@ async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10770
10891
  })),
10771
10892
  initialValue: `${resolved[0].provider.id}:${resolved[0].model.id}`
10772
10893
  });
10773
- if (p11.isCancel(picked)) {
10774
- p11.cancel("Cancelled.");
10894
+ if (p12.isCancel(picked)) {
10895
+ p12.cancel("Cancelled.");
10775
10896
  return null;
10776
10897
  }
10777
10898
  const [providerId, ...modelParts] = picked.split(":");
@@ -10780,31 +10901,31 @@ async function pickAntigravityCliFavoriteLaunchModel(favorites, allProviders) {
10780
10901
  }
10781
10902
  async function resolveAntigravityLaunch(prefs, boot) {
10782
10903
  let catalog;
10783
- const catalogSpinner = p11.spinner();
10904
+ const catalogSpinner = p12.spinner();
10784
10905
  catalogSpinner.start("Loading providers...");
10785
10906
  try {
10786
10907
  catalog = await fetchProviderCatalog();
10787
10908
  } catch (err) {
10788
10909
  catalogSpinner.stop("");
10789
- p11.log.error(String(err instanceof Error ? err.message : err));
10910
+ p12.log.error(String(err instanceof Error ? err.message : err));
10790
10911
  return null;
10791
10912
  }
10792
10913
  catalogSpinner.stop("");
10793
10914
  const allProviders = providersForTarget(providersForPicker(catalog), "antigravity");
10794
10915
  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."));
10916
+ p12.log.warn("No providers available.");
10917
+ p12.log.info(pc9.dim("Run relay-ai providers add or import to get started."));
10797
10918
  return null;
10798
10919
  }
10799
10920
  if (boot?.launchProvider && boot?.launchModel) {
10800
- const provider = allProviders.find((p15) => p15.id === boot.launchProvider);
10921
+ const provider = allProviders.find((p16) => p16.id === boot.launchProvider);
10801
10922
  if (!provider) {
10802
- p11.log.error(`Provider not found: ${boot.launchProvider}`);
10923
+ p12.log.error(`Provider not found: ${boot.launchProvider}`);
10803
10924
  return null;
10804
10925
  }
10805
10926
  const { model, error } = resolveAntigravityBootModel(provider, boot.launchModel);
10806
10927
  if (!model) {
10807
- p11.log.error(error ?? `Model not found: ${boot.launchModel} on provider ${provider.name}`);
10928
+ p12.log.error(error ?? `Model not found: ${boot.launchModel} on provider ${provider.name}`);
10808
10929
  return null;
10809
10930
  }
10810
10931
  return { provider, model, allProviders };
@@ -10821,13 +10942,13 @@ async function resolveAntigravityLaunch(prefs, boot) {
10821
10942
  const conflicts = detectConflicts();
10822
10943
  let currentInitialProvider = initialProvider;
10823
10944
  while (true) {
10824
- const chosen = await p11.select({
10945
+ const chosen = await p12.select({
10825
10946
  message: "Which provider?",
10826
10947
  options: providerOptions,
10827
10948
  initialValue: currentInitialProvider
10828
10949
  });
10829
- if (p11.isCancel(chosen)) {
10830
- p11.cancel("Cancelled.");
10950
+ if (p12.isCancel(chosen)) {
10951
+ p12.cancel("Cancelled.");
10831
10952
  return null;
10832
10953
  }
10833
10954
  if (chosen === AGY_FAVORITES_PROVIDER_ID) {
@@ -10860,32 +10981,32 @@ async function resolveAndBuildRoutes(provider, model, allProviders, prefs, opts)
10860
10981
  maxRoutes: opts.maxRoutes
10861
10982
  });
10862
10983
  if (!result) {
10863
- p11.log.error(`No credential for ${provider.name}. Run: relay-ai providers auth ${provider.id} or add an API key.`);
10984
+ p12.log.error(`No credential for ${provider.name}. Run: relay-ai providers auth ${provider.id} or add an API key.`);
10864
10985
  return null;
10865
10986
  }
10866
10987
  if (result.routes.length > 1) {
10867
- p11.log.info(
10988
+ p12.log.info(
10868
10989
  `Favorites mode active \u2014 Antigravity picker will show ${result.routes.length} models.`
10869
10990
  );
10870
- p11.log.info("Edit with `relay-ai favorites --agy`.");
10991
+ p12.log.info("Edit with `relay-ai favorites --agy`.");
10871
10992
  }
10872
10993
  if (result.droppedFavorites.length > 0) {
10873
- p11.log.warn(
10994
+ p12.log.warn(
10874
10995
  `Skipped ${result.droppedFavorites.length} stale/unauthorized favorite(s): ` + result.droppedFavorites.map((fav) => `${fav.providerId}:${fav.modelId}`).join(", ")
10875
10996
  );
10876
10997
  }
10877
10998
  if (result.capacitySkippedFavorites.length > 0) {
10878
- p11.log.warn(formatAgyCapacityWarning(opts.validatedSlotCount, result.capacitySkippedFavorites.length));
10879
- p11.log.warn(
10999
+ p12.log.warn(formatAgyCapacityWarning(opts.validatedSlotCount, result.capacitySkippedFavorites.length));
11000
+ p12.log.warn(
10880
11001
  "Not exposed: " + result.capacitySkippedFavorites.map((fav) => `${fav.providerId}:${fav.modelId}`).join(", ")
10881
11002
  );
10882
11003
  if (opts.pauseForCapacityWarning && isInteractiveTerminal() && !agyArgsAreNonInteractive(opts.childArgs)) {
10883
- const proceed = await p11.confirm({
11004
+ const proceed = await p12.confirm({
10884
11005
  message: "Continue with the validated AGY switch catalog?",
10885
11006
  initialValue: true
10886
11007
  });
10887
- if (p11.isCancel(proceed) || !proceed) {
10888
- p11.cancel("Cancelled.");
11008
+ if (p12.isCancel(proceed) || !proceed) {
11009
+ p12.cancel("Cancelled.");
10889
11010
  return null;
10890
11011
  }
10891
11012
  }
@@ -10937,7 +11058,7 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10937
11058
  const prefs = loadPreferences();
10938
11059
  relayIntro(intro);
10939
11060
  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.");
11061
+ p12.log.info("Tip: AGY uses its own favorites list. Run `relay-ai favorites --agy` to set up switching.");
10941
11062
  savePreferences({ antigravityCliFavoritesHintShown: true });
10942
11063
  }
10943
11064
  const selection = await resolveAntigravityLaunch(prefs, boot);
@@ -10950,7 +11071,7 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10950
11071
  fixture: fetchAvailableModels_default
10951
11072
  });
10952
11073
  for (const warning of compatibility.warnings) {
10953
- p11.log.warn(warning);
11074
+ p12.log.warn(warning);
10954
11075
  }
10955
11076
  const routeLimit = compatibility.mode === "multi-model" ? compatibility.validatedSwitchSlotCount : 1;
10956
11077
  const routeResult = await resolveAndBuildRoutes(provider, model, allProviders, prefs, {
@@ -10970,12 +11091,12 @@ async function runAntigravityCommand(intro, tracePrefix, trace, boot, launch, op
10970
11091
  try {
10971
11092
  gatewayHandle = await startCloudCodeGateway(routeResult.routes, { trace, logFn });
10972
11093
  } catch (err) {
10973
- p11.log.error(`Failed to start Cloud Code gateway: ${err}`);
11094
+ p12.log.error(`Failed to start Cloud Code gateway: ${err}`);
10974
11095
  return 1;
10975
11096
  }
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)}`);
11097
+ p12.log.info(`Cloud Code gateway on ${pc9.cyan(`127.0.0.1:${gatewayHandle.port}`)}`);
11098
+ p12.log.success(`Active model: ${formatCodexModelLabel(model)} ${pc9.dim("via")} ${provider.name}`);
11099
+ if (traceLogPath) p12.log.info(`Gateway trace \u2192 ${pc9.dim(traceLogPath)}`);
10979
11100
  relayOutro("Launching", `${formatCodexModelLabel(model)} (${provider.name})`);
10980
11101
  try {
10981
11102
  const cleanEnv = buildAntigravityChildEnv(gatewayHandle.url);
@@ -11003,12 +11124,12 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
11003
11124
  async (env, _routes, gatewayHandle) => {
11004
11125
  const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "app-profile");
11005
11126
  if (isAntigravityAppRunning(profileDir)) {
11006
- const restart = await p11.confirm({
11127
+ const restart = await p12.confirm({
11007
11128
  message: "Restart Antigravity to apply this Relay gateway?",
11008
11129
  initialValue: true
11009
11130
  });
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.");
11131
+ if (p12.isCancel(restart) || !restart) {
11132
+ p12.log.info("Quit and reopen Antigravity when you are ready for the new gateway to take effect.");
11012
11133
  return 0;
11013
11134
  }
11014
11135
  quitAntigravityAppGracefully();
@@ -11019,18 +11140,18 @@ async function runAntigravityAppCommand(childArgs, trace = false, boot) {
11019
11140
  }
11020
11141
  const launchCode = await launchAntigravityApp(env, profileDir, gatewayHandle.url, childArgs);
11021
11142
  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."));
11143
+ p12.log.info("Antigravity is using the Relay Cloud Code gateway.");
11144
+ p12.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11024
11145
  await waitForShutdown();
11025
11146
  await new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS));
11026
11147
  console.log("");
11027
- p11.log.step("Gateway stopped.");
11028
- const shouldClose = await p11.confirm({
11148
+ p12.log.step("Gateway stopped.");
11149
+ const shouldClose = await p12.confirm({
11029
11150
  message: "Close Antigravity?",
11030
11151
  initialValue: true
11031
11152
  });
11032
- if (!p11.isCancel(shouldClose) && shouldClose) {
11033
- p11.log.step("Stopping Antigravity...");
11153
+ if (!p12.isCancel(shouldClose) && shouldClose) {
11154
+ p12.log.step("Stopping Antigravity...");
11034
11155
  quitAntigravityAppGracefully();
11035
11156
  if (!await waitForAntigravityAppQuit(profileDir)) {
11036
11157
  forceQuitAntigravityApp(profileDir);
@@ -11051,12 +11172,12 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11051
11172
  async (env, _routes, gatewayHandle) => {
11052
11173
  const profileDir = join10(homedir8(), ".relay-ai", "antigravity", "profile");
11053
11174
  if (isAntigravityIdeRunning(profileDir)) {
11054
- const restart = await p11.confirm({
11175
+ const restart = await p12.confirm({
11055
11176
  message: "Restart Antigravity IDE to apply this Relay gateway?",
11056
11177
  initialValue: true
11057
11178
  });
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.");
11179
+ if (p12.isCancel(restart) || !restart) {
11180
+ p12.log.info("Quit and reopen Antigravity IDE when you are ready for the new gateway to take effect.");
11060
11181
  return 0;
11061
11182
  }
11062
11183
  quitAntigravityIdeGracefully();
@@ -11067,18 +11188,18 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11067
11188
  }
11068
11189
  const launchCode = await launchAntigravityIde(env, profileDir, gatewayHandle.url, childArgs);
11069
11190
  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."));
11191
+ p12.log.info("Antigravity IDE is using the Relay Cloud Code gateway.");
11192
+ p12.log.info(pc9.cyan("Press Ctrl+C to stop the gateway."));
11072
11193
  await waitForShutdown();
11073
11194
  await new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS));
11074
11195
  console.log("");
11075
- p11.log.step("Gateway stopped.");
11076
- const shouldClose = await p11.confirm({
11196
+ p12.log.step("Gateway stopped.");
11197
+ const shouldClose = await p12.confirm({
11077
11198
  message: "Close Antigravity IDE?",
11078
11199
  initialValue: true
11079
11200
  });
11080
- if (!p11.isCancel(shouldClose) && shouldClose) {
11081
- p11.log.step("Stopping Antigravity IDE...");
11201
+ if (!p12.isCancel(shouldClose) && shouldClose) {
11202
+ p12.log.step("Stopping Antigravity IDE...");
11082
11203
  quitAntigravityIdeGracefully();
11083
11204
  if (!await waitForAntigravityIdeQuit(profileDir)) {
11084
11205
  forceQuitAntigravityIde(profileDir);
@@ -11093,7 +11214,7 @@ async function runAntigravityIdeCommand(childArgs, trace = false, boot) {
11093
11214
 
11094
11215
  // src/codex-app.ts
11095
11216
  import pc10 from "picocolors";
11096
- import * as p12 from "@clack/prompts";
11217
+ import * as p13 from "@clack/prompts";
11097
11218
  import { join as join13 } from "path";
11098
11219
 
11099
11220
  // src/codex/app-provider-routes.ts
@@ -11220,12 +11341,12 @@ function readCodexConfigText(path3 = getCodexConfigPath()) {
11220
11341
  if (!existsSync8(path3)) return "";
11221
11342
  return readFileSync3(path3, "utf8");
11222
11343
  }
11223
- function parseCodexConfig(text5) {
11224
- if (!text5.trim()) return {};
11225
- return asRecord(parse(text5));
11344
+ function parseCodexConfig(text6) {
11345
+ if (!text6.trim()) return {};
11346
+ return asRecord(parse(text6));
11226
11347
  }
11227
- function captureRestoreState(text5) {
11228
- const config = parseCodexConfig(text5);
11348
+ function captureRestoreState(text6) {
11349
+ const config = parseCodexConfig(text6);
11229
11350
  const profile = rootString(config, "profile");
11230
11351
  const model = rootString(config, "model");
11231
11352
  const modelProvider = rootString(config, "model_provider");
@@ -11257,8 +11378,8 @@ function captureRestoreState(text5) {
11257
11378
  multiAgentV2: features.multi_agent_v2
11258
11379
  };
11259
11380
  }
11260
- function isAppManagedConfig(text5) {
11261
- const config = parseCodexConfig(text5);
11381
+ function isAppManagedConfig(text6) {
11382
+ const config = parseCodexConfig(text6);
11262
11383
  const mp = rootString(config, "model_provider");
11263
11384
  if (mp.had && mp.value === CODEX_APP_PROVIDER_ID) return true;
11264
11385
  const baseUrl = rootString(config, "openai_base_url");
@@ -11327,8 +11448,8 @@ function mergeAppConfig(existing, spec) {
11327
11448
  }
11328
11449
  return out;
11329
11450
  }
11330
- function validateAppConfigText(text5, spec) {
11331
- const config = parseCodexConfig(text5);
11451
+ function validateAppConfigText(text6, spec) {
11452
+ const config = parseCodexConfig(text6);
11332
11453
  if ("profile" in config) {
11333
11454
  throw new Error("Generated config still contains legacy root profile key");
11334
11455
  }
@@ -11362,17 +11483,17 @@ function applyAppConfigPatch(spec, configPath = getCodexConfigPath()) {
11362
11483
  throw new Error(`Invalid existing Codex config at ${configPath}: ${err instanceof Error ? err.message : err}`);
11363
11484
  }
11364
11485
  const merged = mergeAppConfig(existing, spec);
11365
- const text5 = `${stringify(merged)}
11486
+ const text6 = `${stringify(merged)}
11366
11487
  `;
11367
- validateAppConfigText(text5, spec);
11488
+ validateAppConfigText(text6, spec);
11368
11489
  mkdirSync4(dirname2(configPath), { recursive: true });
11369
- atomicWriteFile(configPath, text5);
11490
+ atomicWriteFile(configPath, text6);
11370
11491
  const written = readCodexConfigText(configPath);
11371
- if (written !== text5) {
11492
+ if (written !== text6) {
11372
11493
  throw new Error(`Codex config readback mismatch at ${configPath}`);
11373
11494
  }
11374
11495
  validateAppConfigText(written, spec);
11375
- return text5;
11496
+ return text6;
11376
11497
  }
11377
11498
  function applyRestoreKey(config, key, had, value) {
11378
11499
  if (had && value !== void 0) {
@@ -11432,10 +11553,10 @@ function restoreConfigFromState(state, configPath = getCodexConfigPath()) {
11432
11553
  return true;
11433
11554
  }
11434
11555
  function previewAppConfigToml(spec) {
11435
- const text5 = `${stringify(buildCodexAppRootConfig(spec))}
11556
+ const text6 = `${stringify(buildCodexAppRootConfig(spec))}
11436
11557
  `;
11437
- validateAppConfigText(text5, spec);
11438
- return text5;
11558
+ validateAppConfigText(text6, spec);
11559
+ return text6;
11439
11560
  }
11440
11561
 
11441
11562
  // src/codex/app-readiness.ts
@@ -11544,12 +11665,12 @@ function backupConfigToml(env = process.env) {
11544
11665
  return backupPath;
11545
11666
  }
11546
11667
  function saveAppRestoreStateBeforePatch(env = process.env) {
11547
- const text5 = readCodexConfigText();
11668
+ const text6 = readCodexConfigText();
11548
11669
  const existing = readAppRestoreState(env);
11549
- if (existing && isAppManagedConfig(text5)) {
11670
+ if (existing && isAppManagedConfig(text6)) {
11550
11671
  return existing;
11551
11672
  }
11552
- const state = captureRestoreState(text5);
11673
+ const state = captureRestoreState(text6);
11553
11674
  writeAppRestoreState(state, env);
11554
11675
  return state;
11555
11676
  }
@@ -11592,8 +11713,8 @@ function restoreCodexAppOverlay(env = process.env) {
11592
11713
  message: `Another relay-ai codex-app session is running (pid ${lock.pid}). Ctrl+C it first, then run --restore.`
11593
11714
  };
11594
11715
  }
11595
- const text5 = readCodexConfigText();
11596
- const managed = isAppManagedConfig(text5);
11716
+ const text6 = readCodexConfigText();
11717
+ const managed = isAppManagedConfig(text6);
11597
11718
  const restoreState = readAppRestoreState(env);
11598
11719
  if (!managed && !restoreState && !lock) {
11599
11720
  removeAppCatalogs(env);
@@ -11665,10 +11786,14 @@ function waitForShutdown2() {
11665
11786
  async function shutdownCodexAppSession(dependencies) {
11666
11787
  if (dependencies.isAppRunning()) {
11667
11788
  dependencies.quitApp();
11668
- const exited = await dependencies.waitForAppExit();
11789
+ let exited = await dependencies.waitForAppExit();
11790
+ if (!exited && dependencies.forceQuitApp) {
11791
+ dependencies.forceQuitApp();
11792
+ exited = await dependencies.waitForAppExit();
11793
+ }
11669
11794
  if (!exited) {
11670
11795
  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."
11796
+ "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
11797
  );
11673
11798
  }
11674
11799
  }
@@ -11708,14 +11833,14 @@ async function waitForShutdownWithConfirm(assumeYes = false) {
11708
11833
  if (signal !== "sigint") return signal;
11709
11834
  if (assumeYes) return signal;
11710
11835
  console.log("");
11711
- const choice = await p12.select({
11836
+ const choice = await p13.select({
11712
11837
  message: "Close ChatGPT Desktop and restore your Codex config?",
11713
11838
  options: [
11714
11839
  { value: "yes", label: "Yes, close ChatGPT Desktop and restore config" },
11715
11840
  { value: "no", label: "No, keep session running" }
11716
11841
  ]
11717
11842
  });
11718
- if (p12.isCancel(choice) || choice === "yes") return signal;
11843
+ if (p13.isCancel(choice) || choice === "yes") return signal;
11719
11844
  }
11720
11845
  }
11721
11846
  function codexAppHelpText() {
@@ -11789,26 +11914,26 @@ function vertexEntryToLocalModel2(entry) {
11789
11914
  }
11790
11915
  async function runCodexAppVertexLaunch(configOnly, trace = false) {
11791
11916
  if (!hasApplicationDefaultCredentials()) {
11792
- p12.log.error("Google Application Default Credentials not found.");
11793
- p12.log.info("Run: gcloud auth application-default login");
11917
+ p13.log.error("Google Application Default Credentials not found.");
11918
+ p13.log.info("Run: gcloud auth application-default login");
11794
11919
  return 1;
11795
11920
  }
11796
11921
  const config = buildVertexRuntimeConfig();
11797
11922
  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");
11923
+ p13.log.error("ANTHROPIC_VERTEX_PROJECT_ID (or GOOGLE_CLOUD_PROJECT) is not set.");
11924
+ p13.log.info("Set your project: export ANTHROPIC_VERTEX_PROJECT_ID=your-project-id");
11800
11925
  return 1;
11801
11926
  }
11802
11927
  let selectedEntry;
11803
11928
  if (config.models.length === 1) {
11804
11929
  selectedEntry = config.models[0];
11805
11930
  } else {
11806
- const choice = await p12.select({
11931
+ const choice = await p13.select({
11807
11932
  message: "Select a starting Vertex AI model:",
11808
11933
  options: config.models.map((m) => ({ value: m, label: m.display_name, hint: m.id }))
11809
11934
  });
11810
- if (p12.isCancel(choice)) {
11811
- p12.cancel("Cancelled.");
11935
+ if (p13.isCancel(choice)) {
11936
+ p13.cancel("Cancelled.");
11812
11937
  return 0;
11813
11938
  }
11814
11939
  selectedEntry = choice;
@@ -11865,7 +11990,7 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11865
11990
  try {
11866
11991
  restoreOverlay();
11867
11992
  } catch (err) {
11868
- p12.log.error(String(err instanceof Error ? err.message : err));
11993
+ p13.log.error(String(err instanceof Error ? err.message : err));
11869
11994
  }
11870
11995
  };
11871
11996
  try {
@@ -11905,14 +12030,14 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11905
12030
  patchedConfigSha256: fileSha256(getCodexConfigPath()),
11906
12031
  ...backupPath ? { originalConfigSha256: fileSha256(backupPath) } : {}
11907
12032
  });
11908
- p12.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
12033
+ p13.log.info(`Vertex AI \xB7 ${selectedEntry.display_name} \u2014 project: ${config.project} / location: ${config.location}`);
11909
12034
  logProxy(proxyPort);
11910
12035
  logActiveModel(selectedEntry.display_name, selectedEntry.id);
11911
12036
  try {
11912
12037
  await launchOrRestartCodexApp();
11913
12038
  } catch (err) {
11914
- p12.log.warn(String(err instanceof Error ? err.message : err));
11915
- p12.log.info(codexAppInstallHint());
12039
+ p13.log.warn(String(err instanceof Error ? err.message : err));
12040
+ p13.log.info(codexAppInstallHint());
11916
12041
  throw err;
11917
12042
  }
11918
12043
  printCodexAppSessionPanel({
@@ -11929,21 +12054,22 @@ async function runCodexAppVertexLaunch(configOnly, trace = false) {
11929
12054
  isAppRunning: isCodexAppRunning,
11930
12055
  quitApp: quitCodexAppGracefully,
11931
12056
  waitForAppExit: () => waitForCodexAppQuit(),
12057
+ ...process.platform === "win32" ? { forceQuitApp: forceQuitCodexApp } : {},
11932
12058
  restoreOverlay,
11933
12059
  closeResources
11934
12060
  });
11935
- p12.log.success(result.message);
12061
+ p13.log.success(result.message);
11936
12062
  return 0;
11937
12063
  } catch (err) {
11938
12064
  shutdownFailed = true;
11939
- p12.log.error(String(err instanceof Error ? err.message : err));
12065
+ p13.log.error(String(err instanceof Error ? err.message : err));
11940
12066
  return 1;
11941
12067
  }
11942
12068
  } finally {
11943
12069
  if (sessionActive && !isCodexAppRunning()) restoreOverlaySafely();
11944
12070
  closeResources();
11945
12071
  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.");
12072
+ p13.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
11947
12073
  }
11948
12074
  }
11949
12075
  }
@@ -11974,7 +12100,7 @@ async function runCodexAppCommand(args, opts = {}) {
11974
12100
  const trace = args.includes("--trace");
11975
12101
  const debugLogPath = getCodexProxyDebugLogPath();
11976
12102
  if (trace && !configOnly) {
11977
- p12.log.info(`Debug log: ${debugLogPath}`);
12103
+ p13.log.info(`Debug log: ${debugLogPath}`);
11978
12104
  }
11979
12105
  const isTty = Boolean(process.stdin.isTTY);
11980
12106
  if (!configOnly) {
@@ -11992,13 +12118,13 @@ async function runCodexAppCommand(args, opts = {}) {
11992
12118
  if (!configOnly) {
11993
12119
  codexAppIntro();
11994
12120
  if (interrupted.recovered) {
11995
- p12.log.warn("Recovered from an interrupted codex-app session (restored Codex config).");
12121
+ p13.log.warn("Recovered from an interrupted codex-app session (restored Codex config).");
11996
12122
  }
11997
12123
  }
11998
12124
  if (opts.vertex) {
11999
12125
  return runCodexAppVertexLaunch(configOnly, trace);
12000
12126
  }
12001
- const catalogSpinner = p12.spinner();
12127
+ const catalogSpinner = p13.spinner();
12002
12128
  catalogSpinner.start("Loading your providers...");
12003
12129
  let catalog;
12004
12130
  try {
@@ -12012,8 +12138,8 @@ async function runCodexAppCommand(args, opts = {}) {
12012
12138
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "codex-app");
12013
12139
  if (compatible.length === 0) {
12014
12140
  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.");
12141
+ p13.log.warn("No Codex-compatible providers in your registry.");
12142
+ p13.log.info("Add a provider with relay-ai providers add.");
12017
12143
  }
12018
12144
  return 0;
12019
12145
  }
@@ -12027,10 +12153,10 @@ async function runCodexAppCommand(args, opts = {}) {
12027
12153
  }
12028
12154
  const favoritesActive = favorites.length > 0 && !mixedMode;
12029
12155
  if (favoritesActive && !configOnly) {
12030
- p12.log.info(
12156
+ p13.log.info(
12031
12157
  `Favorites mode active \u2014 Codex App picker will show ${favorites.length + 1} models (1 starting + ${favorites.length} favorites).`
12032
12158
  );
12033
- p12.log.info("Edit with `relay-ai models`.");
12159
+ p13.log.info("Edit with `relay-ai models`.");
12034
12160
  }
12035
12161
  let activeProvider = providerForCodexPicker(
12036
12162
  compatible.find((lp) => lp.id === prefs.lastCodexProvider) ?? compatible[0]
@@ -12044,7 +12170,7 @@ async function runCodexAppCommand(args, opts = {}) {
12044
12170
  providerForCodexPicker
12045
12171
  );
12046
12172
  if ("error" in bootSelection) {
12047
- p12.log.error(bootSelection.error);
12173
+ p13.log.error(bootSelection.error);
12048
12174
  return 1;
12049
12175
  }
12050
12176
  activeProvider = bootSelection.provider;
@@ -12082,7 +12208,7 @@ async function runCodexAppCommand(args, opts = {}) {
12082
12208
  const apiKey = await resolveLocalProviderApiKey(activeProvider);
12083
12209
  if (!apiKey) {
12084
12210
  if (!configOnly) {
12085
- p12.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id}.`);
12211
+ p13.log.error(`No credential for ${activeProvider.name}. Run relay-ai providers auth ${activeProvider.id}.`);
12086
12212
  }
12087
12213
  return 1;
12088
12214
  }
@@ -12116,7 +12242,7 @@ async function runCodexAppCommand(args, opts = {}) {
12116
12242
  subagentFavorites: prefs.codexSubagentModels ?? []
12117
12243
  });
12118
12244
  if (mixedModels.capacitySkipped.length > 0) {
12119
- p12.log.warn(
12245
+ p13.log.warn(
12120
12246
  `Skipped ${mixedModels.capacitySkipped.length} favorite(s) because the mixed catalog is full: ` + mixedModels.capacitySkipped.map((f) => `${f.providerId}:${f.modelId}`).join(", ")
12121
12247
  );
12122
12248
  }
@@ -12176,7 +12302,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12176
12302
  try {
12177
12303
  restoreOverlay();
12178
12304
  } catch (err) {
12179
- p12.log.error(String(err instanceof Error ? err.message : err));
12305
+ p13.log.error(String(err instanceof Error ? err.message : err));
12180
12306
  }
12181
12307
  };
12182
12308
  try {
@@ -12257,7 +12383,7 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12257
12383
  }
12258
12384
  });
12259
12385
  proxyPort = proxyHandle.port;
12260
- p12.log.info(`Route audit (metadata only): ${routeAuditPath}`);
12386
+ p13.log.info(`Route audit (metadata only): ${routeAuditPath}`);
12261
12387
  } else if (favoritesActive && resolvedFavorites.length > 0) {
12262
12388
  const needsBackend = (r) => {
12263
12389
  const m = r.model;
@@ -12344,8 +12470,8 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12344
12470
  try {
12345
12471
  await launchOrRestartCodexApp(void 0, opts.assumeYes);
12346
12472
  } catch (err) {
12347
- p12.log.warn(String(err instanceof Error ? err.message : err));
12348
- p12.log.info(codexAppInstallHint());
12473
+ p13.log.warn(String(err instanceof Error ? err.message : err));
12474
+ p13.log.info(codexAppInstallHint());
12349
12475
  throw err;
12350
12476
  }
12351
12477
  printCodexAppSessionPanel({
@@ -12363,28 +12489,29 @@ Mixed Codex App mode is unavailable: ${err instanceof Error ? err.message : err}
12363
12489
  isAppRunning: isCodexAppRunning,
12364
12490
  quitApp: quitCodexAppGracefully,
12365
12491
  waitForAppExit: () => waitForCodexAppQuit(),
12492
+ ...process.platform === "win32" ? { forceQuitApp: forceQuitCodexApp } : {},
12366
12493
  restoreOverlay,
12367
12494
  closeResources
12368
12495
  });
12369
- p12.log.success(result.message);
12496
+ p13.log.success(result.message);
12370
12497
  return 0;
12371
12498
  } catch (err) {
12372
12499
  shutdownFailed = true;
12373
- p12.log.error(String(err instanceof Error ? err.message : err));
12500
+ p13.log.error(String(err instanceof Error ? err.message : err));
12374
12501
  return 1;
12375
12502
  }
12376
12503
  } finally {
12377
12504
  if (sessionActive && !isCodexAppRunning()) restoreOverlaySafely();
12378
12505
  closeResources();
12379
12506
  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.");
12507
+ p13.log.error("ChatGPT Desktop is still running; config restoration was skipped. Close Desktop, then run relay-ai codex-app --restore.");
12381
12508
  }
12382
12509
  }
12383
12510
  }
12384
12511
 
12385
12512
  // src/claude-app.ts
12386
12513
  import pc11 from "picocolors";
12387
- import * as p13 from "@clack/prompts";
12514
+ import * as p14 from "@clack/prompts";
12388
12515
 
12389
12516
  // src/claude-desktop/app-config.ts
12390
12517
  import { existsSync as existsSync10, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync6 } from "fs";
@@ -12811,10 +12938,10 @@ async function runClaudeAppCommand(args, boot) {
12811
12938
  return 1;
12812
12939
  }
12813
12940
  if (hasStaleSession()) {
12814
- p13.log.warn("Recovered from an interrupted claude-app session.");
12941
+ p14.log.warn("Recovered from an interrupted claude-app session.");
12815
12942
  recoverSession();
12816
12943
  }
12817
- const catalogSpinner = p13.spinner();
12944
+ const catalogSpinner = p14.spinner();
12818
12945
  catalogSpinner.start("Loading your providers...");
12819
12946
  let catalog;
12820
12947
  try {
@@ -12827,7 +12954,7 @@ async function runClaudeAppCommand(args, boot) {
12827
12954
  catalogSpinner.stop("");
12828
12955
  const compatible = codexCompatibleProviders(providersForPicker(catalog), "claude-app");
12829
12956
  if (compatible.length === 0) {
12830
- p13.log.warn("No compatible providers in your registry.");
12957
+ p14.log.warn("No compatible providers in your registry.");
12831
12958
  return 0;
12832
12959
  }
12833
12960
  const prefs = loadPreferences();
@@ -12844,7 +12971,7 @@ async function runClaudeAppCommand(args, boot) {
12844
12971
  providerForClaudePicker
12845
12972
  );
12846
12973
  if ("error" in bootSelection) {
12847
- p13.log.error(bootSelection.error);
12974
+ p14.log.error(bootSelection.error);
12848
12975
  return 1;
12849
12976
  }
12850
12977
  activeProvider = bootSelection.provider;
@@ -12856,7 +12983,7 @@ async function runClaudeAppCommand(args, boot) {
12856
12983
  useFavorites = true;
12857
12984
  const firstFavorite = resolveFirstAvailableFavorite(favorites, compatible);
12858
12985
  if (!firstFavorite) {
12859
- p13.log.warn("No saved Claude App favorites are currently available.");
12986
+ p14.log.warn("No saved Claude App favorites are currently available.");
12860
12987
  return 0;
12861
12988
  }
12862
12989
  activeProvider = firstFavorite.provider;
@@ -12869,7 +12996,7 @@ async function runClaudeAppCommand(args, boot) {
12869
12996
  }
12870
12997
  }
12871
12998
  if (!activeProvider || !selectedModel) {
12872
- p13.log.error("No Claude App launch model was selected.");
12999
+ p14.log.error("No Claude App launch model was selected.");
12873
13000
  return 1;
12874
13001
  }
12875
13002
  const catalogResolution = await resolveClaudeAppCatalog(
@@ -12879,16 +13006,16 @@ async function runClaudeAppCommand(args, boot) {
12879
13006
  favorites
12880
13007
  );
12881
13008
  if (!catalogResolution.ok) {
12882
- p13.log.error(catalogResolution.error);
13009
+ p14.log.error(catalogResolution.error);
12883
13010
  return 1;
12884
13011
  }
12885
13012
  if (catalogResolution.droppedFavorites.length > 0) {
12886
13013
  const skipped = catalogResolution.droppedFavorites.map((favorite) => `${favorite.providerId}/${favorite.modelId}`).join(", ");
12887
- p13.log.warn(`Skipped unavailable or unauthorized favorite(s): ${skipped}`);
13014
+ p14.log.warn(`Skipped unavailable or unauthorized favorite(s): ${skipped}`);
12888
13015
  }
12889
13016
  if (catalogResolution.capacitySkippedFavorites.length > 0) {
12890
13017
  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}`);
13018
+ p14.log.warn(`Skipped favorite(s) beyond the 20-model catalog limit: ${skipped}`);
12892
13019
  }
12893
13020
  let cloudCodeBackend = null;
12894
13021
  let proxyHandle = null;
@@ -12937,7 +13064,7 @@ ${pc11.green("\u2714")} Proxy started on port ${proxyHandle.port}`);
12937
13064
  try {
12938
13065
  await launchOrRestartClaudeApp();
12939
13066
  } catch (err) {
12940
- p13.log.warn(String(err instanceof Error ? err.message : err));
13067
+ p14.log.warn(String(err instanceof Error ? err.message : err));
12941
13068
  }
12942
13069
  console.log(`
12943
13070
  ${pc11.bold("Claude Desktop 3P Mode Active")}`);
@@ -12953,8 +13080,8 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
12953
13080
  sessionActive = false;
12954
13081
  if (cloudCodeBackend) cloudCodeBackend.handle.close();
12955
13082
  if (isClaudeAppRunning()) {
12956
- const shouldClose = await p13.confirm({ message: "Claude Desktop is still running. Close it?" });
12957
- if (shouldClose && !p13.isCancel(shouldClose)) {
13083
+ const shouldClose = await p14.confirm({ message: "Claude Desktop is still running. Close it?" });
13084
+ if (shouldClose && !p14.isCancel(shouldClose)) {
12958
13085
  quitClaudeAppGracefully();
12959
13086
  }
12960
13087
  }
@@ -12965,7 +13092,7 @@ ${pc11.bold("Claude Desktop 3P Mode Active")}`);
12965
13092
  cleanupSession(uuid);
12966
13093
  }
12967
13094
  if (cloudCodeBackend) cloudCodeBackend.handle.close();
12968
- p13.log.error(String(err instanceof Error ? err.message : err));
13095
+ p14.log.error(String(err instanceof Error ? err.message : err));
12969
13096
  return 1;
12970
13097
  }
12971
13098
  }
@@ -13013,7 +13140,7 @@ function skillInstallTargets() {
13013
13140
  });
13014
13141
  }
13015
13142
  function formatProviderModels(provider) {
13016
- const models = provider.modelsCache?.models ?? [];
13143
+ const models = getProviderModels(provider);
13017
13144
  if (models.length === 0) return ` (no cached models \u2014 run: relay-ai providers refresh-models ${provider.id})`;
13018
13145
  const lines = models.slice(0, 40).map((m) => ` ${m.id}${m.name !== m.id ? ` (${m.name})` : ""}`);
13019
13146
  if (models.length > 40) lines.push(` ... and ${models.length - 40} more`);
@@ -13022,7 +13149,7 @@ function formatProviderModels(provider) {
13022
13149
  function buildLiveStateSection() {
13023
13150
  const prefs = loadPreferences();
13024
13151
  const registry = loadRegistry();
13025
- const enabled = registry.providers.filter((p15) => p15.enabled);
13152
+ const enabled = registry.providers.filter((p16) => p16.enabled);
13026
13153
  const prefLines = [];
13027
13154
  if (prefs.lastProvider || prefs.lastModel) {
13028
13155
  prefLines.push(` Claude last launch: provider=${prefs.lastProvider ?? "(none)"} model=${prefs.lastModel ?? "(none)"}`);
@@ -13039,9 +13166,9 @@ function buildLiveStateSection() {
13039
13166
  prefLines.push(` ${f.providerId} / ${f.modelId}`);
13040
13167
  }
13041
13168
  }
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)
13169
+ const providerBlocks = enabled.length === 0 ? [" No registry providers configured. Built-in cloud: zen, go (OpenCode Zen/Go)."] : enabled.map((p16) => [
13170
+ ` ${p16.name} (${p16.id}) \u2014 ${getProviderModels(p16).length} model(s)`,
13171
+ formatProviderModels(p16)
13045
13172
  ].join("\n"));
13046
13173
  return `
13047
13174
  ================================================================================
@@ -15115,20 +15242,20 @@ ${pc12.bold("Examples:")}
15115
15242
  relay-ai antigravity
15116
15243
  relay-ai antigravity --provider zen --model deepseek-v4-flash-free`;
15117
15244
  }
15118
- function printHelp(text5) {
15245
+ function printHelp(text6) {
15119
15246
  console.log(`
15120
- ${text5}
15247
+ ${text6}
15121
15248
  `);
15122
15249
  }
15123
15250
  async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindow, trace, claudeArgs) {
15124
15251
  let proxyHandle;
15125
15252
  try {
15126
15253
  proxyHandle = await startProxyCatalog(catalogRoutes, startingRoute.aliasId, trace);
15127
- p14.log.info(
15254
+ p15.log.info(
15128
15255
  `Switch menu active \u2014 proxy on port ${proxyHandle.port} ` + pc12.dim(`(${catalogRoutes.length} model${catalogRoutes.length !== 1 ? "s" : ""} in /model)`)
15129
15256
  );
15130
15257
  } catch (err) {
15131
- p14.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
15258
+ p15.log.error(`Failed to start proxy: ${err instanceof Error ? err.message : String(err)}`);
15132
15259
  return 1;
15133
15260
  }
15134
15261
  const childEnv = buildChildEnv(
@@ -15141,7 +15268,7 @@ async function launchClaudeViaCatalog(catalogRoutes, startingRoute, contextWindo
15141
15268
  );
15142
15269
  const debugLogPath = prepareClaudeTraceLog();
15143
15270
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
15144
- if (trace) p14.log.info(`Debug log: ${debugLogPath}`);
15271
+ if (trace) p15.log.info(`Debug log: ${debugLogPath}`);
15145
15272
  const exitCode = await launchClaude(
15146
15273
  childEnv,
15147
15274
  claudeCodeClientModelId(startingRoute.aliasId, contextWindow),
@@ -15161,10 +15288,10 @@ async function runModelsCommand(opts = {}) {
15161
15288
  const listItemLabel = subagentScope ? "Codex SubAgent model" : scope === "agy" ? "Antigravity favorite" : "favorite";
15162
15289
  const configKey = scope === "agy" ? "antigravityCliFavoriteModels" : scope === "codex-subagents" ? "codexSubagentModels" : "favoriteModels";
15163
15290
  relayIntro(scopeName);
15164
- const spinner9 = p14.spinner();
15165
- spinner9.start("Loading providers...");
15291
+ const spinner10 = p15.spinner();
15292
+ spinner10.start("Loading providers...");
15166
15293
  const catalog = await fetchProviderCatalog();
15167
- spinner9.stop("");
15294
+ spinner10.stop("");
15168
15295
  const pickedProviders = providersForPicker(catalog);
15169
15296
  const allProviders = scope === "agy" ? providersForTarget(pickedProviders, "antigravity") : scope === "codex-subagents" ? providersForCodexSubagents(pickedProviders) : pickedProviders;
15170
15297
  const favoriteProviders = allProviders.map((provider) => ({
@@ -15172,8 +15299,8 @@ async function runModelsCommand(opts = {}) {
15172
15299
  name: favoriteProviderDisplayName(provider)
15173
15300
  }));
15174
15301
  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(".")}`);
15302
+ p15.log.warn("No providers found.");
15303
+ p15.log.info(`${pc12.dim("OpenCode Zen/Go is always available. Add providers with ")}${pc12.cyan("relay-ai providers")}${pc12.dim(".")}`);
15177
15304
  relayOutro("Done");
15178
15305
  return 0;
15179
15306
  }
@@ -15202,19 +15329,19 @@ async function runModelsCommand(opts = {}) {
15202
15329
  });
15203
15330
  options.push({ value: "__done__", label: "Done", hint: "" });
15204
15331
  const header = favorites.length === 0 ? `${scopeName} (0/${maxFavorites})` : `${scopeName} (${favorites.length}/${maxFavorites}) \u2014 select to remove`;
15205
- const choice = await p14.select({
15332
+ const choice = await p15.select({
15206
15333
  message: header,
15207
15334
  options,
15208
15335
  initialValue: "__done__"
15209
15336
  });
15210
- if (p14.isCancel(choice) || choice === "__done__") break;
15337
+ if (p15.isCancel(choice) || choice === "__done__") break;
15211
15338
  if (choice === "__add__") {
15212
15339
  if (atCap) {
15213
- p14.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 remove one first.`);
15340
+ p15.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 remove one first.`);
15214
15341
  continue;
15215
15342
  }
15216
15343
  const globalCount = buildGlobalFavoriteIndex(favoriteProviders).length;
15217
- const addPath = await p14.select({
15344
+ const addPath = await p15.select({
15218
15345
  message: subagentScope ? "Add a Codex SubAgent model" : "Add a favorite",
15219
15346
  options: [
15220
15347
  {
@@ -15234,7 +15361,7 @@ async function runModelsCommand(opts = {}) {
15234
15361
  }
15235
15362
  ]
15236
15363
  });
15237
- if (p14.isCancel(addPath)) continue;
15364
+ if (p15.isCancel(addPath)) continue;
15238
15365
  let provider;
15239
15366
  let browsedMultiple = [];
15240
15367
  if (addPath === "global") {
@@ -15257,26 +15384,26 @@ async function runModelsCommand(opts = {}) {
15257
15384
  let currentInitialProvider = void 0;
15258
15385
  while (true) {
15259
15386
  const providerOptions = favoriteProviders.map((ap) => providerSelectOption(ap));
15260
- const pickedProviderId = await p14.select({
15387
+ const pickedProviderId = await p15.select({
15261
15388
  message: "Which provider?",
15262
15389
  options: providerOptions,
15263
15390
  initialValue: currentInitialProvider
15264
15391
  });
15265
- if (p14.isCancel(pickedProviderId)) break;
15392
+ if (p15.isCancel(pickedProviderId)) break;
15266
15393
  provider = favoriteProviders.find((ap) => ap.id === pickedProviderId);
15267
15394
  let modelsToPick = provider.models;
15268
15395
  if (provider.models.length > MODEL_SEARCH_THRESHOLD) {
15269
- const searchInput = await p14.text({
15396
+ const searchInput = await p15.text({
15270
15397
  message: `Search ${provider.name} models (${provider.models.length} available):`,
15271
15398
  placeholder: "e.g. flash 3.6, claude, llama"
15272
15399
  });
15273
- if (p14.isCancel(searchInput)) {
15400
+ if (p15.isCancel(searchInput)) {
15274
15401
  currentInitialProvider = provider.id;
15275
15402
  continue;
15276
15403
  }
15277
15404
  modelsToPick = filterModelsBySearch(provider.models, String(searchInput));
15278
15405
  if (modelsToPick.length === 0) {
15279
- p14.log.warn("No models match \u2014 try a different search");
15406
+ p15.log.warn("No models match \u2014 try a different search");
15280
15407
  currentInitialProvider = provider.id;
15281
15408
  continue;
15282
15409
  }
@@ -15290,12 +15417,12 @@ async function runModelsCommand(opts = {}) {
15290
15417
  hint: favorited ? pc12.yellow(`\u2605 already in ${listLabel}`) : ""
15291
15418
  };
15292
15419
  });
15293
- const pickedModelIds = await p14.multiselect({
15420
+ const pickedModelIds = await p15.multiselect({
15294
15421
  message: `Select models to add from ${provider.name} ${pc12.dim("(Space to select, Enter to confirm)")}`,
15295
15422
  options: options2,
15296
15423
  required: false
15297
15424
  });
15298
- if (p14.isCancel(pickedModelIds)) {
15425
+ if (p15.isCancel(pickedModelIds)) {
15299
15426
  currentInitialProvider = provider.id;
15300
15427
  continue;
15301
15428
  }
@@ -15330,27 +15457,27 @@ async function runModelsCommand(opts = {}) {
15330
15457
  if (addedModels.length > 0) {
15331
15458
  if (addedModels.length === 1) {
15332
15459
  const modelName = addedModels[0].name || addedModels[0].id;
15333
- p14.log.success(`Added ${modelName} (${provider.name}) to ${listLabel}.`);
15460
+ p15.log.success(`Added ${modelName} (${provider.name}) to ${listLabel}.`);
15334
15461
  } else {
15335
- p14.log.success(`Added ${addedModels.length} models from ${provider.name} to ${listLabel}.`);
15462
+ p15.log.success(`Added ${addedModels.length} models from ${provider.name} to ${listLabel}.`);
15336
15463
  }
15337
15464
  }
15338
15465
  if (duplicateCount > 0) {
15339
- p14.log.warn(`${duplicateCount} selected model(s) were already in ${listLabel}.`);
15466
+ p15.log.warn(`${duplicateCount} selected model(s) were already in ${listLabel}.`);
15340
15467
  }
15341
15468
  if (limitReached) {
15342
- p14.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 some selected models could not be added.`);
15469
+ p15.log.warn(`Limit of ${maxFavorites} ${subagentScope ? "Codex SubAgent" : "favorites"} reached \u2014 some selected models could not be added.`);
15343
15470
  }
15344
15471
  } else if (choice.startsWith("fav-")) {
15345
15472
  const idx = parseInt(choice.slice(4), 10);
15346
15473
  const fav = favorites[idx];
15347
15474
  const entry = modelLookup.get(`${fav.providerId}:${fav.modelId}`);
15348
15475
  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;
15476
+ const confirmed = await p15.confirm({ message: `Remove ${label} from ${listLabel}?` });
15477
+ if (p15.isCancel(confirmed) || !confirmed) continue;
15351
15478
  favorites = removeFavorite(favorites, fav);
15352
15479
  favoritesDirty = true;
15353
- p14.log.success(`Removed ${label} from ${listLabel}.`);
15480
+ p15.log.success(`Removed ${label} from ${listLabel}.`);
15354
15481
  }
15355
15482
  }
15356
15483
  if (favoritesDirty) {
@@ -15402,7 +15529,7 @@ Error: ${launchPlan.error}
15402
15529
  }
15403
15530
  if (!agentStdout) relayIntro("Claude Code");
15404
15531
  if (setup && !dryRun && !agentStdout) {
15405
- p14.log.info("Provider setup now lives in relay-ai providers \u2014 opening that next is recommended.");
15532
+ p15.log.info("Provider setup now lives in relay-ai providers \u2014 opening that next is recommended.");
15406
15533
  }
15407
15534
  if (!httpProxyOnly && !dryRun && await needsFirstRunSetup()) {
15408
15535
  const firstRun = await runFirstRunWizard(trace);
@@ -15417,7 +15544,7 @@ Error: ${launchPlan.error}
15417
15544
  return 1;
15418
15545
  }
15419
15546
  } else {
15420
- const catalogSpinner = p14.spinner();
15547
+ const catalogSpinner = p15.spinner();
15421
15548
  catalogSpinner.start("Loading your providers...");
15422
15549
  try {
15423
15550
  catalog = await fetchProviderCatalog();
@@ -15430,8 +15557,8 @@ Error: ${launchPlan.error}
15430
15557
  }
15431
15558
  const allProviders = providersForTarget(providersForPicker(catalog), "claude");
15432
15559
  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."));
15560
+ p15.log.warn("No providers available.");
15561
+ p15.log.info(pc12.dim("Run relay-ai providers add or import to get started."));
15435
15562
  return 0;
15436
15563
  }
15437
15564
  const runTransparentProxy = async (selected) => {
@@ -15449,7 +15576,7 @@ Error: ${launchPlan.error}
15449
15576
  }
15450
15577
  const debugLogPath2 = prepareClaudeTraceLog();
15451
15578
  const traceArgs2 = trace ? ["--debug-file", debugLogPath2] : [];
15452
- if (trace && !agentStdout) p14.log.info(`Debug log: ${debugLogPath2}`);
15579
+ if (trace && !agentStdout) p15.log.info(`Debug log: ${debugLogPath2}`);
15453
15580
  try {
15454
15581
  const result = await launchClaudeWithHttpProxy({
15455
15582
  providers: catalog,
@@ -15461,26 +15588,26 @@ Error: ${launchPlan.error}
15461
15588
  onProxyReady: (proxy) => {
15462
15589
  if (agentStdout) return;
15463
15590
  const count = proxy.handle.modelIds.length;
15464
- p14.log.info(
15591
+ p15.log.info(
15465
15592
  count === 0 ? "Secure Anthropic passthrough ready; no compatible Relay models were added." : `Secure Anthropic passthrough ready with ${count} Relay model${count === 1 ? "" : "s"}.`
15466
15593
  );
15467
- for (const modelId of proxy.handle.modelIds) p14.log.message(pc12.dim(` /model ${modelId}`));
15594
+ for (const modelId of proxy.handle.modelIds) p15.log.message(pc12.dim(` /model ${modelId}`));
15468
15595
  }
15469
15596
  });
15470
15597
  if (!agentStdout && result.proxy.loaded.unavailable.length > 0) {
15471
- p14.log.warn(
15598
+ p15.log.warn(
15472
15599
  `${result.proxy.loaded.unavailable.length} favorite${result.proxy.loaded.unavailable.length === 1 ? "" : "s"} unavailable or missing credentials.`
15473
15600
  );
15474
15601
  }
15475
15602
  if (!agentStdout && result.proxy.loaded.unsupported.length > 0) {
15476
- p14.log.warn(
15603
+ p15.log.warn(
15477
15604
  `${result.proxy.loaded.unsupported.length} incompatible favorite${result.proxy.loaded.unsupported.length === 1 ? "" : "s"} skipped.`
15478
15605
  );
15479
15606
  }
15480
15607
  if (trace) printTraceLog(debugLogPath2);
15481
15608
  return result.exitCode;
15482
15609
  } catch (error) {
15483
- p14.log.error(
15610
+ p15.log.error(
15484
15611
  `Could not start secure Anthropic + Relay mode: ${error instanceof Error ? error.message : String(error)}`
15485
15612
  );
15486
15613
  return 1;
@@ -15501,7 +15628,7 @@ Error: ${launchPlan.error}
15501
15628
  if (launchPlan.skip && launchPlan.target) {
15502
15629
  const resolved = findProviderAndModel(allProviders, launchPlan.target);
15503
15630
  if (!resolved) {
15504
- p14.log.error(
15631
+ p15.log.error(
15505
15632
  `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
15506
15633
  );
15507
15634
  return 1;
@@ -15509,19 +15636,19 @@ Error: ${launchPlan.error}
15509
15636
  activeProvider = resolved.provider;
15510
15637
  selectedModel = resolved.model;
15511
15638
  if (!agentStdout) {
15512
- p14.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
15639
+ p15.log.step(`Using ${selectedModel.name || selectedModel.id} (${activeProvider.name})`);
15513
15640
  }
15514
15641
  if (!dryRun) recordLaunchSelection("claude", activeProvider.id, selectedModel.id, prefs);
15515
15642
  } else {
15516
15643
  let currentInitialProvider = initialProvider;
15517
15644
  while (true) {
15518
- const chosen = await p14.select({
15645
+ const chosen = await p15.select({
15519
15646
  message: "Which provider?",
15520
15647
  options: providerOptions,
15521
15648
  initialValue: currentInitialProvider
15522
15649
  });
15523
- if (p14.isCancel(chosen)) {
15524
- p14.cancel("Cancelled.");
15650
+ if (p15.isCancel(chosen)) {
15651
+ p15.cancel("Cancelled.");
15525
15652
  return 0;
15526
15653
  }
15527
15654
  const providerChoice = chosen;
@@ -15533,7 +15660,7 @@ Error: ${launchPlan.error}
15533
15660
  if (prov && mod) available.push({ provider: prov, model: mod });
15534
15661
  }
15535
15662
  if (available.length === 0) {
15536
- p14.log.warn("No saved favorites are currently available.");
15663
+ p15.log.warn("No saved favorites are currently available.");
15537
15664
  return 0;
15538
15665
  }
15539
15666
  const favOptions = available.map((f, i) => ({
@@ -15541,13 +15668,13 @@ Error: ${launchPlan.error}
15541
15668
  label: `${f.model.name || f.model.id} \u2014 ${f.provider.name}`,
15542
15669
  hint: f.model.id
15543
15670
  }));
15544
- const pickedIdx = await p14.select({
15671
+ const pickedIdx = await p15.select({
15545
15672
  message: "Starting model?",
15546
15673
  options: favOptions,
15547
15674
  initialValue: "0"
15548
15675
  });
15549
- if (p14.isCancel(pickedIdx)) {
15550
- p14.cancel("Cancelled.");
15676
+ if (p15.isCancel(pickedIdx)) {
15677
+ p15.cancel("Cancelled.");
15551
15678
  return 0;
15552
15679
  }
15553
15680
  const sel = available[Number(pickedIdx)];
@@ -15571,13 +15698,13 @@ Error: ${launchPlan.error}
15571
15698
  }
15572
15699
  let useHttpProxy = Boolean(httpProxy);
15573
15700
  if (!httpProxy && !launchPlan.skip && supportsClaudeTransparentMode(selectedModel)) {
15574
- const transparentChoice = await p14.select({
15701
+ const transparentChoice = await p15.select({
15575
15702
  message: "Keep your normal Claude models available too?",
15576
15703
  options: claudeTransparentModeOptions(selectedModel.name || selectedModel.id),
15577
15704
  initialValue: prefs.lastClaudeTransparentMode ?? true
15578
15705
  });
15579
- if (p14.isCancel(transparentChoice)) {
15580
- p14.cancel("Cancelled.");
15706
+ if (p15.isCancel(transparentChoice)) {
15707
+ p15.cancel("Cancelled.");
15581
15708
  return 0;
15582
15709
  }
15583
15710
  useHttpProxy = transparentChoice;
@@ -15596,12 +15723,12 @@ Error: ${launchPlan.error}
15596
15723
  );
15597
15724
  const startingRoute = resolveRoute(activeProvider.id, selectedModel.id) ?? null;
15598
15725
  if (!startingRoute) {
15599
- p14.log.error("Could not resolve a proxy route for the selected model.");
15726
+ p15.log.error("Could not resolve a proxy route for the selected model.");
15600
15727
  return 1;
15601
15728
  }
15602
15729
  const { routes: catalogRoutes, droppedFavorites } = buildCatalogRoutes(startingRoute, favorites, resolveRoute);
15603
15730
  if (droppedFavorites.length > 0) {
15604
- p14.log.warn(
15731
+ p15.log.warn(
15605
15732
  `Skipping ${droppedFavorites.length} favorite${droppedFavorites.length === 1 ? "" : "s"} that are no longer available in /model`
15606
15733
  );
15607
15734
  }
@@ -15646,7 +15773,7 @@ Error: ${launchPlan.error}
15646
15773
  }
15647
15774
  const launchApiKey = await resolveLocalProviderApiKey(activeProvider);
15648
15775
  if (!launchApiKey?.trim()) {
15649
- p14.log.error(
15776
+ p15.log.error(
15650
15777
  `No credential found for ${activeProvider.name}. Add a key with relay-ai providers or set OPENCODE_API_KEY.`
15651
15778
  );
15652
15779
  return 1;
@@ -15670,9 +15797,9 @@ Error: ${launchPlan.error}
15670
15797
  },
15671
15798
  launchApiKey
15672
15799
  );
15673
- if (!isAgentStdoutMode()) p14.log.info(`Cloud Code proxy started on port ${proxyHandle.port}`);
15800
+ if (!isAgentStdoutMode()) p15.log.info(`Cloud Code proxy started on port ${proxyHandle.port}`);
15674
15801
  } catch (err) {
15675
- p14.log.error(`Failed to start Cloud Code proxy: ${err instanceof Error ? err.message : String(err)}`);
15802
+ p15.log.error(`Failed to start Cloud Code proxy: ${err instanceof Error ? err.message : String(err)}`);
15676
15803
  return 1;
15677
15804
  }
15678
15805
  childEnv = buildChildEnv(
@@ -15698,9 +15825,9 @@ Error: ${launchPlan.error}
15698
15825
  },
15699
15826
  launchApiKey
15700
15827
  );
15701
- if (!isAgentStdoutMode()) p14.log.info(`OAuth proxy started on port ${proxyHandle.port}`);
15828
+ if (!isAgentStdoutMode()) p15.log.info(`OAuth proxy started on port ${proxyHandle.port}`);
15702
15829
  } catch (err) {
15703
- p14.log.error(`Failed to start OAuth proxy: ${err instanceof Error ? err.message : String(err)}`);
15830
+ p15.log.error(`Failed to start OAuth proxy: ${err instanceof Error ? err.message : String(err)}`);
15704
15831
  return 1;
15705
15832
  }
15706
15833
  childEnv = buildChildEnv(
@@ -15743,12 +15870,12 @@ Error: ${launchPlan.error}
15743
15870
  launchApiKey
15744
15871
  );
15745
15872
  if (!isAgentStdoutMode()) {
15746
- p14.log.info(
15873
+ p15.log.info(
15747
15874
  `SDK adapter proxy started on port ${proxyHandle.port}` + (selectedModel.npm ? pc12.dim(` (${selectedModel.npm})`) : "")
15748
15875
  );
15749
15876
  }
15750
15877
  } catch (err) {
15751
- p14.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
15878
+ p15.log.error(`Failed to start SDK adapter proxy: ${err instanceof Error ? err.message : String(err)}`);
15752
15879
  return 1;
15753
15880
  }
15754
15881
  childEnv = buildChildEnv(
@@ -15764,7 +15891,7 @@ Error: ${launchPlan.error}
15764
15891
  }
15765
15892
  const debugLogPath = prepareClaudeTraceLog();
15766
15893
  const traceArgs = trace ? ["--debug-file", debugLogPath] : [];
15767
- if (trace) p14.log.info(`Debug log: ${debugLogPath}`);
15894
+ if (trace) p15.log.info(`Debug log: ${debugLogPath}`);
15768
15895
  const exitCode = await launchClaude(
15769
15896
  childEnv,
15770
15897
  claudeCodeClientModelId(selectedModel.id, selectedModel.contextWindow),
@@ -15791,7 +15918,7 @@ Error: ${parsed.error}
15791
15918
  printHelp(rootHelpText());
15792
15919
  return 1;
15793
15920
  }
15794
- if (!parsed.showVersion && !parsed.showAi) {
15921
+ if (shouldRefreshModelsDev(parsed)) {
15795
15922
  refreshModelsDevCacheAsync();
15796
15923
  }
15797
15924
  if (parsed.command === "root") {
@@ -15846,7 +15973,7 @@ Options:
15846
15973
  --trace Write debug logs under ~/.relay-ai/logs/`);
15847
15974
  return 0;
15848
15975
  }
15849
- const { runUiCommand } = await import("./ui-command-RJVKDGGG.js");
15976
+ const { runUiCommand } = await import("./ui-command-WKPJI2CU.js");
15850
15977
  return runUiCommand({ trace: parsed.trace, serverMode: parsed.uiServerMode });
15851
15978
  }
15852
15979
  if (parsed.command === "models") {
@@ -15984,6 +16111,9 @@ Options:
15984
16111
  }
15985
16112
  return runClaudeCommand(parsed);
15986
16113
  }
16114
+ function shouldRefreshModelsDev(parsed) {
16115
+ return !parsed.showVersion && !parsed.showAi && !parsed.showHelp && !parsed.claudeArgs.includes("--restore");
16116
+ }
15987
16117
  function isCliEntryPoint() {
15988
16118
  if (!process.argv[1]) return false;
15989
16119
  try {
@@ -16019,6 +16149,7 @@ export {
16019
16149
  rootHelpText,
16020
16150
  runClaudeCommand,
16021
16151
  runModelsCommand,
16022
- serverHelpText
16152
+ serverHelpText,
16153
+ shouldRefreshModelsDev
16023
16154
  };
16024
16155
  //# sourceMappingURL=cli.js.map