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