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