@bman654/clodex 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/{chunk-W4SVDQCZ.js → chunk-MRO3KE3P.js} +510 -101
- package/dist/chunk-MRO3KE3P.js.map +1 -0
- package/dist/claude-wrapper.js +19 -1
- package/dist/claude-wrapper.js.map +1 -1
- package/dist/cli.js +1745 -246
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +15 -0
- package/package.json +1 -1
- package/dist/chunk-W4SVDQCZ.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CHILD_NETWORK_ENV_VARS,
|
|
4
|
+
NETWORK_ENV_CONTRACT_VAR,
|
|
5
|
+
OAUTH_ACCOUNT_ENV,
|
|
6
|
+
OAUTH_ACCOUNT_NAME_RE,
|
|
7
|
+
REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT,
|
|
3
8
|
assertRegistryWriteOwnership,
|
|
9
|
+
clearActiveOAuthAccount,
|
|
4
10
|
ensureSecureAppHome,
|
|
5
11
|
findClaudeBinary,
|
|
6
12
|
getAppHome,
|
|
@@ -11,6 +17,7 @@ import {
|
|
|
11
17
|
getInstalledClaudeVersion,
|
|
12
18
|
getLocalPatchesPath,
|
|
13
19
|
getLogsPath,
|
|
20
|
+
getOAuthAccountSlot,
|
|
14
21
|
getSavedServerPassword,
|
|
15
22
|
getServerExposedProviders,
|
|
16
23
|
getServerFavoritesOnly,
|
|
@@ -18,12 +25,15 @@ import {
|
|
|
18
25
|
getServerMaskGatewayIds,
|
|
19
26
|
isDiscoveryDisabled,
|
|
20
27
|
isValidProviderId,
|
|
21
|
-
launchClaude,
|
|
22
28
|
listenTcpServer,
|
|
23
29
|
loadPreferences,
|
|
24
30
|
loadRegistry,
|
|
25
31
|
loadRegistryStrict,
|
|
32
|
+
networkEnvBaseline,
|
|
33
|
+
providerDefaultAuthRef,
|
|
34
|
+
readLiveServerRuntimeStates,
|
|
26
35
|
recordLaunchSelection,
|
|
36
|
+
recordNetworkEnvMutation,
|
|
27
37
|
registerServerRuntimeState,
|
|
28
38
|
removeAnthropicProxyBypass,
|
|
29
39
|
resolveBridgeMode,
|
|
@@ -34,13 +44,14 @@ import {
|
|
|
34
44
|
setServerFavoritesOnly,
|
|
35
45
|
setServerListenMode,
|
|
36
46
|
setServerMaskGatewayIds,
|
|
47
|
+
storeActiveOAuthAccount,
|
|
37
48
|
tcpListenerUrlHost,
|
|
38
49
|
unregisterServerRuntimeState,
|
|
39
50
|
withCredentialMutationLock,
|
|
40
51
|
withProviderMutationLock,
|
|
41
52
|
withRegistryWriteLock,
|
|
42
53
|
withRegistryWriteLockSync
|
|
43
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-MRO3KE3P.js";
|
|
44
55
|
|
|
45
56
|
// src/cli.ts
|
|
46
57
|
import pc13 from "picocolors";
|
|
@@ -109,8 +120,8 @@ function fmtProvider(name) {
|
|
|
109
120
|
}
|
|
110
121
|
function fmtProviderBracket(providerId, providerName, isFree) {
|
|
111
122
|
const color = providerTagColor(providerId);
|
|
112
|
-
const
|
|
113
|
-
return color(pc.bold(`(${
|
|
123
|
+
const text5 = isFree ? `${providerName} \xB7 free` : providerName;
|
|
124
|
+
return color(pc.bold(`(${text5})`));
|
|
114
125
|
}
|
|
115
126
|
function providerTagColor(providerId) {
|
|
116
127
|
switch (providerId) {
|
|
@@ -211,6 +222,151 @@ import * as p12 from "@clack/prompts";
|
|
|
211
222
|
import { realpathSync as realpathSync2 } from "fs";
|
|
212
223
|
import { fileURLToPath } from "url";
|
|
213
224
|
|
|
225
|
+
// src/launch.ts
|
|
226
|
+
import { spawn } from "child_process";
|
|
227
|
+
import { appendFileSync } from "fs";
|
|
228
|
+
|
|
229
|
+
// src/parent-notice.ts
|
|
230
|
+
import { writeSync } from "fs";
|
|
231
|
+
var MAX_NOTICE_CHARS = 2e3;
|
|
232
|
+
var CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
233
|
+
var activeSink = null;
|
|
234
|
+
var stderrErrorGuardInstalled = false;
|
|
235
|
+
function toNoticeLine(message) {
|
|
236
|
+
const flattened = message.replace(CONTROL_CHARS, " ").trimEnd();
|
|
237
|
+
const bounded = flattened.length > MAX_NOTICE_CHARS ? `${flattened.slice(0, MAX_NOTICE_CHARS)}...` : flattened;
|
|
238
|
+
return `${bounded}
|
|
239
|
+
`;
|
|
240
|
+
}
|
|
241
|
+
function guardStderrErrors() {
|
|
242
|
+
if (stderrErrorGuardInstalled) return;
|
|
243
|
+
stderrErrorGuardInstalled = true;
|
|
244
|
+
try {
|
|
245
|
+
process.stderr.on("error", () => {
|
|
246
|
+
});
|
|
247
|
+
} catch {
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function writeParentNoticeLines(lines) {
|
|
251
|
+
if (lines.length === 0) return;
|
|
252
|
+
guardStderrErrors();
|
|
253
|
+
try {
|
|
254
|
+
process.stderr.write(lines.join(""), () => {
|
|
255
|
+
});
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function writeParentNoticeLinesSync(lines) {
|
|
260
|
+
if (lines.length === 0) return;
|
|
261
|
+
try {
|
|
262
|
+
writeSync(2, lines.join(""));
|
|
263
|
+
} catch {
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function emitParentNotice(message) {
|
|
267
|
+
const line = toNoticeLine(message);
|
|
268
|
+
try {
|
|
269
|
+
if (activeSink) activeSink(line);
|
|
270
|
+
else writeParentNoticeLines([line]);
|
|
271
|
+
} catch {
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function installParentNoticeSink(sink) {
|
|
275
|
+
const previous = activeSink;
|
|
276
|
+
activeSink = sink;
|
|
277
|
+
let released = false;
|
|
278
|
+
return () => {
|
|
279
|
+
if (released) return;
|
|
280
|
+
released = true;
|
|
281
|
+
if (activeSink === sink) activeSink = previous;
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/launch.ts
|
|
286
|
+
var isWindows = process.platform === "win32";
|
|
287
|
+
var MAX_QUEUED_NOTICES = 50;
|
|
288
|
+
function buildClaudeArgs(model, extraArgs) {
|
|
289
|
+
return model ? ["--model", model, ...extraArgs] : [...extraArgs];
|
|
290
|
+
}
|
|
291
|
+
function launchClaude(env, model, extraArgs) {
|
|
292
|
+
return new Promise((resolve3) => {
|
|
293
|
+
const claudePath = findClaudeBinary();
|
|
294
|
+
const args = buildClaudeArgs(model, extraArgs);
|
|
295
|
+
const debugFileIdx = extraArgs.indexOf("--debug-file");
|
|
296
|
+
const debugLogPath = debugFileIdx !== -1 && extraArgs[debugFileIdx + 1] ? extraArgs[debugFileIdx + 1] : void 0;
|
|
297
|
+
const originalStdoutWrite = process.stdout.write;
|
|
298
|
+
const originalStderrWrite = process.stderr.write;
|
|
299
|
+
const muteWrite = (chunk, encoding, callback) => {
|
|
300
|
+
if (typeof encoding === "function") {
|
|
301
|
+
callback = encoding;
|
|
302
|
+
}
|
|
303
|
+
if (debugLogPath) {
|
|
304
|
+
try {
|
|
305
|
+
const str = typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk);
|
|
306
|
+
appendFileSync(debugLogPath, `[parent] ${str}`);
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (callback) callback();
|
|
311
|
+
return true;
|
|
312
|
+
};
|
|
313
|
+
process.stdout.write = muteWrite;
|
|
314
|
+
process.stderr.write = muteWrite;
|
|
315
|
+
const queuedNotices = [];
|
|
316
|
+
let droppedNotices = 0;
|
|
317
|
+
const releaseNoticeSink = installParentNoticeSink((line) => {
|
|
318
|
+
if (debugLogPath) {
|
|
319
|
+
try {
|
|
320
|
+
appendFileSync(debugLogPath, `[parent] ${line}`);
|
|
321
|
+
} catch {
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (queuedNotices.length < MAX_QUEUED_NOTICES) queuedNotices.push(line);
|
|
325
|
+
else droppedNotices += 1;
|
|
326
|
+
});
|
|
327
|
+
const takeQueuedNotices = () => {
|
|
328
|
+
const lines = queuedNotices.splice(0, queuedNotices.length);
|
|
329
|
+
if (droppedNotices > 0) {
|
|
330
|
+
lines.push(
|
|
331
|
+
`clodex: warning: and ${droppedNotices} further notice${droppedNotices === 1 ? "" : "s"} suppressed while Claude Code held the terminal.
|
|
332
|
+
`
|
|
333
|
+
);
|
|
334
|
+
droppedNotices = 0;
|
|
335
|
+
}
|
|
336
|
+
return lines;
|
|
337
|
+
};
|
|
338
|
+
const flushNoticesOnExit = () => {
|
|
339
|
+
writeParentNoticeLinesSync(takeQueuedNotices());
|
|
340
|
+
};
|
|
341
|
+
process.once("exit", flushNoticesOnExit);
|
|
342
|
+
const restore = () => {
|
|
343
|
+
releaseNoticeSink();
|
|
344
|
+
process.removeListener("exit", flushNoticesOnExit);
|
|
345
|
+
process.stdout.write = originalStdoutWrite;
|
|
346
|
+
process.stderr.write = originalStderrWrite;
|
|
347
|
+
writeParentNoticeLines(takeQueuedNotices());
|
|
348
|
+
};
|
|
349
|
+
const child = spawn(claudePath, args, {
|
|
350
|
+
stdio: "inherit",
|
|
351
|
+
env,
|
|
352
|
+
shell: isWindows
|
|
353
|
+
});
|
|
354
|
+
const forward = (signal) => {
|
|
355
|
+
child.kill(signal);
|
|
356
|
+
};
|
|
357
|
+
process.once("SIGINT", () => forward("SIGINT"));
|
|
358
|
+
process.once("SIGTERM", () => forward("SIGTERM"));
|
|
359
|
+
child.on("exit", (code) => {
|
|
360
|
+
restore();
|
|
361
|
+
resolve3(code ?? 0);
|
|
362
|
+
});
|
|
363
|
+
child.on("error", (err) => {
|
|
364
|
+
restore();
|
|
365
|
+
resolve3(1);
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
214
370
|
// src/constants.ts
|
|
215
371
|
import { homedir } from "os";
|
|
216
372
|
import { join } from "path";
|
|
@@ -218,7 +374,7 @@ import { join } from "path";
|
|
|
218
374
|
// package.json
|
|
219
375
|
var package_default = {
|
|
220
376
|
name: "@bman654/clodex",
|
|
221
|
-
version: "2.
|
|
377
|
+
version: "2.5.0",
|
|
222
378
|
publishConfig: {
|
|
223
379
|
access: "public"
|
|
224
380
|
},
|
|
@@ -351,7 +507,7 @@ import {
|
|
|
351
507
|
import { join as join2 } from "path";
|
|
352
508
|
|
|
353
509
|
// src/credential-helper.ts
|
|
354
|
-
import { spawn } from "child_process";
|
|
510
|
+
import { spawn as spawn2 } from "child_process";
|
|
355
511
|
import { createHash } from "crypto";
|
|
356
512
|
import { accessSync, constants, statSync } from "fs";
|
|
357
513
|
import { isAbsolute, normalize, resolve } from "path";
|
|
@@ -418,7 +574,7 @@ async function runCredentialHelper(operation, account, input, expectedHelperId)
|
|
|
418
574
|
);
|
|
419
575
|
}
|
|
420
576
|
return new Promise((resolve3, reject) => {
|
|
421
|
-
const child =
|
|
577
|
+
const child = spawn2(
|
|
422
578
|
helper.path,
|
|
423
579
|
[operation, CREDENTIAL_HELPER_SERVICE, account],
|
|
424
580
|
{ shell: false, stdio: ["pipe", "pipe", "pipe"] }
|
|
@@ -840,6 +996,51 @@ function applyClaudeCodeThirdPartyCompat(env) {
|
|
|
840
996
|
env["ENABLE_TOOL_SEARCH"] = "true";
|
|
841
997
|
env["CLAUDE_CODE_SIMPLE_SYSTEM_PROMPT"] = "0";
|
|
842
998
|
}
|
|
999
|
+
var LOOPBACK_NO_PROXY_ENTRIES = ["localhost", "127.0.0.1", "::1"];
|
|
1000
|
+
function gatewayHostname(gatewayUrl) {
|
|
1001
|
+
try {
|
|
1002
|
+
return new URL(gatewayUrl).hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
1003
|
+
} catch {
|
|
1004
|
+
return "";
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
function isLoopbackHost(host) {
|
|
1008
|
+
if (host === "localhost" || host.endsWith(".localhost")) return true;
|
|
1009
|
+
if (host === "::1" || host === "0:0:0:0:0:0:0:1") return true;
|
|
1010
|
+
const v4 = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(host);
|
|
1011
|
+
if (!v4) return false;
|
|
1012
|
+
const octets = v4.slice(1).map(Number);
|
|
1013
|
+
if (octets.some((o) => o > 255)) return false;
|
|
1014
|
+
return octets[0] === 127;
|
|
1015
|
+
}
|
|
1016
|
+
function effectiveNoProxyValue(env) {
|
|
1017
|
+
return env["no_proxy"] || env["NO_PROXY"] || "";
|
|
1018
|
+
}
|
|
1019
|
+
function normalizedNoProxyEntries(env) {
|
|
1020
|
+
return effectiveNoProxyValue(env).split(",").map((value) => value.trim()).filter(Boolean);
|
|
1021
|
+
}
|
|
1022
|
+
function addGatewayNoProxyBypass(env, gatewayUrl) {
|
|
1023
|
+
const hasProxy = Boolean(
|
|
1024
|
+
env["HTTPS_PROXY"]?.trim() || env["https_proxy"]?.trim() || env["HTTP_PROXY"]?.trim() || env["http_proxy"]?.trim()
|
|
1025
|
+
);
|
|
1026
|
+
if (!hasProxy) return;
|
|
1027
|
+
const host = gatewayHostname(gatewayUrl);
|
|
1028
|
+
if (!isLoopbackHost(host)) return;
|
|
1029
|
+
if (effectiveNoProxyValue(env) === "*") return;
|
|
1030
|
+
const existing = normalizedNoProxyEntries(env);
|
|
1031
|
+
const additions = [...LOOPBACK_NO_PROXY_ENTRIES, host];
|
|
1032
|
+
const seen = new Set(existing.map((entry) => entry.toLowerCase()));
|
|
1033
|
+
const merged = [...existing];
|
|
1034
|
+
for (const entry of additions) {
|
|
1035
|
+
const key = entry.toLowerCase();
|
|
1036
|
+
if (seen.has(key)) continue;
|
|
1037
|
+
seen.add(key);
|
|
1038
|
+
merged.push(entry);
|
|
1039
|
+
}
|
|
1040
|
+
const value = merged.join(",");
|
|
1041
|
+
env["NO_PROXY"] = value;
|
|
1042
|
+
env["no_proxy"] = value;
|
|
1043
|
+
}
|
|
843
1044
|
function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableGatewayDiscovery) {
|
|
844
1045
|
const env = { ...process.env };
|
|
845
1046
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
@@ -847,6 +1048,7 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
847
1048
|
}
|
|
848
1049
|
env["ANTHROPIC_BASE_URL"] = proxyPort ? `http://127.0.0.1:${proxyPort}` : baseUrl;
|
|
849
1050
|
env["ANTHROPIC_API_KEY"] = apiKey;
|
|
1051
|
+
addGatewayNoProxyBypass(env, env["ANTHROPIC_BASE_URL"]);
|
|
850
1052
|
const bareModel = stripOneMContextSuffix(model);
|
|
851
1053
|
env["ANTHROPIC_MODEL"] = claudeCodeClientModelId(model, contextWindow);
|
|
852
1054
|
env["CLAUDE_CODE_MAX_CONTEXT_TOKENS"] = String(resolveContextWindow(bareModel, contextWindow));
|
|
@@ -856,8 +1058,9 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
856
1058
|
applyClaudeCodeThirdPartyCompat(env);
|
|
857
1059
|
return env;
|
|
858
1060
|
}
|
|
859
|
-
function buildHttpProxyChildEnv(proxyPort, caCertPath) {
|
|
860
|
-
const
|
|
1061
|
+
function buildHttpProxyChildEnv(proxyPort, caCertPath, baseEnv = process.env) {
|
|
1062
|
+
const baseline = networkEnvBaseline(baseEnv);
|
|
1063
|
+
const env = { ...baseline };
|
|
861
1064
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
862
1065
|
if (name === "ANTHROPIC_API_KEY" || name === "ANTHROPIC_AUTH_TOKEN" || name === "ANTHROPIC_MODEL") continue;
|
|
863
1066
|
delete env[name];
|
|
@@ -869,6 +1072,7 @@ function buildHttpProxyChildEnv(proxyPort, caCertPath) {
|
|
|
869
1072
|
env["http_proxy"] = proxyUrl;
|
|
870
1073
|
env["NODE_EXTRA_CA_CERTS"] = caCertPath;
|
|
871
1074
|
removeAnthropicProxyBypass(env);
|
|
1075
|
+
recordNetworkEnvMutation(baseline, env);
|
|
872
1076
|
return env;
|
|
873
1077
|
}
|
|
874
1078
|
function classifyKeyringError(err) {
|
|
@@ -906,7 +1110,10 @@ var KEYRING_GENERATION_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0
|
|
|
906
1110
|
function oauthProviderIdFromAccount(account) {
|
|
907
1111
|
const prefix = "oauth:provider:";
|
|
908
1112
|
const baseAccount = credentialAccountBase(account);
|
|
909
|
-
|
|
1113
|
+
if (!baseAccount.startsWith(prefix)) return null;
|
|
1114
|
+
const id = baseAccount.slice(prefix.length);
|
|
1115
|
+
const slot = id.indexOf(":account:");
|
|
1116
|
+
return slot === -1 ? id : id.slice(0, slot);
|
|
910
1117
|
}
|
|
911
1118
|
var oauthRefreshInflight = /* @__PURE__ */ new Map();
|
|
912
1119
|
var OAUTH_CREDENTIAL_CACHE_MAX_AGE_MS = 3e4;
|
|
@@ -946,8 +1153,8 @@ function parseAuthRef(authRef) {
|
|
|
946
1153
|
function clodexKeyEnvVar(providerId) {
|
|
947
1154
|
return `CLODEX_KEY_${providerId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
948
1155
|
}
|
|
949
|
-
function readEnvCredential(varName) {
|
|
950
|
-
const raw =
|
|
1156
|
+
function readEnvCredential(varName, env = process.env) {
|
|
1157
|
+
const raw = env[varName];
|
|
951
1158
|
if (!raw?.trim()) return null;
|
|
952
1159
|
return raw.trim().split(/\r?\n/)[0]?.trim() || null;
|
|
953
1160
|
}
|
|
@@ -971,6 +1178,26 @@ function usableEnvCredential(source, value, rejectedAccessToken) {
|
|
|
971
1178
|
}
|
|
972
1179
|
return value;
|
|
973
1180
|
}
|
|
1181
|
+
function readProviderCredentialOverride(providerId, env, options) {
|
|
1182
|
+
if (options.ignoreProviderOverride) return null;
|
|
1183
|
+
const variable = clodexKeyEnvVar(providerId);
|
|
1184
|
+
const credential = usableEnvCredential(
|
|
1185
|
+
`provider:${providerId}`,
|
|
1186
|
+
readEnvCredential(variable, env),
|
|
1187
|
+
options.rejectedAccessToken
|
|
1188
|
+
);
|
|
1189
|
+
if (!credential) return null;
|
|
1190
|
+
return {
|
|
1191
|
+
credential,
|
|
1192
|
+
state: {
|
|
1193
|
+
variable,
|
|
1194
|
+
fingerprint: credentialFingerprint(credential)
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function resolveProviderCredentialOverrideState(providerId, env = process.env, options = {}) {
|
|
1199
|
+
return readProviderCredentialOverride(providerId, env, options)?.state ?? null;
|
|
1200
|
+
}
|
|
974
1201
|
function readKeyringEntry(keyring, service, account) {
|
|
975
1202
|
const value = new keyring.Entry(service, account).getPassword();
|
|
976
1203
|
if (value !== null) return value;
|
|
@@ -2459,25 +2686,37 @@ async function deleteStoredCredential(ref, diag, blockLegacy = true) {
|
|
|
2459
2686
|
return false;
|
|
2460
2687
|
}
|
|
2461
2688
|
}
|
|
2462
|
-
async function
|
|
2689
|
+
async function resolveProviderCredentialWithSource(providerId, authRef, diag, options = {}) {
|
|
2463
2690
|
const parsed = parseAuthRef(authRef);
|
|
2464
|
-
if (parsed?.kind === "none") return null;
|
|
2465
|
-
const
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
if (!parsed) return null;
|
|
2691
|
+
if (parsed?.kind === "none") return { credential: null };
|
|
2692
|
+
const namespaced = readProviderCredentialOverride(providerId, process.env, options);
|
|
2693
|
+
if (namespaced) {
|
|
2694
|
+
return {
|
|
2695
|
+
credential: namespaced.credential,
|
|
2696
|
+
credentialOverride: namespaced.state
|
|
2697
|
+
};
|
|
2698
|
+
}
|
|
2699
|
+
if (!parsed) return { credential: null };
|
|
2473
2700
|
if (parsed.kind === "env") {
|
|
2474
|
-
return
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2701
|
+
return {
|
|
2702
|
+
credential: usableEnvCredential(
|
|
2703
|
+
`provider:${providerId}:env:${parsed.varName}`,
|
|
2704
|
+
readEnvCredential(parsed.varName),
|
|
2705
|
+
options.rejectedAccessToken
|
|
2706
|
+
)
|
|
2707
|
+
};
|
|
2479
2708
|
}
|
|
2480
|
-
return
|
|
2709
|
+
return {
|
|
2710
|
+
credential: await readProviderSecret(parsed, diag, options.rejectedAccessToken)
|
|
2711
|
+
};
|
|
2712
|
+
}
|
|
2713
|
+
async function resolveProviderCredential(providerId, authRef, diag, options = {}) {
|
|
2714
|
+
return (await resolveProviderCredentialWithSource(
|
|
2715
|
+
providerId,
|
|
2716
|
+
authRef,
|
|
2717
|
+
diag,
|
|
2718
|
+
options
|
|
2719
|
+
)).credential;
|
|
2481
2720
|
}
|
|
2482
2721
|
async function resolveProviderOAuthAccountId(authRef, diag) {
|
|
2483
2722
|
const parsed = parseAuthRef(authRef);
|
|
@@ -3414,12 +3653,24 @@ function applyPricingToRegistryProviders(registry, cache) {
|
|
|
3414
3653
|
const index = buildPricingIndex(cache);
|
|
3415
3654
|
let changed = false;
|
|
3416
3655
|
for (const provider of registry.providers) {
|
|
3417
|
-
if (
|
|
3656
|
+
if (provider.preserveModelPricing) continue;
|
|
3418
3657
|
const platform = TEMPLATE_TO_PRICING_PLATFORM[provider.templateId] ?? TEMPLATE_TO_PRICING_PLATFORM[provider.id];
|
|
3419
|
-
const
|
|
3420
|
-
|
|
3421
|
-
|
|
3658
|
+
const enrichCache = (modelsCache) => {
|
|
3659
|
+
if (!modelsCache?.models.length) return modelsCache;
|
|
3660
|
+
const enriched = enrichModelsWithPricing(modelsCache.models, index, platform);
|
|
3661
|
+
if (JSON.stringify(enriched) === JSON.stringify(modelsCache.models)) return modelsCache;
|
|
3422
3662
|
changed = true;
|
|
3663
|
+
return { ...modelsCache, models: enriched };
|
|
3664
|
+
};
|
|
3665
|
+
const topLevelCache = enrichCache(provider.modelsCache);
|
|
3666
|
+
if (topLevelCache !== provider.modelsCache) provider.modelsCache = topLevelCache;
|
|
3667
|
+
const defaultCache = enrichCache(provider.defaultModelsCache);
|
|
3668
|
+
if (defaultCache !== provider.defaultModelsCache) provider.defaultModelsCache = defaultCache;
|
|
3669
|
+
for (const [name, account] of Object.entries(provider.authAccounts ?? {})) {
|
|
3670
|
+
const accountCache = enrichCache(account.modelsCache);
|
|
3671
|
+
if (accountCache !== account.modelsCache) {
|
|
3672
|
+
provider.authAccounts[name] = { ...account, modelsCache: accountCache };
|
|
3673
|
+
}
|
|
3423
3674
|
}
|
|
3424
3675
|
}
|
|
3425
3676
|
if (changed) {
|
|
@@ -3716,7 +3967,9 @@ function cachedModelToLocal(cached, provider) {
|
|
|
3716
3967
|
reasoning: cached.reasoning ?? modelsDev?.reasoning,
|
|
3717
3968
|
interleavedReasoningField: cached.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
3718
3969
|
useResponsesLite: cached.useResponsesLite,
|
|
3719
|
-
preferWebSockets: cached.preferWebSockets
|
|
3970
|
+
preferWebSockets: cached.preferWebSockets,
|
|
3971
|
+
modalities: cached.modalities,
|
|
3972
|
+
compatibility: cached.compatibility
|
|
3720
3973
|
};
|
|
3721
3974
|
}
|
|
3722
3975
|
function isAnonymousProvider(provider) {
|
|
@@ -3725,6 +3978,48 @@ function isAnonymousProvider(provider) {
|
|
|
3725
3978
|
function isLegacyAnonymousCustomEndpoint(provider, credential) {
|
|
3726
3979
|
return provider.authType === void 0 && (provider.templateId === "custom-openai" || provider.templateId === "custom-anthropic") && provider.authRef === `keyring:provider:${provider.id}` && credential === "local";
|
|
3727
3980
|
}
|
|
3981
|
+
function applySelectedOAuthAccount(provider, selected = process.env[OAUTH_ACCOUNT_ENV], warn) {
|
|
3982
|
+
const requested = selected?.trim().toLowerCase();
|
|
3983
|
+
const fromEnvironment = Boolean(requested);
|
|
3984
|
+
const name = requested || provider.activeAuthAccount?.trim();
|
|
3985
|
+
if (!name) return provider;
|
|
3986
|
+
if (!provider.enabled) return provider;
|
|
3987
|
+
if (provider.authType !== "oauth") return provider;
|
|
3988
|
+
const slots = provider.authAccounts;
|
|
3989
|
+
const stored = provider.activeAuthAccount?.trim();
|
|
3990
|
+
if (!slots || Object.keys(slots).length === 0) {
|
|
3991
|
+
if (stored === void 0 || stored === "") {
|
|
3992
|
+
if (fromEnvironment) {
|
|
3993
|
+
warn?.(`${OAUTH_ACCOUNT_ENV}=${requested} ignored for provider "${provider.id}" because it has no named account slots.`);
|
|
3994
|
+
}
|
|
3995
|
+
return provider;
|
|
3996
|
+
}
|
|
3997
|
+
throw new Error(
|
|
3998
|
+
`Provider "${provider.id}" is set to use account "${stored}", but it has no named accounts. Re-add the account with: clodex providers auth openai --account ` + stored + ", or clear the selection with: clodex providers"
|
|
3999
|
+
);
|
|
4000
|
+
}
|
|
4001
|
+
if (!Object.prototype.hasOwnProperty.call(slots, name)) {
|
|
4002
|
+
const available = Object.keys(slots).sort().join(", ");
|
|
4003
|
+
throw new Error(
|
|
4004
|
+
fromEnvironment ? `CLODEX_OAUTH_ACCOUNT=${name}: provider "${provider.id}" has no account named "${name}" (available: ${available}). Add it with: clodex providers auth openai --account ` + name : `Provider "${provider.id}" is set to use account "${name}", which no longer exists (available: ${available}). Choose another with: clodex providers`
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
const account = slots[name];
|
|
4008
|
+
const projected = { ...provider, authRef: account.authRef };
|
|
4009
|
+
if (fromEnvironment && name !== stored) {
|
|
4010
|
+
if (account.modelsCache) projected.modelsCache = account.modelsCache;
|
|
4011
|
+
else delete projected.modelsCache;
|
|
4012
|
+
} else if (!projected.modelsCache && account.modelsCache) {
|
|
4013
|
+
projected.modelsCache = account.modelsCache;
|
|
4014
|
+
}
|
|
4015
|
+
return projected;
|
|
4016
|
+
}
|
|
4017
|
+
function projectSelectedOAuthAccount(provider, selected = process.env[OAUTH_ACCOUNT_ENV]) {
|
|
4018
|
+
const dormantOAuth = provider.authType === "oauth" && !provider.enabled;
|
|
4019
|
+
const candidate = dormantOAuth ? { ...provider, enabled: true } : provider;
|
|
4020
|
+
const projected = applySelectedOAuthAccount(candidate, selected);
|
|
4021
|
+
return dormantOAuth ? { ...projected, enabled: false } : projected;
|
|
4022
|
+
}
|
|
3728
4023
|
function materializeOne(provider, resolveCredential, agent) {
|
|
3729
4024
|
if (!provider.enabled) return null;
|
|
3730
4025
|
if (!isValidProviderId(provider.id)) return null;
|
|
@@ -3772,19 +4067,37 @@ function materializeRegistry(registry, resolveCredential, opts) {
|
|
|
3772
4067
|
|
|
3773
4068
|
// src/registry/load.ts
|
|
3774
4069
|
async function loadRegistryProviders(diag, opts) {
|
|
3775
|
-
const registry = loadRegistry();
|
|
4070
|
+
const registry = loadRegistry(void 0, diag);
|
|
4071
|
+
const providers = registry.providers.map((provider) => applySelectedOAuthAccount(
|
|
4072
|
+
provider,
|
|
4073
|
+
void 0,
|
|
4074
|
+
opts?.warn
|
|
4075
|
+
));
|
|
4076
|
+
const selectedRegistry = { ...registry, providers };
|
|
3776
4077
|
const keys = /* @__PURE__ */ new Map();
|
|
3777
4078
|
const oauthAccountIds = /* @__PURE__ */ new Map();
|
|
3778
4079
|
const oauthProviderData = /* @__PURE__ */ new Map();
|
|
3779
|
-
|
|
3780
|
-
|
|
4080
|
+
const blockedProviders = /* @__PURE__ */ new Map();
|
|
4081
|
+
await Promise.all(providers.map(async (provider) => {
|
|
4082
|
+
if (isAnonymousProvider(provider) || provider.authType === "none" || provider.authRef === "none:anonymous") return;
|
|
4083
|
+
let resolved;
|
|
3781
4084
|
try {
|
|
3782
|
-
|
|
3783
|
-
if (key) keys.set(provider.id, key);
|
|
4085
|
+
resolved = await resolveProviderCredentialWithSource(provider.id, provider.authRef, diag);
|
|
3784
4086
|
} catch (err) {
|
|
3785
4087
|
diag?.(`${provider.id}: credential unavailable \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
4088
|
+
return;
|
|
4089
|
+
}
|
|
4090
|
+
const credentialOverride = resolved.credentialOverride !== void 0;
|
|
4091
|
+
if (provider.enabled && resolved.credentialOverride) {
|
|
4092
|
+
blockedProviders.set(
|
|
4093
|
+
provider.id,
|
|
4094
|
+
`${resolved.credentialOverride.variable} is a process-scoped credential with no isolated model catalog for provider "${provider.id}". Save that credential as a provider or account and refresh its models, or unset the variable.`
|
|
4095
|
+
);
|
|
4096
|
+
return;
|
|
3786
4097
|
}
|
|
3787
|
-
|
|
4098
|
+
const credentialAvailable = Boolean(resolved.credential);
|
|
4099
|
+
if (resolved.credential) keys.set(provider.id, resolved.credential);
|
|
4100
|
+
if (provider.authType === "oauth" && credentialAvailable && !credentialOverride) {
|
|
3788
4101
|
try {
|
|
3789
4102
|
const accountId = await resolveProviderOAuthAccountId(provider.authRef, diag);
|
|
3790
4103
|
if (accountId) oauthAccountIds.set(provider.id, accountId);
|
|
@@ -3794,11 +4107,16 @@ async function loadRegistryProviders(diag, opts) {
|
|
|
3794
4107
|
}
|
|
3795
4108
|
}
|
|
3796
4109
|
}));
|
|
3797
|
-
|
|
4110
|
+
const materialized = materializeRegistry(selectedRegistry, (provider) => keys.get(provider.id) ?? null, opts).map((provider) => ({
|
|
3798
4111
|
...provider,
|
|
3799
4112
|
oauthAccountId: oauthAccountIds.get(provider.id),
|
|
3800
4113
|
providerData: oauthProviderData.get(provider.id)
|
|
3801
4114
|
}));
|
|
4115
|
+
Object.defineProperty(materialized, "blockedProviders", {
|
|
4116
|
+
value: blockedProviders,
|
|
4117
|
+
enumerable: false
|
|
4118
|
+
});
|
|
4119
|
+
return materialized;
|
|
3802
4120
|
}
|
|
3803
4121
|
|
|
3804
4122
|
// src/provider-templates.ts
|
|
@@ -3840,7 +4158,8 @@ function getTemplateById(id) {
|
|
|
3840
4158
|
|
|
3841
4159
|
// src/provider-catalog.ts
|
|
3842
4160
|
async function fetchProviderCatalog(opts) {
|
|
3843
|
-
|
|
4161
|
+
const warn = (message) => console.warn(message);
|
|
4162
|
+
return loadRegistryProviders(warn, { ...opts, warn });
|
|
3844
4163
|
}
|
|
3845
4164
|
function providersForPicker(providers) {
|
|
3846
4165
|
for (const p13 of providers) {
|
|
@@ -3883,16 +4202,115 @@ function formatRegistryAuthLabel(provider) {
|
|
|
3883
4202
|
}
|
|
3884
4203
|
return provider.authRef;
|
|
3885
4204
|
}
|
|
4205
|
+
var PROVIDER_DEFAULT_ACCOUNT_LABEL = "(provider default)";
|
|
4206
|
+
function resolveActiveAccount(provider, env = process.env) {
|
|
4207
|
+
const slots = provider.authAccounts ?? {};
|
|
4208
|
+
const has = (name) => Object.prototype.hasOwnProperty.call(slots, name);
|
|
4209
|
+
const stored = provider.activeAuthAccount?.trim();
|
|
4210
|
+
const override = env[OAUTH_ACCOUNT_ENV]?.trim().toLowerCase();
|
|
4211
|
+
const projectOAuthSelection = (environmentSelection) => {
|
|
4212
|
+
const latentOrphan = stored && !has(stored) && stored !== environmentSelection ? { latentOrphan: stored } : {};
|
|
4213
|
+
if (environmentSelection) {
|
|
4214
|
+
if (has(environmentSelection)) {
|
|
4215
|
+
return {
|
|
4216
|
+
kind: "slot",
|
|
4217
|
+
name: environmentSelection,
|
|
4218
|
+
fromEnvironment: true,
|
|
4219
|
+
...latentOrphan
|
|
4220
|
+
};
|
|
4221
|
+
}
|
|
4222
|
+
if (Object.keys(slots).length > 0) {
|
|
4223
|
+
return {
|
|
4224
|
+
kind: "broken",
|
|
4225
|
+
name: environmentSelection,
|
|
4226
|
+
fromEnvironment: true,
|
|
4227
|
+
...latentOrphan
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
if (!stored) return { kind: "default" };
|
|
4232
|
+
if (has(stored)) return { kind: "slot", name: stored, fromEnvironment: false };
|
|
4233
|
+
return { kind: "broken", name: stored, fromEnvironment: false };
|
|
4234
|
+
};
|
|
4235
|
+
const selected = provider.authType !== "oauth" ? { ...projectOAuthSelection(void 0), inactiveReason: "non-oauth" } : provider.enabled ? projectOAuthSelection(override) : { ...projectOAuthSelection(override), inactiveReason: "disabled" };
|
|
4236
|
+
const effectiveAuthRef = provider.authType === "oauth" && selected.kind === "slot" ? slots[selected.name]?.authRef : provider.authRef;
|
|
4237
|
+
const credentialOverride = provider.authType !== "none" && effectiveAuthRef !== "none:anonymous" ? resolveProviderCredentialOverrideState(provider.id, env) : null;
|
|
4238
|
+
if (!credentialOverride) return selected;
|
|
4239
|
+
if (provider.authType === "oauth" && selected.kind === "broken") {
|
|
4240
|
+
return { ...selected, credentialOverride };
|
|
4241
|
+
}
|
|
4242
|
+
return {
|
|
4243
|
+
kind: "credential-override",
|
|
4244
|
+
credentialOverride,
|
|
4245
|
+
selection: selected,
|
|
4246
|
+
...selected.latentOrphan ? { latentOrphan: selected.latentOrphan } : {},
|
|
4247
|
+
...selected.inactiveReason ? { inactiveReason: selected.inactiveReason } : {}
|
|
4248
|
+
};
|
|
4249
|
+
}
|
|
3886
4250
|
async function resolveProvidersForDisplay() {
|
|
3887
4251
|
const reg = loadRegistry();
|
|
3888
4252
|
const entries = [];
|
|
3889
4253
|
for (const provider of reg.providers) {
|
|
4254
|
+
const accountNames = Object.keys(provider.authAccounts ?? {}).sort();
|
|
4255
|
+
const effective = resolveActiveAccount(provider);
|
|
4256
|
+
const selection = effective.kind === "credential-override" ? effective.selection : effective;
|
|
4257
|
+
const credentialOverride = effective.credentialOverride;
|
|
4258
|
+
const credentialOverrideWins = effective.kind === "credential-override";
|
|
4259
|
+
const active = !credentialOverrideWins && selection.kind === "slot" && !selection.inactiveReason ? selection.name : void 0;
|
|
4260
|
+
const projected = !credentialOverrideWins && selection.kind === "slot" && selection.inactiveReason === "disabled" ? selection.name : void 0;
|
|
4261
|
+
const storedButInapplicable = selection.kind === "slot" && selection.inactiveReason === "non-oauth" ? selection.name : void 0;
|
|
4262
|
+
const accountOverrideApplies = selection.kind !== "default" && selection.fromEnvironment;
|
|
4263
|
+
const broken = selection.kind === "broken" ? selection : void 0;
|
|
4264
|
+
const latent = selection.latentOrphan;
|
|
4265
|
+
const label = (name) => {
|
|
4266
|
+
if (name === active) {
|
|
4267
|
+
return accountOverrideApplies ? `${name} (active, from ${OAUTH_ACCOUNT_ENV})` : `${name} (active)`;
|
|
4268
|
+
}
|
|
4269
|
+
if (credentialOverrideWins && selection.kind === "slot" && name === selection.name) {
|
|
4270
|
+
if (selection.inactiveReason === "non-oauth") {
|
|
4271
|
+
return `${name} (stored; provider is not OAuth)`;
|
|
4272
|
+
}
|
|
4273
|
+
const selectedFrom = accountOverrideApplies ? `, from ${OAUTH_ACCOUNT_ENV}` : "";
|
|
4274
|
+
return selection.inactiveReason === "disabled" ? `${name} (selected${selectedFrom}; provider disabled; ${credentialOverride.variable} has no isolated model catalog)` : `${name} (selected${selectedFrom}; ${credentialOverride.variable} configured; launch blocked \u2014 no isolated model catalog)`;
|
|
4275
|
+
}
|
|
4276
|
+
if (name === projected) {
|
|
4277
|
+
return accountOverrideApplies ? `${name} (selected, from ${OAUTH_ACCOUNT_ENV}; provider disabled)` : `${name} (selected; provider disabled)`;
|
|
4278
|
+
}
|
|
4279
|
+
if (name === storedButInapplicable) {
|
|
4280
|
+
return `${name} (stored; provider is not OAuth)`;
|
|
4281
|
+
}
|
|
4282
|
+
return name;
|
|
4283
|
+
};
|
|
4284
|
+
const defaultLabel = selection.kind !== "default" ? PROVIDER_DEFAULT_ACCOUNT_LABEL : credentialOverrideWins ? selection.inactiveReason === "disabled" ? `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (selected; provider disabled; ${credentialOverride.variable} has no isolated model catalog)` : selection.inactiveReason === "non-oauth" ? `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (OAuth selection inactive; provider is not OAuth)` : `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (selected; ${credentialOverride.variable} configured; launch blocked \u2014 no isolated model catalog)` : selection.inactiveReason === "disabled" ? `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (selected; provider disabled)` : selection.inactiveReason === "non-oauth" ? `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (OAuth selection inactive; provider is not OAuth)` : `${PROVIDER_DEFAULT_ACCOUNT_LABEL} (active)`;
|
|
4285
|
+
const brokenConsequence = selection.inactiveReason === "disabled" ? "will fail if this provider is enabled" : selection.inactiveReason === "non-oauth" ? "ignored because this provider is not OAuth" : "every launch fails";
|
|
4286
|
+
const accountList = [
|
|
4287
|
+
defaultLabel,
|
|
4288
|
+
...accountNames.map(label),
|
|
4289
|
+
// An inactive account selection gets saved-state wording, not a current
|
|
4290
|
+
// OAuth outcome. A disabled provider is excluded from materialization;
|
|
4291
|
+
// a non-OAuth provider may launch, but never applies these selectors.
|
|
4292
|
+
...broken ? [`${broken.name} (${selection.inactiveReason === "non-oauth" ? "stored" : "selected"}${broken.fromEnvironment ? ` via ${OAUTH_ACCOUNT_ENV}` : ""}, MISSING \u2014 ${brokenConsequence})`] : [],
|
|
4293
|
+
...latent ? [`${latent} (stored, MISSING \u2014 masked by ${OAUTH_ACCOUNT_ENV}; ${selection.inactiveReason === "disabled" ? "will fail if enabled without it" : "launches fail without it"})`] : []
|
|
4294
|
+
].join(", ");
|
|
4295
|
+
const storedAuthLabel = formatRegistryAuthLabel(provider);
|
|
4296
|
+
const effectiveAuthLabel = !credentialOverride ? storedAuthLabel : credentialOverrideWins ? provider.enabled ? `${credentialOverride.variable} (configured provider override; launch blocked \u2014 no isolated model catalog; stored auth: ${storedAuthLabel})` : `${credentialOverride.variable} (configured provider override; no isolated model catalog; provider disabled; stored auth: ${storedAuthLabel})` : `${storedAuthLabel}; ${credentialOverride.variable} is configured but blocked by the invalid OAuth account selection`;
|
|
4297
|
+
const authLabel = accountNames.length || broken || latent ? `${effectiveAuthLabel}; accounts: ${accountList}` : effectiveAuthLabel;
|
|
3890
4298
|
entries.push({
|
|
3891
4299
|
id: provider.id,
|
|
3892
4300
|
name: provider.name,
|
|
3893
|
-
|
|
4301
|
+
// Model counts describe the identity this process would launch, not the
|
|
4302
|
+
// persisted account hidden behind CLODEX_OAUTH_ACCOUNT. A broken
|
|
4303
|
+
// selection has no safe catalog to advertise.
|
|
4304
|
+
modelCount: (() => {
|
|
4305
|
+
if (credentialOverrideWins) return 0;
|
|
4306
|
+
try {
|
|
4307
|
+
return projectSelectedOAuthAccount(provider).modelsCache?.models.length ?? 0;
|
|
4308
|
+
} catch {
|
|
4309
|
+
return 0;
|
|
4310
|
+
}
|
|
4311
|
+
})(),
|
|
3894
4312
|
enabled: provider.enabled,
|
|
3895
|
-
authLabel
|
|
4313
|
+
authLabel,
|
|
3896
4314
|
inRegistry: true
|
|
3897
4315
|
});
|
|
3898
4316
|
}
|
|
@@ -3926,6 +4344,7 @@ function localProvidersToServerModels(localProviders) {
|
|
|
3926
4344
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
3927
4345
|
useResponsesLite: model.useResponsesLite,
|
|
3928
4346
|
preferWebSockets: model.preferWebSockets,
|
|
4347
|
+
compatibility: model.compatibility,
|
|
3929
4348
|
headers: provider.headers,
|
|
3930
4349
|
providerData: provider.providerData
|
|
3931
4350
|
}))
|
|
@@ -3940,6 +4359,8 @@ import { createHash as createHash3 } from "crypto";
|
|
|
3940
4359
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3941
4360
|
|
|
3942
4361
|
// src/outbound-proxy.ts
|
|
4362
|
+
import { networkInterfaces } from "os";
|
|
4363
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
3943
4364
|
function hasOutboundProxyEnv(env = process.env) {
|
|
3944
4365
|
return Boolean(
|
|
3945
4366
|
env["HTTPS_PROXY"]?.trim() || env["https_proxy"]?.trim() || env["HTTP_PROXY"]?.trim() || env["http_proxy"]?.trim()
|
|
@@ -3975,6 +4396,26 @@ function outboundProxyUrlForTarget(targetUrl, env = process.env) {
|
|
|
3975
4396
|
if (noProxyBypasses(parsed.hostname, env)) return void 0;
|
|
3976
4397
|
return proxy.trim();
|
|
3977
4398
|
}
|
|
4399
|
+
function proxyUrlTargetsListener(proxyUrl, listenerHost, listenerPort, localAddresses = new Set(
|
|
4400
|
+
Object.values(networkInterfaces()).flatMap((entries) => (entries ?? []).map((entry) => entry.address.toLowerCase()))
|
|
4401
|
+
)) {
|
|
4402
|
+
let parsed;
|
|
4403
|
+
try {
|
|
4404
|
+
parsed = new URL(proxyUrl);
|
|
4405
|
+
} catch {
|
|
4406
|
+
return false;
|
|
4407
|
+
}
|
|
4408
|
+
const proxyPort = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : parsed.protocol === "http:" ? 80 : void 0;
|
|
4409
|
+
if (proxyPort !== listenerPort) return false;
|
|
4410
|
+
const normalizeHost = (host) => host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
4411
|
+
const proxyHost = normalizeHost(parsed.hostname);
|
|
4412
|
+
const boundHost = normalizeHost(listenerHost);
|
|
4413
|
+
if (proxyHost === boundHost) return true;
|
|
4414
|
+
const isLoopback = (host) => host === "localhost" || host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host);
|
|
4415
|
+
const isWildcard = (host) => host === "0.0.0.0" || host === "::";
|
|
4416
|
+
if (isLoopback(proxyHost) && (isLoopback(boundHost) || isWildcard(boundHost))) return true;
|
|
4417
|
+
return isWildcard(boundHost) && (isWildcard(proxyHost) || localAddresses.has(proxyHost));
|
|
4418
|
+
}
|
|
3978
4419
|
var dispatcherInstalled = false;
|
|
3979
4420
|
async function installOutboundProxyDispatcher() {
|
|
3980
4421
|
if (dispatcherInstalled) return true;
|
|
@@ -3991,11 +4432,24 @@ async function installOutboundProxyDispatcher() {
|
|
|
3991
4432
|
return false;
|
|
3992
4433
|
}
|
|
3993
4434
|
}
|
|
3994
|
-
|
|
3995
|
-
const proxyUrl = outboundProxyUrlForTarget(
|
|
4435
|
+
function outboundHttpProxyAgent(targetUrl, env = process.env) {
|
|
4436
|
+
const proxyUrl = outboundProxyUrlForTarget(targetUrl, env);
|
|
3996
4437
|
if (!proxyUrl) return void 0;
|
|
3997
|
-
|
|
3998
|
-
|
|
4438
|
+
try {
|
|
4439
|
+
const parsedProxy = new URL(proxyUrl);
|
|
4440
|
+
if (!parsedProxy.hostname || !["http:", "https:"].includes(parsedProxy.protocol)) {
|
|
4441
|
+
throw new TypeError("Invalid proxy URL");
|
|
4442
|
+
}
|
|
4443
|
+
return new HttpsProxyAgent(parsedProxy, { keepAlive: true });
|
|
4444
|
+
} catch (err) {
|
|
4445
|
+
console.error(
|
|
4446
|
+
`clodex: HTTP(S)_PROXY cannot be used for a CONNECT tunnel; using a direct connection (${err instanceof Error ? err.message : String(err)})`
|
|
4447
|
+
);
|
|
4448
|
+
return void 0;
|
|
4449
|
+
}
|
|
4450
|
+
}
|
|
4451
|
+
function outboundWsProxyAgent(wsUrl, env = process.env) {
|
|
4452
|
+
return outboundHttpProxyAgent(wsUrl, env);
|
|
3999
4453
|
}
|
|
4000
4454
|
|
|
4001
4455
|
// src/upstream-error.ts
|
|
@@ -4044,7 +4498,7 @@ function frameStatusCode(code, discriminator) {
|
|
|
4044
4498
|
const numeric = Number(code);
|
|
4045
4499
|
if (numeric >= 400 && numeric <= 599) return numeric;
|
|
4046
4500
|
}
|
|
4047
|
-
if (/insufficient_quota|rate_limit/.test(discriminator)) return 429;
|
|
4501
|
+
if (/insufficient_quota|rate_limit|usage_limit/.test(discriminator)) return 429;
|
|
4048
4502
|
if (discriminator.includes("authentication")) return 401;
|
|
4049
4503
|
if (discriminator.includes("permission")) return 403;
|
|
4050
4504
|
if (discriminator.includes("not_found")) return 404;
|
|
@@ -4487,13 +4941,9 @@ function warnReasoningNormalizationGap(fields, log12) {
|
|
|
4487
4941
|
if (warnedReasoningGaps.has(signature)) return;
|
|
4488
4942
|
if (warnedReasoningGaps.size >= MAX_REASONING_GAP_WARNINGS) return;
|
|
4489
4943
|
warnedReasoningGaps.add(signature);
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
if (warnedReasoningGaps.size === MAX_REASONING_GAP_WARNINGS) {
|
|
4494
|
-
process.stderr.write("clodex: warning: further reasoning-normalization warnings suppressed.\n");
|
|
4495
|
-
}
|
|
4496
|
-
} catch {
|
|
4944
|
+
emitParentNotice(message);
|
|
4945
|
+
if (warnedReasoningGaps.size === MAX_REASONING_GAP_WARNINGS) {
|
|
4946
|
+
emitParentNotice("clodex: warning: further reasoning-normalization warnings suppressed.");
|
|
4497
4947
|
}
|
|
4498
4948
|
}
|
|
4499
4949
|
function toolArgumentNormalizationGap(expected, actual, requiredProps) {
|
|
@@ -4542,13 +4992,9 @@ function warnToolArgumentNormalizationGap(gap, log12) {
|
|
|
4542
4992
|
if (warnedToolArgumentGaps.has(signature)) return;
|
|
4543
4993
|
if (warnedToolArgumentGaps.size >= MAX_TOOL_ARGUMENT_GAP_WARNINGS) return;
|
|
4544
4994
|
warnedToolArgumentGaps.add(signature);
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4549
|
-
process.stderr.write("clodex: warning: further tool-argument normalization warnings suppressed.\n");
|
|
4550
|
-
}
|
|
4551
|
-
} catch {
|
|
4995
|
+
emitParentNotice(message);
|
|
4996
|
+
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4997
|
+
emitParentNotice("clodex: warning: further tool-argument normalization warnings suppressed.");
|
|
4552
4998
|
}
|
|
4553
4999
|
}
|
|
4554
5000
|
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
@@ -4986,12 +5432,12 @@ function expectedAssistantItems(ctx) {
|
|
|
4986
5432
|
const type = accumulator.type ?? (typeof done.type === "string" ? done.type : void 0);
|
|
4987
5433
|
if (type === "message") {
|
|
4988
5434
|
const doneContent = Array.isArray(done.content) ? done.content : void 0;
|
|
4989
|
-
const
|
|
4990
|
-
output.push({ role: "assistant", content: [{ type: "output_text", text:
|
|
5435
|
+
const text5 = accumulator.text || (doneContent ? doneContent.filter((part) => part && typeof part === "object" && part.type === "output_text").map((part) => String(part.text ?? "")).join("") : "");
|
|
5436
|
+
output.push({ role: "assistant", content: [{ type: "output_text", text: text5 }] });
|
|
4991
5437
|
continue;
|
|
4992
5438
|
}
|
|
4993
5439
|
if (type === "reasoning") {
|
|
4994
|
-
const summary = accumulator.summaries.size ? [...accumulator.summaries.entries()].sort(([a], [b]) => a - b).map(([,
|
|
5440
|
+
const summary = accumulator.summaries.size ? [...accumulator.summaries.entries()].sort(([a], [b]) => a - b).map(([, text5]) => ({ type: "summary_text", text: text5 })) : Array.isArray(done.summary) ? done.summary : [];
|
|
4995
5441
|
output.push({ ...withoutEphemeralFields(done), type: "reasoning", summary });
|
|
4996
5442
|
continue;
|
|
4997
5443
|
}
|
|
@@ -5210,7 +5656,7 @@ function transportReplaySafe(ctx) {
|
|
|
5210
5656
|
function handleSocketMessage(entry, data) {
|
|
5211
5657
|
const ctx = entry.current;
|
|
5212
5658
|
if (!ctx || ctx.closed) return;
|
|
5213
|
-
const
|
|
5659
|
+
const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
|
|
5214
5660
|
ctx.frameCount += 1;
|
|
5215
5661
|
if (ctx.transportRetryPending) {
|
|
5216
5662
|
ctx.transportRetryPending = false;
|
|
@@ -5222,9 +5668,9 @@ function handleSocketMessage(entry, data) {
|
|
|
5222
5668
|
}
|
|
5223
5669
|
let event;
|
|
5224
5670
|
try {
|
|
5225
|
-
event = JSON.parse(
|
|
5671
|
+
event = JSON.parse(text5);
|
|
5226
5672
|
} catch {
|
|
5227
|
-
ctx.pendingEvents.push(
|
|
5673
|
+
ctx.pendingEvents.push(text5.replace(/\r?\n/g, " "));
|
|
5228
5674
|
flushPending(ctx);
|
|
5229
5675
|
return;
|
|
5230
5676
|
}
|
|
@@ -5267,7 +5713,8 @@ function handleSocketMessage(entry, data) {
|
|
|
5267
5713
|
return;
|
|
5268
5714
|
}
|
|
5269
5715
|
const errorStatus = type === "error" && !ctx.emittedModelData ? responseErrorStatus(event) : void 0;
|
|
5270
|
-
|
|
5716
|
+
const emptyFailureTerminal = FAILURE_EVENT_TYPES.has(type ?? "") && errorStatus === void 0 && !willRetry && !ctx.emittedModelData;
|
|
5717
|
+
if (FAILURE_EVENT_TYPES.has(type ?? "") && (errorStatus === void 0 || willRetry) && !emptyFailureTerminal) {
|
|
5271
5718
|
emitResponseErrorDiagnostic(entry, ctx, {
|
|
5272
5719
|
source: "response_event",
|
|
5273
5720
|
upstreamEventType: type,
|
|
@@ -5310,6 +5757,39 @@ function handleSocketMessage(entry, data) {
|
|
|
5310
5757
|
);
|
|
5311
5758
|
return;
|
|
5312
5759
|
}
|
|
5760
|
+
if (emptyFailureTerminal) {
|
|
5761
|
+
const details = responseFailureDetails(event);
|
|
5762
|
+
const named = [details.errorType, details.errorCode, details.incompleteReason].filter((value) => typeof value === "string");
|
|
5763
|
+
const summary = "OpenAI ended the response with no output" + (named.length ? ` (${named.join(" / ")})` : ` (${type})`);
|
|
5764
|
+
const discriminator = [details.errorType, details.errorCode].filter((value) => typeof value === "string").join(" ").toLowerCase();
|
|
5765
|
+
const settledReason = details.incompleteReason === "content_filter" || details.incompleteReason === "max_output_tokens";
|
|
5766
|
+
const numericOrNamed = typeof details.errorCode === "string" ? details.errorCode : void 0;
|
|
5767
|
+
const classified = numericOrNamed !== void 0 || discriminator ? frameStatusCode(numericOrNamed, discriminator) : 500;
|
|
5768
|
+
const statusCode = classified !== 500 ? classified : settledReason ? 400 : 502;
|
|
5769
|
+
const usageLimited = statusCode === 429;
|
|
5770
|
+
const retryAfterSeconds = usageLimited && responseRetryAfterSeconds(event) !== void 0 ? clampRetryAfterSeconds(responseRetryAfterSeconds(event)) : void 0;
|
|
5771
|
+
failContext(
|
|
5772
|
+
entry,
|
|
5773
|
+
ctx,
|
|
5774
|
+
retryAfterSeconds === void 0 ? summary : `${summary}; retry after ${retryAfterSeconds}s`,
|
|
5775
|
+
{
|
|
5776
|
+
source: "empty_failure_terminal",
|
|
5777
|
+
upstreamEventType: type,
|
|
5778
|
+
...details,
|
|
5779
|
+
// Under DISTINCT keys. `failContext` fingerprints the message it was
|
|
5780
|
+
// given after spreading these, so an `errorMessage*` pair here is
|
|
5781
|
+
// overwritten by the summary's — which would silently discard the only
|
|
5782
|
+
// content-free evidence of what upstream actually said, and leave two
|
|
5783
|
+
// failures with the same type and code indistinguishable. `errorMessage*`
|
|
5784
|
+
// now means "what the client was told", `upstreamMessage*` means "what
|
|
5785
|
+
// upstream said", and both survive.
|
|
5786
|
+
...diagnosticTextFingerprint("upstreamMessage", responseErrorMessage(event))
|
|
5787
|
+
},
|
|
5788
|
+
statusCode,
|
|
5789
|
+
retryAfterSeconds
|
|
5790
|
+
);
|
|
5791
|
+
return;
|
|
5792
|
+
}
|
|
5313
5793
|
ctx.pendingEvents.push(event);
|
|
5314
5794
|
if (isModelDataEvent(type)) flushPending(ctx);
|
|
5315
5795
|
if (TERMINAL_EVENT_TYPES.has(type ?? "") || type === "error") {
|
|
@@ -5731,8 +6211,8 @@ function buildClaudeCodeBillingSystemLine() {
|
|
|
5731
6211
|
function systemBlockText(block) {
|
|
5732
6212
|
if (typeof block === "string") return block;
|
|
5733
6213
|
if (block && typeof block === "object" && "text" in block) {
|
|
5734
|
-
const
|
|
5735
|
-
return typeof
|
|
6214
|
+
const text5 = block.text;
|
|
6215
|
+
return typeof text5 === "string" ? text5 : void 0;
|
|
5736
6216
|
}
|
|
5737
6217
|
return void 0;
|
|
5738
6218
|
}
|
|
@@ -5805,6 +6285,63 @@ function isCredentialBearingHeader(name) {
|
|
|
5805
6285
|
return CREDENTIAL_BEARING_HEADER.test(name);
|
|
5806
6286
|
}
|
|
5807
6287
|
|
|
6288
|
+
// src/model-runtime-compatibility.ts
|
|
6289
|
+
function remapMaxTokensField(body, field) {
|
|
6290
|
+
if (field === "max_tokens") {
|
|
6291
|
+
if (body.max_tokens === void 0 && body.max_completion_tokens !== void 0) {
|
|
6292
|
+
body.max_tokens = body.max_completion_tokens;
|
|
6293
|
+
}
|
|
6294
|
+
delete body.max_completion_tokens;
|
|
6295
|
+
return;
|
|
6296
|
+
}
|
|
6297
|
+
if (field === "max_completion_tokens") {
|
|
6298
|
+
if (body.max_completion_tokens === void 0 && body.max_tokens !== void 0) {
|
|
6299
|
+
body.max_completion_tokens = body.max_tokens;
|
|
6300
|
+
}
|
|
6301
|
+
delete body.max_tokens;
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
function transformMessages(messages, compatibility) {
|
|
6305
|
+
if (!Array.isArray(messages)) return messages;
|
|
6306
|
+
const rewriteDeveloper = compatibility.supportsDeveloperRole === false;
|
|
6307
|
+
const replayReasoning = compatibility.requiresReasoningContentOnAssistantMessages === true;
|
|
6308
|
+
if (!rewriteDeveloper && !replayReasoning) return messages;
|
|
6309
|
+
let changed = false;
|
|
6310
|
+
const transformed = messages.map((message) => {
|
|
6311
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) return message;
|
|
6312
|
+
const source = message;
|
|
6313
|
+
const role = source.role;
|
|
6314
|
+
const needsRoleRewrite = rewriteDeveloper && role === "developer";
|
|
6315
|
+
const needsReasoningReplay = replayReasoning && role === "assistant" && !Object.prototype.hasOwnProperty.call(source, "reasoning_content");
|
|
6316
|
+
if (!needsRoleRewrite && !needsReasoningReplay) return message;
|
|
6317
|
+
changed = true;
|
|
6318
|
+
return {
|
|
6319
|
+
...source,
|
|
6320
|
+
...needsRoleRewrite ? { role: "system" } : {},
|
|
6321
|
+
...needsReasoningReplay ? { reasoning_content: "" } : {}
|
|
6322
|
+
};
|
|
6323
|
+
});
|
|
6324
|
+
return changed ? transformed : messages;
|
|
6325
|
+
}
|
|
6326
|
+
function transformOpenAiCompatibleRequestBody(body, compatibility) {
|
|
6327
|
+
const transformed = { ...body };
|
|
6328
|
+
if (compatibility.supportsStore === false) delete transformed.store;
|
|
6329
|
+
if (compatibility.supportsLongCacheRetention === false) {
|
|
6330
|
+
delete transformed.prompt_cache_retention;
|
|
6331
|
+
delete transformed.promptCacheRetention;
|
|
6332
|
+
}
|
|
6333
|
+
remapMaxTokensField(transformed, compatibility.maxTokensField);
|
|
6334
|
+
const messages = transformMessages(transformed.messages, compatibility);
|
|
6335
|
+
if (messages !== transformed.messages) transformed.messages = messages;
|
|
6336
|
+
const hasReasoningEffort = typeof transformed.reasoning_effort === "string" && transformed.reasoning_effort.trim().length > 0;
|
|
6337
|
+
if (hasReasoningEffort && compatibility.thinkingFormat === "deepseek") {
|
|
6338
|
+
if (transformed.thinking === void 0) transformed.thinking = { type: "enabled" };
|
|
6339
|
+
} else if (hasReasoningEffort && compatibility.thinkingFormat === "qwen") {
|
|
6340
|
+
if (transformed.enable_thinking === void 0) transformed.enable_thinking = true;
|
|
6341
|
+
}
|
|
6342
|
+
return transformed;
|
|
6343
|
+
}
|
|
6344
|
+
|
|
5808
6345
|
// src/provider-factory.ts
|
|
5809
6346
|
var RESPONSES_ONLY_PREFIXES = [
|
|
5810
6347
|
"gpt-5-codex",
|
|
@@ -5947,7 +6484,10 @@ async function createLanguageModel(spec) {
|
|
|
5947
6484
|
baseURL: baseURL ?? "",
|
|
5948
6485
|
...spec.authType !== "none" && apiKey.trim() ? { apiKey } : {},
|
|
5949
6486
|
...spec.authType === "none" ? { fetch: fetchWithoutCredentialHeaders } : {},
|
|
5950
|
-
...spec.headers ? { headers: spec.headers } : {}
|
|
6487
|
+
...spec.headers ? { headers: spec.headers } : {},
|
|
6488
|
+
...spec.compatibility ? {
|
|
6489
|
+
transformRequestBody: (body) => transformOpenAiCompatibleRequestBody(body, spec.compatibility)
|
|
6490
|
+
} : {}
|
|
5951
6491
|
};
|
|
5952
6492
|
model = createOpenAICompatible({
|
|
5953
6493
|
...options
|
|
@@ -6203,8 +6743,68 @@ function mapCodexEffortToGeminiBudget(effort) {
|
|
|
6203
6743
|
if (!level) return void 0;
|
|
6204
6744
|
return GEMINI_25_BUDGETS[level];
|
|
6205
6745
|
}
|
|
6746
|
+
function compatibilityReasoningCapabilities(metadata) {
|
|
6747
|
+
const compatibility = metadata?.compatibility;
|
|
6748
|
+
if (!compatibility) return void 0;
|
|
6749
|
+
if (compatibility.supportsReasoningEffort === false) {
|
|
6750
|
+
return metadata?.reasoning === false ? EMPTY_REASONING : {
|
|
6751
|
+
...EMPTY_REASONING,
|
|
6752
|
+
mode: "internal-only",
|
|
6753
|
+
source: "provider-metadata",
|
|
6754
|
+
confidence: "documented"
|
|
6755
|
+
};
|
|
6756
|
+
}
|
|
6757
|
+
if (compatibility.reasoningEffortMap) {
|
|
6758
|
+
const levels = Object.entries(compatibility.reasoningEffortMap).filter(([, mapped]) => mapped !== null).map(([level]) => level);
|
|
6759
|
+
if (levels.length === 0) {
|
|
6760
|
+
return metadata?.reasoning === false ? EMPTY_REASONING : {
|
|
6761
|
+
...EMPTY_REASONING,
|
|
6762
|
+
mode: "internal-only",
|
|
6763
|
+
source: "provider-metadata",
|
|
6764
|
+
confidence: "documented"
|
|
6765
|
+
};
|
|
6766
|
+
}
|
|
6767
|
+
const preferredDefault = ["medium", "high", "max", "low"].find((level) => levels.includes(level));
|
|
6768
|
+
return {
|
|
6769
|
+
levels,
|
|
6770
|
+
defaultLevel: preferredDefault ?? levels[0],
|
|
6771
|
+
supportsSummaries: false,
|
|
6772
|
+
mode: "controllable",
|
|
6773
|
+
source: "provider-metadata",
|
|
6774
|
+
confidence: "documented",
|
|
6775
|
+
wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
|
|
6776
|
+
};
|
|
6777
|
+
}
|
|
6778
|
+
if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
|
|
6779
|
+
if (metadata?.reasoning === false) return EMPTY_REASONING;
|
|
6780
|
+
return {
|
|
6781
|
+
levels: ["low", "medium", "high"],
|
|
6782
|
+
defaultLevel: "medium",
|
|
6783
|
+
supportsSummaries: false,
|
|
6784
|
+
mode: "controllable",
|
|
6785
|
+
source: "provider-metadata",
|
|
6786
|
+
confidence: "documented",
|
|
6787
|
+
wireFormat: compatibility.thinkingFormat === "deepseek" ? { kind: "deepseek-thinking" } : { kind: "openai-reasoning-effort" }
|
|
6788
|
+
};
|
|
6789
|
+
}
|
|
6790
|
+
return void 0;
|
|
6791
|
+
}
|
|
6792
|
+
function compatibilityReasoningEffort(effort, modelId, compatibility) {
|
|
6793
|
+
if (compatibility.supportsReasoningEffort === false) return void 0;
|
|
6794
|
+
const map = compatibility.reasoningEffortMap;
|
|
6795
|
+
if (map) {
|
|
6796
|
+
if (!Object.prototype.hasOwnProperty.call(map, effort)) return void 0;
|
|
6797
|
+
return map[effort] ?? void 0;
|
|
6798
|
+
}
|
|
6799
|
+
if (compatibility.supportsReasoningEffort === true || compatibility.thinkingFormat !== void 0) {
|
|
6800
|
+
return mapCodexEffortToOpenAI(effort, modelId);
|
|
6801
|
+
}
|
|
6802
|
+
return void 0;
|
|
6803
|
+
}
|
|
6206
6804
|
function getReasoningCapabilities(npm, modelId, metadata) {
|
|
6207
6805
|
const id = modelId.toLowerCase();
|
|
6806
|
+
const compatibilityCapabilities = compatibilityReasoningCapabilities(metadata);
|
|
6807
|
+
if (compatibilityCapabilities) return compatibilityCapabilities;
|
|
6208
6808
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
6209
6809
|
return openRouterReasoningCapabilities(metadata);
|
|
6210
6810
|
}
|
|
@@ -6350,7 +6950,10 @@ function getReasoningCapabilities(npm, modelId, metadata) {
|
|
|
6350
6950
|
return EMPTY_REASONING;
|
|
6351
6951
|
}
|
|
6352
6952
|
function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
6353
|
-
if (metadata?.
|
|
6953
|
+
if (metadata?.compatibility?.supportsReasoningEffort === false) {
|
|
6954
|
+
return compatibilityReasoningCapabilities(metadata) ?? EMPTY_REASONING;
|
|
6955
|
+
}
|
|
6956
|
+
if (metadata?.reasoning === false && !metadata?.compatibility?.reasoningEffortMap && !hasSupportedParameter(metadata, "reasoning_effort") && !hasSupportedParameter(metadata, "reasoning")) {
|
|
6354
6957
|
return EMPTY_REASONING;
|
|
6355
6958
|
}
|
|
6356
6959
|
const capabilities = getReasoningCapabilities(npm, modelId, metadata);
|
|
@@ -6369,11 +6972,22 @@ function getPatchReasoningCapabilities(npm, modelId, metadata) {
|
|
|
6369
6972
|
}
|
|
6370
6973
|
function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
6371
6974
|
if (!effort) return void 0;
|
|
6975
|
+
if (npm === "@ai-sdk/openai-compatible" && modelId && metadata?.compatibility) {
|
|
6976
|
+
const reasoningEffort = compatibilityReasoningEffort(
|
|
6977
|
+
effort,
|
|
6978
|
+
modelId,
|
|
6979
|
+
metadata.compatibility
|
|
6980
|
+
);
|
|
6981
|
+
if (!reasoningEffort) return void 0;
|
|
6982
|
+
const key = metadata.providerId ? toCamelCase(metadata.providerId) : "openaiCompatible";
|
|
6983
|
+
return { [key]: { reasoningEffort } };
|
|
6984
|
+
}
|
|
6372
6985
|
if (isOpenRouterRoute(npm, metadata)) {
|
|
6373
6986
|
const caps = openRouterReasoningCapabilities(metadata);
|
|
6374
6987
|
if (caps.mode !== "controllable") return void 0;
|
|
6375
6988
|
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
6376
|
-
const
|
|
6989
|
+
const candidate = effort;
|
|
6990
|
+
const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
|
|
6377
6991
|
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
6378
6992
|
}
|
|
6379
6993
|
if (npm === "@ai-sdk/openai" || npm === "@ai-sdk/azure") {
|
|
@@ -6433,7 +7047,8 @@ function effortProviderOptions(npm, effort, modelId, metadata) {
|
|
|
6433
7047
|
}
|
|
6434
7048
|
if (hasSupportedParameter(metadata, "reasoning")) {
|
|
6435
7049
|
const allowed = new Set(OPENROUTER_EFFORT_LEVELS);
|
|
6436
|
-
const
|
|
7050
|
+
const candidate = effort;
|
|
7051
|
+
const mapped = allowed.has(candidate) ? candidate : candidate === "max" ? "xhigh" : void 0;
|
|
6437
7052
|
return mapped ? { openrouter: { reasoning: { effort: mapped, exclude: false } } } : void 0;
|
|
6438
7053
|
}
|
|
6439
7054
|
return void 0;
|
|
@@ -6515,7 +7130,12 @@ function isManagedCredentialAccount(account) {
|
|
|
6515
7130
|
const base = credentialAccountBase2(account);
|
|
6516
7131
|
if (!base) return false;
|
|
6517
7132
|
const oauth = /^oauth:provider:(.+)$/.exec(base);
|
|
6518
|
-
if (oauth)
|
|
7133
|
+
if (oauth) {
|
|
7134
|
+
const id = oauth[1];
|
|
7135
|
+
const slot = id.indexOf(":account:");
|
|
7136
|
+
if (slot === -1) return isValidProviderId(id);
|
|
7137
|
+
return isValidProviderId(id.slice(0, slot)) && OAUTH_ACCOUNT_NAME_RE.test(id.slice(slot + ":account:".length));
|
|
7138
|
+
}
|
|
6519
7139
|
const provider = /^provider:([^:]+)(?::(.+))?$/.exec(base);
|
|
6520
7140
|
if (!provider || !isValidProviderId(provider[1])) return false;
|
|
6521
7141
|
const suffix = provider[2];
|
|
@@ -6667,7 +7287,7 @@ function appendError(errors, context, error) {
|
|
|
6667
7287
|
errors.push(`${context}: ${errorMessage(error)}`);
|
|
6668
7288
|
}
|
|
6669
7289
|
function credentialIsReferenced(registry, authRef) {
|
|
6670
|
-
return registry.providers.some((provider) => provider.authRef === authRef);
|
|
7290
|
+
return registry.providers.some((provider) => provider.authRef === authRef || provider.defaultAuthRef === authRef || Object.values(provider.authAccounts ?? {}).some((slot) => slot.authRef === authRef));
|
|
6671
7291
|
}
|
|
6672
7292
|
async function journalCredentialWrite(authRef) {
|
|
6673
7293
|
if (!await queueCredentialDelete(authRef)) {
|
|
@@ -6856,8 +7476,8 @@ function compactLogValueWithMarker(value, max) {
|
|
|
6856
7476
|
function systemPreview(system) {
|
|
6857
7477
|
if (typeof system === "string") return compactLogValue(system, REQUEST_PREVIEW_MAX) || void 0;
|
|
6858
7478
|
if (!Array.isArray(system)) return void 0;
|
|
6859
|
-
const
|
|
6860
|
-
return compactLogValue(
|
|
7479
|
+
const text5 = system.map((block) => typeof block === "string" ? block : block && typeof block === "object" && typeof block.text === "string" ? block.text : "").filter(Boolean).join(" ");
|
|
7480
|
+
return compactLogValue(text5, REQUEST_PREVIEW_MAX) || void 0;
|
|
6861
7481
|
}
|
|
6862
7482
|
function inlineSystemPreview(messages) {
|
|
6863
7483
|
if (!Array.isArray(messages)) return void 0;
|
|
@@ -6883,9 +7503,9 @@ function getLatestMessagePreview(messages, system) {
|
|
|
6883
7503
|
if (typeof content === "string") {
|
|
6884
7504
|
summary = content;
|
|
6885
7505
|
} else if (Array.isArray(content)) {
|
|
6886
|
-
const
|
|
6887
|
-
if (
|
|
6888
|
-
summary =
|
|
7506
|
+
const text5 = content.filter((block) => Boolean(block && typeof block === "object")).filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join(" ");
|
|
7507
|
+
if (text5.trim()) {
|
|
7508
|
+
summary = text5;
|
|
6889
7509
|
} else {
|
|
6890
7510
|
const blockTypes = [...new Set(content.filter((block) => Boolean(block && typeof block === "object")).map((block) => typeof block.type === "string" ? block.type : "unknown"))];
|
|
6891
7511
|
if (blockTypes.length > 0) blockSummary = `${role}: [${blockTypes.join(", ")}]`;
|
|
@@ -6981,6 +7601,7 @@ function writeInferenceRequestLog(path, entry) {
|
|
|
6981
7601
|
...claudeSessionId ? { claudeSessionId } : {},
|
|
6982
7602
|
modelId: compactLogValue(entry.modelId),
|
|
6983
7603
|
...entry.effort ? { effort: compactLogValue(entry.effort, 100) } : {},
|
|
7604
|
+
...entry.serviceTier ? { serviceTier: compactLogValue(entry.serviceTier, 40) } : {},
|
|
6984
7605
|
provider: compactLogValue(entry.provider, 200),
|
|
6985
7606
|
route: entry.route,
|
|
6986
7607
|
...entry.stream !== void 0 ? { stream: entry.stream } : {},
|
|
@@ -7243,6 +7864,62 @@ function parseModelList(body, npm) {
|
|
|
7243
7864
|
}
|
|
7244
7865
|
return models;
|
|
7245
7866
|
}
|
|
7867
|
+
function materializeTemplateModel(template, model, baseUrl) {
|
|
7868
|
+
const npm = model.npm ?? template.npm;
|
|
7869
|
+
const { id, upstreamModelId: normalizedUpstream } = normalizeGoogleModelId(model.id, npm);
|
|
7870
|
+
const family = model.family ?? (id.split(/[-/:]/)[0] ?? id);
|
|
7871
|
+
const freeStatus = model.freeStatus ?? classifyFreeStatus({ model });
|
|
7872
|
+
return {
|
|
7873
|
+
...model,
|
|
7874
|
+
id,
|
|
7875
|
+
name: normalizeGoogleDisplayName(model.name, id),
|
|
7876
|
+
upstreamModelId: model.upstreamModelId ?? normalizedUpstream,
|
|
7877
|
+
family,
|
|
7878
|
+
brand: model.brand ?? deriveBrand(family),
|
|
7879
|
+
contextWindow: model.contextWindow ?? resolveContextWindow(id),
|
|
7880
|
+
isFree: model.isFree ?? isFreeStatus(freeStatus),
|
|
7881
|
+
freeStatus,
|
|
7882
|
+
modelFormat: model.modelFormat ?? modelFormatForNpm(npm),
|
|
7883
|
+
npm,
|
|
7884
|
+
apiUrl: model.apiUrl ?? baseUrl
|
|
7885
|
+
};
|
|
7886
|
+
}
|
|
7887
|
+
function normalizeTemplateOverlay(template, model) {
|
|
7888
|
+
const npm = model.npm ?? template.npm;
|
|
7889
|
+
const { id } = normalizeGoogleModelId(model.id, npm);
|
|
7890
|
+
const family = model.family;
|
|
7891
|
+
const hasFreeMetadata = model.cost !== void 0 || model.isFree !== void 0 || model.freeStatus !== void 0;
|
|
7892
|
+
const freeStatus = hasFreeMetadata ? model.freeStatus ?? classifyFreeStatus({ model }) : void 0;
|
|
7893
|
+
return {
|
|
7894
|
+
...model,
|
|
7895
|
+
id,
|
|
7896
|
+
name: normalizeGoogleDisplayName(model.name, id),
|
|
7897
|
+
...model.upstreamModelId !== void 0 ? { upstreamModelId: normalizeGoogleModelId(model.upstreamModelId, npm).upstreamModelId } : {},
|
|
7898
|
+
...model.npm !== void 0 ? { npm } : {},
|
|
7899
|
+
...model.modelFormat !== void 0 ? { modelFormat: model.modelFormat } : model.npm !== void 0 ? { modelFormat: modelFormatForNpm(npm) } : {},
|
|
7900
|
+
...family !== void 0 ? { family, brand: model.brand ?? deriveBrand(family) } : {},
|
|
7901
|
+
...freeStatus !== void 0 ? {
|
|
7902
|
+
freeStatus,
|
|
7903
|
+
isFree: model.isFree ?? isFreeStatus(freeStatus)
|
|
7904
|
+
} : {}
|
|
7905
|
+
};
|
|
7906
|
+
}
|
|
7907
|
+
function applyTemplateModelMetadata(template, discovered, _baseUrl) {
|
|
7908
|
+
const curated = new Map(
|
|
7909
|
+
(template.staticModels ?? []).map((model) => normalizeTemplateOverlay(template, model)).map((model) => [model.id, model])
|
|
7910
|
+
);
|
|
7911
|
+
const visible = template.staticModelPolicy === "allowlist" ? discovered.filter((model) => curated.has(model.id)) : discovered;
|
|
7912
|
+
return visible.map((model) => {
|
|
7913
|
+
const overlay = curated.get(model.id);
|
|
7914
|
+
if (!overlay) return model;
|
|
7915
|
+
return {
|
|
7916
|
+
...model,
|
|
7917
|
+
...overlay,
|
|
7918
|
+
id: model.id,
|
|
7919
|
+
upstreamModelId: overlay.upstreamModelId ?? model.upstreamModelId
|
|
7920
|
+
};
|
|
7921
|
+
});
|
|
7922
|
+
}
|
|
7246
7923
|
async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeaders) {
|
|
7247
7924
|
const trimmedOverride = baseUrlOverride?.trim();
|
|
7248
7925
|
const baseUrl = (trimmedOverride || template.defaultBaseUrl)?.replace(/\/$/, "");
|
|
@@ -7254,19 +7931,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7254
7931
|
};
|
|
7255
7932
|
}
|
|
7256
7933
|
if (template.modelSource === "static-seed") {
|
|
7257
|
-
const models = (template.staticModels
|
|
7258
|
-
const family = sm.id.split(/[-/:]/)[0] ?? sm.id;
|
|
7259
|
-
return {
|
|
7260
|
-
id: sm.id,
|
|
7261
|
-
name: sm.name,
|
|
7262
|
-
upstreamModelId: sm.id,
|
|
7263
|
-
family,
|
|
7264
|
-
brand: deriveBrand(family),
|
|
7265
|
-
contextWindow: resolveContextWindow(sm.id),
|
|
7266
|
-
modelFormat: modelFormatForNpm(template.npm),
|
|
7267
|
-
npm: template.npm
|
|
7268
|
-
};
|
|
7269
|
-
});
|
|
7934
|
+
const models = (template.staticModels ?? []).map((model) => materializeTemplateModel(template, model, baseUrl));
|
|
7270
7935
|
return { models, baseUrl };
|
|
7271
7936
|
}
|
|
7272
7937
|
const url = modelsUrl(baseUrl, template);
|
|
@@ -7336,7 +8001,7 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7336
8001
|
}
|
|
7337
8002
|
} catch {
|
|
7338
8003
|
}
|
|
7339
|
-
const models = parseModelList(json, template.npm);
|
|
8004
|
+
const models = applyTemplateModelMetadata(template, parseModelList(json, template.npm), baseUrl);
|
|
7340
8005
|
if (models.length === 0) {
|
|
7341
8006
|
return {
|
|
7342
8007
|
models: [],
|
|
@@ -7361,6 +8026,25 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7361
8026
|
}
|
|
7362
8027
|
|
|
7363
8028
|
// src/registry/add-template.ts
|
|
8029
|
+
function existingProviderError(template, existing, replaceExisting) {
|
|
8030
|
+
if (!existing) return null;
|
|
8031
|
+
const removeFirst = `Remove it first with: clodex providers remove ${template.id}`;
|
|
8032
|
+
if (!replaceExisting) {
|
|
8033
|
+
return {
|
|
8034
|
+
added: false,
|
|
8035
|
+
error: `${template.name} is already configured.`,
|
|
8036
|
+
hint: removeFirst
|
|
8037
|
+
};
|
|
8038
|
+
}
|
|
8039
|
+
if (existing.defaultAuthRef !== void 0 || existing.activeAuthAccount !== void 0 || Object.keys(existing.authAccounts ?? {}).length > 0) {
|
|
8040
|
+
return {
|
|
8041
|
+
added: false,
|
|
8042
|
+
error: `${template.name} has OAuth account state and cannot be replaced in place.`,
|
|
8043
|
+
hint: removeFirst
|
|
8044
|
+
};
|
|
8045
|
+
}
|
|
8046
|
+
return null;
|
|
8047
|
+
}
|
|
7364
8048
|
async function probeTemplatePackage(template) {
|
|
7365
8049
|
if (!template.supported) return template.unsupportedReason ?? "Provider is not supported yet.";
|
|
7366
8050
|
if (!template.npm) return "Template is missing an SDK package.";
|
|
@@ -7394,13 +8078,10 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7394
8078
|
const existingState = await withRegistryWriteLock(() => {
|
|
7395
8079
|
const registry = loadRegistryStrict();
|
|
7396
8080
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7397
|
-
|
|
8081
|
+
const error = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8082
|
+
if (error) {
|
|
7398
8083
|
return {
|
|
7399
|
-
error
|
|
7400
|
-
added: false,
|
|
7401
|
-
error: `${template.name} is already configured.`,
|
|
7402
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7403
|
-
}
|
|
8084
|
+
error
|
|
7404
8085
|
};
|
|
7405
8086
|
}
|
|
7406
8087
|
return { authRef: existing?.authRef ?? null, error: null };
|
|
@@ -7424,24 +8105,21 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7424
8105
|
}
|
|
7425
8106
|
const pricingCache = loadPricingCache();
|
|
7426
8107
|
const platform = pricingPlatformForProvider(template.id, template.id);
|
|
7427
|
-
const
|
|
7428
|
-
|
|
7429
|
-
|
|
7430
|
-
|
|
7431
|
-
);
|
|
8108
|
+
const discoveredModels = usableModels.map((m) => ({
|
|
8109
|
+
...m,
|
|
8110
|
+
apiUrl: m.apiUrl ?? fetched.baseUrl
|
|
8111
|
+
}));
|
|
8112
|
+
const pricedModels = template.preserveModelPricing ? discoveredModels : enrichModelsWithPricing(discoveredModels, buildPricingIndex(pricingCache), platform);
|
|
7432
8113
|
const account = `provider:${template.id}`;
|
|
7433
8114
|
const result = await withProviderMutationLock(template.id, async () => {
|
|
7434
8115
|
const currentState = await withRegistryWriteLock(() => {
|
|
7435
8116
|
const registry = loadRegistryStrict();
|
|
7436
8117
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7437
|
-
|
|
8118
|
+
const error = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8119
|
+
if (error) {
|
|
7438
8120
|
return {
|
|
7439
8121
|
existingAuthRef: null,
|
|
7440
|
-
error
|
|
7441
|
-
added: false,
|
|
7442
|
-
error: `${template.name} is already configured.`,
|
|
7443
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7444
|
-
}
|
|
8122
|
+
error
|
|
7445
8123
|
};
|
|
7446
8124
|
}
|
|
7447
8125
|
return { existingAuthRef: existing?.authRef ?? null, error: null };
|
|
@@ -7463,13 +8141,8 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7463
8141
|
return withRegistryWriteLock(async () => {
|
|
7464
8142
|
const registry = loadRegistryStrict();
|
|
7465
8143
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7466
|
-
|
|
7467
|
-
|
|
7468
|
-
added: false,
|
|
7469
|
-
error: `${template.name} is already configured.`,
|
|
7470
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7471
|
-
};
|
|
7472
|
-
}
|
|
8144
|
+
const existingError = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8145
|
+
if (existingError) return existingError;
|
|
7473
8146
|
if ((existing?.authRef ?? null) !== currentState.existingAuthRef) {
|
|
7474
8147
|
return {
|
|
7475
8148
|
added: false,
|
|
@@ -7485,6 +8158,7 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7485
8158
|
enabled: true,
|
|
7486
8159
|
authRef,
|
|
7487
8160
|
authType: trimmedKey ? template.authType : "none",
|
|
8161
|
+
...template.preserveModelPricing ? { preserveModelPricing: true } : {},
|
|
7488
8162
|
...!trimmedKey && template.anonymousFreeModels ? { subscriptionFilter: "free" } : {},
|
|
7489
8163
|
api: {
|
|
7490
8164
|
npm: template.npm,
|
|
@@ -7559,11 +8233,21 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7559
8233
|
credentialDeleted: false,
|
|
7560
8234
|
error: `Provider not found: ${id}`
|
|
7561
8235
|
},
|
|
7562
|
-
|
|
8236
|
+
queuedRefs: []
|
|
7563
8237
|
};
|
|
7564
8238
|
}
|
|
7565
8239
|
const [removedProvider] = registry.providers.splice(index, 1);
|
|
7566
|
-
const
|
|
8240
|
+
const queuedRefs = [];
|
|
8241
|
+
if (opts?.deleteCredential !== false) {
|
|
8242
|
+
const credentialRefs = /* @__PURE__ */ new Set([
|
|
8243
|
+
removedProvider.authRef,
|
|
8244
|
+
...removedProvider.defaultAuthRef ? [removedProvider.defaultAuthRef] : [],
|
|
8245
|
+
...Object.values(removedProvider.authAccounts ?? {}).map((slot) => slot.authRef)
|
|
8246
|
+
]);
|
|
8247
|
+
for (const authRef of credentialRefs) {
|
|
8248
|
+
if (await queueCredentialDelete(authRef)) queuedRefs.push(authRef);
|
|
8249
|
+
}
|
|
8250
|
+
}
|
|
7567
8251
|
saveRegistry(registry);
|
|
7568
8252
|
return {
|
|
7569
8253
|
result: {
|
|
@@ -7572,14 +8256,14 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7572
8256
|
name: removedProvider.name,
|
|
7573
8257
|
credentialDeleted: false
|
|
7574
8258
|
},
|
|
7575
|
-
|
|
8259
|
+
queuedRefs
|
|
7576
8260
|
};
|
|
7577
8261
|
});
|
|
7578
|
-
if (removal.
|
|
8262
|
+
if (removal.queuedRefs.length > 0) {
|
|
7579
8263
|
try {
|
|
7580
8264
|
const cleanup = await reconcilePendingCredentialDeletes();
|
|
7581
|
-
removal.result.credentialDeleted = cleanup.deleted.includes(
|
|
7582
|
-
removal.result.credentialCleanupPending = cleanup.pending.includes(
|
|
8265
|
+
removal.result.credentialDeleted = removal.queuedRefs.every((ref) => cleanup.deleted.includes(ref));
|
|
8266
|
+
removal.result.credentialCleanupPending = removal.queuedRefs.some((ref) => cleanup.pending.includes(ref)) || cleanup.persistenceError !== void 0;
|
|
7583
8267
|
} catch {
|
|
7584
8268
|
removal.result.credentialCleanupPending = true;
|
|
7585
8269
|
}
|
|
@@ -7590,6 +8274,60 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7590
8274
|
async function removeProviderFromRegistry(id, opts) {
|
|
7591
8275
|
return withProviderMutationLock(id, () => removeProviderWithinLifecycle(id, opts));
|
|
7592
8276
|
}
|
|
8277
|
+
async function setActiveOAuthAccount(id, account) {
|
|
8278
|
+
return withProviderMutationLock(id, () => withRegistryWriteLock(() => {
|
|
8279
|
+
const registry = loadRegistryStrict();
|
|
8280
|
+
const provider = registry.providers.find((p13) => p13.id === id);
|
|
8281
|
+
if (!provider) return { updated: false, error: `Provider not found: ${id}` };
|
|
8282
|
+
const name = account?.trim().toLowerCase();
|
|
8283
|
+
const previous = provider.activeAuthAccount?.trim() || void 0;
|
|
8284
|
+
const selectedSlot = name ? getOAuthAccountSlot(provider, name) : void 0;
|
|
8285
|
+
let selectionChanged = previous !== name;
|
|
8286
|
+
let storageChanged = false;
|
|
8287
|
+
if (name && !selectedSlot) {
|
|
8288
|
+
const slots = provider.authAccounts ?? {};
|
|
8289
|
+
const available = Object.keys(slots).sort().join(", ") || "none";
|
|
8290
|
+
return {
|
|
8291
|
+
updated: false,
|
|
8292
|
+
error: `${provider.name} has no account named "${name}" (available: ${available}).`
|
|
8293
|
+
};
|
|
8294
|
+
}
|
|
8295
|
+
if (name && provider.authType === "oauth") {
|
|
8296
|
+
storageChanged = storeActiveOAuthAccount(provider, name, selectedSlot.authRef);
|
|
8297
|
+
}
|
|
8298
|
+
if (selectionChanged && provider.authType === "oauth") {
|
|
8299
|
+
const previousSlot = previous ? getOAuthAccountSlot(provider, previous) : void 0;
|
|
8300
|
+
if (previous && previousSlot && provider.modelsCache && provider.authAccounts) {
|
|
8301
|
+
provider.authAccounts[previous] = {
|
|
8302
|
+
...previousSlot,
|
|
8303
|
+
modelsCache: provider.modelsCache
|
|
8304
|
+
};
|
|
8305
|
+
}
|
|
8306
|
+
const selectedCache = selectedSlot?.modelsCache;
|
|
8307
|
+
if (selectedCache) {
|
|
8308
|
+
provider.modelsCache = selectedCache;
|
|
8309
|
+
provider.refreshedAt = selectedCache.fetchedAt;
|
|
8310
|
+
} else {
|
|
8311
|
+
delete provider.modelsCache;
|
|
8312
|
+
delete provider.refreshedAt;
|
|
8313
|
+
}
|
|
8314
|
+
}
|
|
8315
|
+
if (name && provider.authType !== "oauth") {
|
|
8316
|
+
storageChanged = clearActiveOAuthAccount(provider) || storageChanged;
|
|
8317
|
+
if (provider.activeAuthAccount !== name) {
|
|
8318
|
+
provider.activeAuthAccount = name;
|
|
8319
|
+
storageChanged = true;
|
|
8320
|
+
}
|
|
8321
|
+
} else if (!name && (previous !== void 0 || provider.defaultAuthRef !== void 0)) {
|
|
8322
|
+
storageChanged = clearActiveOAuthAccount(provider);
|
|
8323
|
+
} else if (!name) {
|
|
8324
|
+
selectionChanged = false;
|
|
8325
|
+
}
|
|
8326
|
+
const migrationNeedsPersistence = registry.schemaVersion < REGISTRY_SCHEMA_VERSION_WITH_MATERIALIZED_ACTIVE_ACCOUNT && provider.defaultAuthRef !== void 0;
|
|
8327
|
+
if (selectionChanged || storageChanged || migrationNeedsPersistence) saveRegistry(registry);
|
|
8328
|
+
return { updated: true, changed: selectionChanged, ...name ? { account: name } : {}, provider };
|
|
8329
|
+
}));
|
|
8330
|
+
}
|
|
7593
8331
|
function toggleProviderEnabled(id) {
|
|
7594
8332
|
return withRegistryWriteLockSync(() => {
|
|
7595
8333
|
const registry = loadRegistryStrict();
|
|
@@ -7853,6 +8591,41 @@ function resolveModelSource(provider) {
|
|
|
7853
8591
|
}
|
|
7854
8592
|
|
|
7855
8593
|
// src/registry/refresh-credentials.ts
|
|
8594
|
+
function refreshCredentialSnapshot(provider, selected = process.env[OAUTH_ACCOUNT_ENV], options = {}) {
|
|
8595
|
+
const environmentAccount = selected === null ? void 0 : selected?.trim().toLowerCase() || void 0;
|
|
8596
|
+
const effective = projectSelectedOAuthAccount(provider, environmentAccount ?? "");
|
|
8597
|
+
const activeAuthAccount = provider.activeAuthAccount?.trim() || void 0;
|
|
8598
|
+
const selectedName = environmentAccount || activeAuthAccount;
|
|
8599
|
+
const selectedAccount = provider.authType === "oauth" && selectedName ? getOAuthAccountSlot(provider, selectedName) : void 0;
|
|
8600
|
+
const credentialOverride = effective.authType !== "none" && effective.authRef !== "none:anonymous" ? resolveProviderCredentialOverrideState(effective.id, process.env, {
|
|
8601
|
+
ignoreProviderOverride: options.ignoreProviderOverride
|
|
8602
|
+
}) : null;
|
|
8603
|
+
return {
|
|
8604
|
+
provider: {
|
|
8605
|
+
id: provider.id,
|
|
8606
|
+
addedAt: provider.addedAt,
|
|
8607
|
+
enabled: provider.enabled,
|
|
8608
|
+
authType: provider.authType,
|
|
8609
|
+
templateId: provider.templateId,
|
|
8610
|
+
api: {
|
|
8611
|
+
...provider.api,
|
|
8612
|
+
...provider.api.headers ? { headers: { ...provider.api.headers } } : {}
|
|
8613
|
+
}
|
|
8614
|
+
},
|
|
8615
|
+
authRef: effective.authRef,
|
|
8616
|
+
...activeAuthAccount ? { activeAuthAccount } : {},
|
|
8617
|
+
...environmentAccount ? { environmentAccount } : {},
|
|
8618
|
+
...selectedName && selectedAccount ? {
|
|
8619
|
+
selectedAccount: {
|
|
8620
|
+
name: selectedName,
|
|
8621
|
+
authRef: selectedAccount.authRef,
|
|
8622
|
+
addedAt: selectedAccount.addedAt
|
|
8623
|
+
}
|
|
8624
|
+
} : {},
|
|
8625
|
+
...credentialOverride ? { credentialOverride } : {},
|
|
8626
|
+
...options.ignoreProviderOverride ? { ignoreProviderOverride: true } : {}
|
|
8627
|
+
};
|
|
8628
|
+
}
|
|
7856
8629
|
var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set([
|
|
7857
8630
|
"anything",
|
|
7858
8631
|
"local",
|
|
@@ -7892,20 +8665,36 @@ function skipWithCachedModels(provider, reason) {
|
|
|
7892
8665
|
reason
|
|
7893
8666
|
};
|
|
7894
8667
|
}
|
|
7895
|
-
async function
|
|
7896
|
-
|
|
7897
|
-
|
|
8668
|
+
async function resolveRefreshCredentialWithSource(provider, resolveKey, selected = process.env[OAUTH_ACCOUNT_ENV], options = {}) {
|
|
8669
|
+
const effectiveProvider = projectSelectedOAuthAccount(provider, selected ?? "");
|
|
8670
|
+
if (isAnonymousProvider(effectiveProvider) || effectiveProvider.authType === "none" || effectiveProvider.authRef === "none:anonymous") return { credential: null };
|
|
8671
|
+
let resolved;
|
|
7898
8672
|
try {
|
|
7899
|
-
|
|
8673
|
+
const result = await resolveKey(effectiveProvider);
|
|
8674
|
+
resolved = typeof result === "object" && result !== null ? result : {
|
|
8675
|
+
credential: result,
|
|
8676
|
+
// Backwards-compatible resolvers return only the key. Attribute the
|
|
8677
|
+
// current usable override for them; production resolvers return the
|
|
8678
|
+
// source atomically with the credential and close this race fully.
|
|
8679
|
+
...result ? {
|
|
8680
|
+
credentialOverride: resolveProviderCredentialOverrideState(
|
|
8681
|
+
effectiveProvider.id,
|
|
8682
|
+
process.env,
|
|
8683
|
+
{ ignoreProviderOverride: options.ignoreProviderOverride }
|
|
8684
|
+
) ?? void 0
|
|
8685
|
+
} : {}
|
|
8686
|
+
};
|
|
7900
8687
|
} catch {
|
|
7901
|
-
|
|
8688
|
+
resolved = { credential: null };
|
|
7902
8689
|
}
|
|
7903
|
-
if (!isLikelyPlaceholderKey(
|
|
7904
|
-
|
|
8690
|
+
if (resolved.credentialOverride || !isLikelyPlaceholderKey(resolved.credential)) {
|
|
8691
|
+
return resolved;
|
|
8692
|
+
}
|
|
8693
|
+
for (const envVar of ENV_FALLBACK_BY_PROVIDER[effectiveProvider.id] ?? []) {
|
|
7905
8694
|
const fromEnv = process.env[envVar]?.trim();
|
|
7906
|
-
if (fromEnv && !isLikelyPlaceholderKey(fromEnv)) return fromEnv;
|
|
8695
|
+
if (fromEnv && !isLikelyPlaceholderKey(fromEnv)) return { credential: fromEnv };
|
|
7907
8696
|
}
|
|
7908
|
-
return
|
|
8697
|
+
return resolved;
|
|
7909
8698
|
}
|
|
7910
8699
|
|
|
7911
8700
|
// src/data/openai-oauth-models.ts
|
|
@@ -8055,10 +8844,13 @@ async function refreshOpenAiOAuthModels(accessToken) {
|
|
|
8055
8844
|
if (chatGptEntries.length > 0) {
|
|
8056
8845
|
return { models: toModels(chatGptEntries), source: "live" };
|
|
8057
8846
|
}
|
|
8847
|
+
const failures = [codexResult.error, chatGptResult.error].filter((error) => error !== void 0);
|
|
8848
|
+
const credentialFailure = failures.find((error) => /(?:\brejected\b|\b401\b|\b403\b)/i.test(error));
|
|
8058
8849
|
return {
|
|
8059
8850
|
models: [...seedById.values()],
|
|
8060
8851
|
source: "seed",
|
|
8061
|
-
failureReason: chatGptResult.error ?? codexResult.error
|
|
8852
|
+
failureReason: credentialFailure ?? chatGptResult.error ?? codexResult.error,
|
|
8853
|
+
credentialRejected: credentialFailure !== void 0
|
|
8062
8854
|
};
|
|
8063
8855
|
}
|
|
8064
8856
|
async function refreshApiListProvider(provider, apiKey) {
|
|
@@ -8087,7 +8879,7 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
8087
8879
|
return { models: [], error: fetched2.error ?? "No models returned.", baseUrl: fetched2.baseUrl };
|
|
8088
8880
|
}
|
|
8089
8881
|
return {
|
|
8090
|
-
models: fetched2.models.map((m) => ({ ...m, apiUrl: fetched2.baseUrl })),
|
|
8882
|
+
models: fetched2.models.map((m) => ({ ...m, apiUrl: m.apiUrl ?? fetched2.baseUrl })),
|
|
8091
8883
|
baseUrl: fetched2.baseUrl
|
|
8092
8884
|
};
|
|
8093
8885
|
}
|
|
@@ -8106,37 +8898,116 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
8106
8898
|
return {
|
|
8107
8899
|
models: usableModels.map((m) => ({
|
|
8108
8900
|
...m,
|
|
8109
|
-
apiUrl: fetched.baseUrl
|
|
8901
|
+
apiUrl: m.apiUrl ?? fetched.baseUrl
|
|
8110
8902
|
})),
|
|
8111
8903
|
baseUrl: fetched.baseUrl
|
|
8112
8904
|
};
|
|
8113
8905
|
}
|
|
8114
|
-
function updateProviderCache(registry, providerId, models, baseUrl) {
|
|
8906
|
+
function updateProviderCache(registry, providerId, models, baseUrl, credentialSnapshot) {
|
|
8115
8907
|
const idx = registry.providers.findIndex((p13) => p13.id === providerId);
|
|
8116
8908
|
if (idx < 0) return;
|
|
8117
8909
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8118
8910
|
const existing = registry.providers[idx];
|
|
8911
|
+
const modelsCache = { fetchedAt: now, models };
|
|
8912
|
+
const selectedAccount = credentialSnapshot?.selectedAccount;
|
|
8913
|
+
const temporaryAccount = isTemporaryAccountSelection(credentialSnapshot);
|
|
8914
|
+
const selectedSlot = selectedAccount ? getOAuthAccountSlot(existing, selectedAccount.name) : void 0;
|
|
8915
|
+
const authAccounts = selectedAccount && selectedSlot ? {
|
|
8916
|
+
...existing.authAccounts,
|
|
8917
|
+
[selectedAccount.name]: {
|
|
8918
|
+
...selectedSlot,
|
|
8919
|
+
modelsCache
|
|
8920
|
+
}
|
|
8921
|
+
} : existing.authAccounts;
|
|
8119
8922
|
registry.providers[idx] = {
|
|
8120
8923
|
...existing,
|
|
8121
|
-
refreshedAt: now,
|
|
8122
8924
|
api: baseUrl ? { ...existing.api, url: baseUrl } : existing.api,
|
|
8123
|
-
|
|
8124
|
-
|
|
8125
|
-
models
|
|
8126
|
-
}
|
|
8925
|
+
...authAccounts ? { authAccounts } : {},
|
|
8926
|
+
...!temporaryAccount ? { refreshedAt: now, modelsCache } : {}
|
|
8127
8927
|
};
|
|
8128
8928
|
}
|
|
8929
|
+
function isTemporaryAccountSelection(snapshot) {
|
|
8930
|
+
return Boolean(
|
|
8931
|
+
snapshot?.environmentAccount && snapshot.selectedAccount && snapshot.environmentAccount !== snapshot.activeAuthAccount
|
|
8932
|
+
);
|
|
8933
|
+
}
|
|
8934
|
+
function providerWithRefreshCache(provider, snapshot) {
|
|
8935
|
+
const selected = snapshot?.selectedAccount;
|
|
8936
|
+
const temporary = isTemporaryAccountSelection(snapshot);
|
|
8937
|
+
if (!temporary || !selected) return provider;
|
|
8938
|
+
const projected = { ...provider };
|
|
8939
|
+
const cache = getOAuthAccountSlot(provider, selected.name)?.modelsCache;
|
|
8940
|
+
if (cache) projected.modelsCache = cache;
|
|
8941
|
+
else delete projected.modelsCache;
|
|
8942
|
+
return projected;
|
|
8943
|
+
}
|
|
8129
8944
|
function providerDiscoveryInputsMatch(current, started) {
|
|
8130
|
-
return current.authRef === started.authRef && current.authType === started.authType && current.templateId === started.templateId && isDeepStrictEqual(current.api, started.api);
|
|
8945
|
+
return current.authRef === started.authRef && current.enabled === started.enabled && current.authType === started.authType && current.templateId === started.templateId && isDeepStrictEqual(current.api, started.api);
|
|
8946
|
+
}
|
|
8947
|
+
function assertRefreshCredentialStillCurrent(current, snapshot) {
|
|
8948
|
+
const routing = snapshot.provider;
|
|
8949
|
+
if (current.id !== routing.id || current.addedAt !== routing.addedAt || current.enabled !== routing.enabled || current.authType !== routing.authType || current.templateId !== routing.templateId || !isDeepStrictEqual(current.api, routing.api)) {
|
|
8950
|
+
throw new Error("Provider configuration changed while credentials were resolving.");
|
|
8951
|
+
}
|
|
8952
|
+
const activeAuthAccount = current.activeAuthAccount?.trim() || void 0;
|
|
8953
|
+
if (activeAuthAccount !== snapshot.activeAuthAccount) {
|
|
8954
|
+
throw new Error("Provider account selection changed while models were refreshing.");
|
|
8955
|
+
}
|
|
8956
|
+
let currentSnapshot;
|
|
8957
|
+
try {
|
|
8958
|
+
currentSnapshot = refreshCredentialSnapshot(
|
|
8959
|
+
current,
|
|
8960
|
+
snapshot.environmentAccount ?? null,
|
|
8961
|
+
{ ignoreProviderOverride: snapshot.ignoreProviderOverride }
|
|
8962
|
+
);
|
|
8963
|
+
} catch {
|
|
8964
|
+
throw new Error("Provider account selection changed while models were refreshing.");
|
|
8965
|
+
}
|
|
8966
|
+
if (currentSnapshot.authRef !== snapshot.authRef) {
|
|
8967
|
+
throw new Error("Provider credentials changed while models were refreshing.");
|
|
8968
|
+
}
|
|
8969
|
+
if (!isDeepStrictEqual(currentSnapshot.selectedAccount, snapshot.selectedAccount)) {
|
|
8970
|
+
throw new Error("Provider account credentials changed while models were refreshing.");
|
|
8971
|
+
}
|
|
8972
|
+
if (!isDeepStrictEqual(currentSnapshot.credentialOverride, snapshot.credentialOverride)) {
|
|
8973
|
+
throw new Error("Provider credential override changed while models were refreshing.");
|
|
8974
|
+
}
|
|
8131
8975
|
}
|
|
8132
|
-
async function refreshProviderModels(providerId, apiKey, registry) {
|
|
8976
|
+
async function refreshProviderModels(providerId, apiKey, registry, credentialSnapshot) {
|
|
8133
8977
|
const workingRegistry = registry ?? loadRegistryStrict();
|
|
8134
8978
|
const provider = workingRegistry.providers.find((p13) => p13.id === providerId);
|
|
8135
8979
|
if (!provider) {
|
|
8136
8980
|
return { id: providerId, name: providerId, ok: false, reason: "Provider not found." };
|
|
8137
8981
|
}
|
|
8982
|
+
if (credentialSnapshot) {
|
|
8983
|
+
try {
|
|
8984
|
+
assertRefreshCredentialStillCurrent(provider, credentialSnapshot);
|
|
8985
|
+
} catch (err) {
|
|
8986
|
+
return {
|
|
8987
|
+
id: provider.id,
|
|
8988
|
+
name: provider.name,
|
|
8989
|
+
ok: false,
|
|
8990
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
8991
|
+
};
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
const cacheProvider = providerWithRefreshCache(provider, credentialSnapshot);
|
|
8995
|
+
if (credentialSnapshot?.credentialOverride) {
|
|
8996
|
+
return skipWithCachedModels(
|
|
8997
|
+
cacheProvider,
|
|
8998
|
+
`${credentialSnapshot.credentialOverride.variable} is a process-scoped provider credential override \u2014 skipped the persistent model refresh so another shell cannot inherit this credential's catalog.`
|
|
8999
|
+
);
|
|
9000
|
+
}
|
|
8138
9001
|
const source = resolveModelSource(provider);
|
|
8139
9002
|
if (source === "manual-only") {
|
|
9003
|
+
if (provider.authType !== "none" && !apiKey) {
|
|
9004
|
+
return {
|
|
9005
|
+
id: provider.id,
|
|
9006
|
+
name: provider.name,
|
|
9007
|
+
ok: false,
|
|
9008
|
+
reason: provider.authType === "oauth" ? "OAuth token not available \u2014 try signing in again with clodex providers auth." : "API key not available \u2014 cannot verify the saved model catalog."
|
|
9009
|
+
};
|
|
9010
|
+
}
|
|
8140
9011
|
return {
|
|
8141
9012
|
id: provider.id,
|
|
8142
9013
|
name: provider.name,
|
|
@@ -8146,7 +9017,8 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8146
9017
|
};
|
|
8147
9018
|
}
|
|
8148
9019
|
try {
|
|
8149
|
-
const previousModelCount =
|
|
9020
|
+
const previousModelCount = cacheProvider.modelsCache?.models.length ?? 0;
|
|
9021
|
+
const hadPreviousRefresh = isTemporaryAccountSelection(credentialSnapshot) ? cacheProvider.modelsCache !== void 0 : provider.refreshedAt !== void 0;
|
|
8150
9022
|
let models = [];
|
|
8151
9023
|
let baseUrl;
|
|
8152
9024
|
let oauthFallbackReason;
|
|
@@ -8159,11 +9031,21 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8159
9031
|
reason: "OAuth token not available \u2014 try signing in again with clodex providers auth."
|
|
8160
9032
|
};
|
|
8161
9033
|
}
|
|
8162
|
-
const oauthResult = await refreshOAuthProvider(
|
|
9034
|
+
const oauthResult = await refreshOAuthProvider(cacheProvider, apiKey);
|
|
8163
9035
|
const failureDetail = oauthResult.failureReason ? ` (${oauthResult.failureReason})` : "";
|
|
8164
|
-
if (oauthResult.source === "seed" &&
|
|
9036
|
+
if (oauthResult.source === "seed" && oauthResult.credentialRejected) {
|
|
9037
|
+
const count = cachedModelCount(cacheProvider);
|
|
9038
|
+
return {
|
|
9039
|
+
id: provider.id,
|
|
9040
|
+
name: provider.name,
|
|
9041
|
+
ok: false,
|
|
9042
|
+
...count > 0 ? { modelCount: count } : {},
|
|
9043
|
+
reason: `OAuth credential was rejected${failureDetail}. ` + (count > 0 ? `Kept ${count} cached model${count === 1 ? "" : "s"}, but sign in again before launching.` : "Sign in again before refreshing or launching.")
|
|
9044
|
+
};
|
|
9045
|
+
}
|
|
9046
|
+
if (oauthResult.source === "seed" && cachedModelCount(cacheProvider) > 0) {
|
|
8165
9047
|
return skipWithCachedModels(
|
|
8166
|
-
|
|
9048
|
+
cacheProvider,
|
|
8167
9049
|
`Live model discovery failed${failureDetail} \u2014 kept your existing cached model list instead of overwriting it with clodex's built-in fallback list. Try refreshing again later.`
|
|
8168
9050
|
);
|
|
8169
9051
|
}
|
|
@@ -8184,11 +9066,21 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8184
9066
|
const keyOptional = template?.apiKeyOptional === true;
|
|
8185
9067
|
const effectiveKey = keyOptional && isLikelyPlaceholderKey(apiKey) ? "" : apiKey;
|
|
8186
9068
|
if (!keyOptional && isLikelyPlaceholderKey(effectiveKey)) {
|
|
8187
|
-
if (cachedModelCount(
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
9069
|
+
if (cachedModelCount(cacheProvider) > 0) {
|
|
9070
|
+
if (isLegacyAnonymousCustomEndpoint(provider, effectiveKey)) {
|
|
9071
|
+
return skipWithCachedModels(
|
|
9072
|
+
cacheProvider,
|
|
9073
|
+
"Legacy anonymous custom endpoint \u2014 kept cached model list."
|
|
9074
|
+
);
|
|
9075
|
+
}
|
|
9076
|
+
const count = cachedModelCount(cacheProvider);
|
|
9077
|
+
return {
|
|
9078
|
+
id: provider.id,
|
|
9079
|
+
name: provider.name,
|
|
9080
|
+
ok: false,
|
|
9081
|
+
modelCount: count,
|
|
9082
|
+
reason: `A placeholder API key is configured \u2014 kept ${count} cached model${count === 1 ? "" : "s"}, but add this provider again with a real key before launching.`
|
|
9083
|
+
};
|
|
8192
9084
|
}
|
|
8193
9085
|
return {
|
|
8194
9086
|
id: provider.id,
|
|
@@ -8207,11 +9099,15 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8207
9099
|
}
|
|
8208
9100
|
const fetched = await refreshApiListProvider(provider, effectiveKey ?? "");
|
|
8209
9101
|
if (fetched.error) {
|
|
8210
|
-
if ((fetched.error.includes("rejected") || fetched.error.includes("401") || fetched.error.includes("403")) && cachedModelCount(
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
9102
|
+
if ((fetched.error.includes("rejected") || fetched.error.includes("401") || fetched.error.includes("403")) && cachedModelCount(cacheProvider) > 0) {
|
|
9103
|
+
const count = cachedModelCount(cacheProvider);
|
|
9104
|
+
return {
|
|
9105
|
+
id: provider.id,
|
|
9106
|
+
name: provider.name,
|
|
9107
|
+
ok: false,
|
|
9108
|
+
modelCount: count,
|
|
9109
|
+
reason: `${fetched.error} Kept ${count} cached model${count === 1 ? "" : "s"} from import, but update the API key before launching.`
|
|
9110
|
+
};
|
|
8215
9111
|
}
|
|
8216
9112
|
return { id: provider.id, name: provider.name, ok: false, reason: fetched.error };
|
|
8217
9113
|
}
|
|
@@ -8220,18 +9116,21 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8220
9116
|
}
|
|
8221
9117
|
const pricingCache = loadPricingCache();
|
|
8222
9118
|
const platform = pricingPlatformForProvider(provider.templateId, provider.id);
|
|
8223
|
-
const enriched = enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
|
|
9119
|
+
const enriched = provider.preserveModelPricing ? models : enrichModelsWithPricing(models, buildPricingIndex(pricingCache), platform);
|
|
8224
9120
|
await withRegistryWriteLock(() => {
|
|
8225
9121
|
const currentRegistry = loadRegistryStrict();
|
|
8226
9122
|
const currentProvider = currentRegistry.providers.find((candidate) => candidate.id === providerId);
|
|
8227
9123
|
if (!currentProvider) throw new Error("Provider was removed while models were refreshing.");
|
|
9124
|
+
if (credentialSnapshot) {
|
|
9125
|
+
assertRefreshCredentialStillCurrent(currentProvider, credentialSnapshot);
|
|
9126
|
+
}
|
|
8228
9127
|
if (currentProvider.authRef !== provider.authRef) {
|
|
8229
9128
|
throw new Error("Provider credentials changed while models were refreshing.");
|
|
8230
9129
|
}
|
|
8231
9130
|
if (!providerDiscoveryInputsMatch(currentProvider, provider)) {
|
|
8232
9131
|
throw new Error("Provider configuration changed while models were refreshing.");
|
|
8233
9132
|
}
|
|
8234
|
-
updateProviderCache(currentRegistry, providerId, enriched, baseUrl);
|
|
9133
|
+
updateProviderCache(currentRegistry, providerId, enriched, baseUrl, credentialSnapshot);
|
|
8235
9134
|
saveRegistry(currentRegistry);
|
|
8236
9135
|
});
|
|
8237
9136
|
enrichPricingAsync();
|
|
@@ -8240,7 +9139,7 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8240
9139
|
name: provider.name,
|
|
8241
9140
|
ok: true,
|
|
8242
9141
|
modelCount: enriched.length,
|
|
8243
|
-
previousModelCount:
|
|
9142
|
+
previousModelCount: hadPreviousRefresh ? previousModelCount : void 0,
|
|
8244
9143
|
reason: oauthFallbackReason
|
|
8245
9144
|
};
|
|
8246
9145
|
} catch (err) {
|
|
@@ -8252,13 +9151,63 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8252
9151
|
};
|
|
8253
9152
|
}
|
|
8254
9153
|
}
|
|
9154
|
+
async function refreshProviderModelsWithCredential(providerId, resolveKey, selected = process.env[OAUTH_ACCOUNT_ENV], options = {}) {
|
|
9155
|
+
return withProviderMutationLock(providerId, async () => {
|
|
9156
|
+
const provider = loadRegistryStrict().providers.find((candidate) => candidate.id === providerId);
|
|
9157
|
+
if (!provider) {
|
|
9158
|
+
return { id: providerId, name: providerId, ok: false, reason: "Provider not found." };
|
|
9159
|
+
}
|
|
9160
|
+
if (options.requireEnabled && !provider.enabled) {
|
|
9161
|
+
return {
|
|
9162
|
+
id: provider.id,
|
|
9163
|
+
name: provider.name,
|
|
9164
|
+
ok: true,
|
|
9165
|
+
skipped: true,
|
|
9166
|
+
reason: "Provider was disabled before its model refresh began."
|
|
9167
|
+
};
|
|
9168
|
+
}
|
|
9169
|
+
const accountOverride = selected === null ? null : selected ?? process.env[OAUTH_ACCOUNT_ENV] ?? null;
|
|
9170
|
+
const snapshot = refreshCredentialSnapshot(provider, accountOverride, {
|
|
9171
|
+
ignoreProviderOverride: options.ignoreProviderOverride
|
|
9172
|
+
});
|
|
9173
|
+
const resolved = await resolveRefreshCredentialWithSource(
|
|
9174
|
+
provider,
|
|
9175
|
+
resolveKey,
|
|
9176
|
+
accountOverride,
|
|
9177
|
+
{ ignoreProviderOverride: options.ignoreProviderOverride }
|
|
9178
|
+
);
|
|
9179
|
+
if (!isDeepStrictEqual(resolved.credentialOverride, snapshot.credentialOverride)) {
|
|
9180
|
+
return {
|
|
9181
|
+
id: provider.id,
|
|
9182
|
+
name: provider.name,
|
|
9183
|
+
ok: false,
|
|
9184
|
+
reason: "Provider credential override changed while models were refreshing."
|
|
9185
|
+
};
|
|
9186
|
+
}
|
|
9187
|
+
return refreshProviderModels(provider.id, resolved.credential, void 0, snapshot);
|
|
9188
|
+
});
|
|
9189
|
+
}
|
|
8255
9190
|
async function refreshAllProviderModels(resolveKey) {
|
|
8256
9191
|
const refreshed = [];
|
|
8257
9192
|
const registry = loadRegistryStrict();
|
|
8258
9193
|
const enabledProviders = registry.providers.filter((p13) => p13.enabled);
|
|
8259
9194
|
for (const provider of enabledProviders) {
|
|
8260
|
-
const
|
|
8261
|
-
|
|
9195
|
+
const accountOverride = process.env[OAUTH_ACCOUNT_ENV] ?? null;
|
|
9196
|
+
try {
|
|
9197
|
+
refreshed.push(await refreshProviderModelsWithCredential(
|
|
9198
|
+
provider.id,
|
|
9199
|
+
resolveKey,
|
|
9200
|
+
accountOverride,
|
|
9201
|
+
{ requireEnabled: true }
|
|
9202
|
+
));
|
|
9203
|
+
} catch (err) {
|
|
9204
|
+
refreshed.push({
|
|
9205
|
+
id: provider.id,
|
|
9206
|
+
name: provider.name,
|
|
9207
|
+
ok: false,
|
|
9208
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
9209
|
+
});
|
|
9210
|
+
}
|
|
8262
9211
|
}
|
|
8263
9212
|
return { refreshed };
|
|
8264
9213
|
}
|
|
@@ -8267,6 +9216,15 @@ async function refreshAllProviderModels(resolveKey) {
|
|
|
8267
9216
|
import pc3 from "picocolors";
|
|
8268
9217
|
import * as p2 from "@clack/prompts";
|
|
8269
9218
|
import open from "open";
|
|
9219
|
+
function validateOAuthAccountName(name) {
|
|
9220
|
+
const trimmed = name.trim().toLowerCase();
|
|
9221
|
+
if (!OAUTH_ACCOUNT_NAME_RE.test(trimmed)) {
|
|
9222
|
+
throw new Error(
|
|
9223
|
+
`Invalid account name "${name}" \u2014 use 1-32 characters: lowercase letters, digits, "-" or "_", starting with a letter or digit.`
|
|
9224
|
+
);
|
|
9225
|
+
}
|
|
9226
|
+
return trimmed;
|
|
9227
|
+
}
|
|
8270
9228
|
var OPENAI_DISPLAY = "OpenAI ChatGPT Plus/Pro";
|
|
8271
9229
|
var PROVIDER_DISPLAY = {
|
|
8272
9230
|
openai: OPENAI_DISPLAY,
|
|
@@ -8296,6 +9254,47 @@ async function runNativeDeviceCode(providerId) {
|
|
|
8296
9254
|
throw err;
|
|
8297
9255
|
}
|
|
8298
9256
|
}
|
|
9257
|
+
async function upsertOAuthAccountSlot(registryId, account, authRef, expectedAuthRef) {
|
|
9258
|
+
return withRegistryWriteLock(async () => {
|
|
9259
|
+
const registry = loadRegistryStrict();
|
|
9260
|
+
const entry = registry.providers.find((pr) => pr.id === registryId);
|
|
9261
|
+
if (!entry) {
|
|
9262
|
+
throw new Error(
|
|
9263
|
+
`Provider "${registryId}" is not configured yet \u2014 run the default sign-in first: clodex providers auth openai`
|
|
9264
|
+
);
|
|
9265
|
+
}
|
|
9266
|
+
const previousAuthRef = getOAuthAccountSlot(entry, account)?.authRef;
|
|
9267
|
+
if (previousAuthRef !== expectedAuthRef) {
|
|
9268
|
+
throw new Error(`Account "${account}" of "${registryId}" changed while its credential was being saved`);
|
|
9269
|
+
}
|
|
9270
|
+
const updated = {
|
|
9271
|
+
...entry,
|
|
9272
|
+
authAccounts: {
|
|
9273
|
+
...entry.authAccounts,
|
|
9274
|
+
[account]: {
|
|
9275
|
+
authRef,
|
|
9276
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
};
|
|
9280
|
+
if (entry.activeAuthAccount === account) {
|
|
9281
|
+
storeActiveOAuthAccount(updated, account, authRef);
|
|
9282
|
+
delete updated.modelsCache;
|
|
9283
|
+
delete updated.refreshedAt;
|
|
9284
|
+
}
|
|
9285
|
+
const idx = registry.providers.findIndex((provider) => provider.id === registryId);
|
|
9286
|
+
registry.providers[idx] = updated;
|
|
9287
|
+
if (previousAuthRef && previousAuthRef !== authRef) {
|
|
9288
|
+
await queueCredentialDelete(previousAuthRef);
|
|
9289
|
+
}
|
|
9290
|
+
saveRegistry(registry);
|
|
9291
|
+
try {
|
|
9292
|
+
await cancelCredentialDelete(authRef);
|
|
9293
|
+
} catch {
|
|
9294
|
+
}
|
|
9295
|
+
return updated;
|
|
9296
|
+
});
|
|
9297
|
+
}
|
|
8299
9298
|
function oauthDisplayName(registryId, fallbackName) {
|
|
8300
9299
|
if (registryId === "openai-oauth") return "OpenAI (ChatGPT)";
|
|
8301
9300
|
return fallbackName;
|
|
@@ -8307,7 +9306,7 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8307
9306
|
const registry = loadRegistryStrict();
|
|
8308
9307
|
const template = getTemplateById(templateId);
|
|
8309
9308
|
let entry = registry.providers.find((pr) => pr.id === registryId);
|
|
8310
|
-
if (entry
|
|
9309
|
+
if ((entry ? providerDefaultAuthRef(entry) : void 0) !== expectedAuthRef) {
|
|
8311
9310
|
throw new Error(`Provider "${registryId}" changed while its credential was being saved`);
|
|
8312
9311
|
}
|
|
8313
9312
|
if (!entry) {
|
|
@@ -8315,7 +9314,7 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8315
9314
|
throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
|
|
8316
9315
|
}
|
|
8317
9316
|
}
|
|
8318
|
-
const previousAuthRef = entry
|
|
9317
|
+
const previousAuthRef = entry ? providerDefaultAuthRef(entry) : void 0;
|
|
8319
9318
|
if (!entry) {
|
|
8320
9319
|
if (!template) throw new Error(`Provider "${providerId}" has no template`);
|
|
8321
9320
|
const displayName = oauthDisplayName(registryId, template.name);
|
|
@@ -8333,8 +9332,34 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8333
9332
|
},
|
|
8334
9333
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8335
9334
|
};
|
|
9335
|
+
} else if (entry.activeAuthAccount) {
|
|
9336
|
+
const selected = getOAuthAccountSlot(entry, entry.activeAuthAccount);
|
|
9337
|
+
if (!selected) {
|
|
9338
|
+
throw new Error(
|
|
9339
|
+
`Provider "${registryId}" is set to use account "${entry.activeAuthAccount}", which no longer exists`
|
|
9340
|
+
);
|
|
9341
|
+
}
|
|
9342
|
+
entry = {
|
|
9343
|
+
...entry,
|
|
9344
|
+
authType: "oauth",
|
|
9345
|
+
authRef: selected.authRef,
|
|
9346
|
+
defaultAuthRef: authRef,
|
|
9347
|
+
templateId
|
|
9348
|
+
};
|
|
9349
|
+
delete entry.defaultModelsCache;
|
|
9350
|
+
if (selected.modelsCache) {
|
|
9351
|
+
entry.modelsCache = selected.modelsCache;
|
|
9352
|
+
entry.refreshedAt = selected.modelsCache.fetchedAt;
|
|
9353
|
+
} else {
|
|
9354
|
+
delete entry.modelsCache;
|
|
9355
|
+
delete entry.refreshedAt;
|
|
9356
|
+
}
|
|
8336
9357
|
} else {
|
|
8337
9358
|
entry = { ...entry, authType: "oauth", authRef, templateId };
|
|
9359
|
+
delete entry.defaultAuthRef;
|
|
9360
|
+
delete entry.defaultModelsCache;
|
|
9361
|
+
delete entry.modelsCache;
|
|
9362
|
+
delete entry.refreshedAt;
|
|
8338
9363
|
}
|
|
8339
9364
|
const idx = registry.providers.findIndex((provider) => provider.id === registryId);
|
|
8340
9365
|
if (idx >= 0) registry.providers[idx] = entry;
|
|
@@ -8350,9 +9375,9 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8350
9375
|
return entry;
|
|
8351
9376
|
});
|
|
8352
9377
|
}
|
|
8353
|
-
async function persistNativeOAuthCredential(providerId, cred) {
|
|
9378
|
+
async function persistNativeOAuthCredential(providerId, cred, accountName) {
|
|
8354
9379
|
const registryId = toOAuthRegistryId(providerId);
|
|
8355
|
-
const account = `oauth:provider:${registryId}`;
|
|
9380
|
+
const account = accountName ? `oauth:provider:${registryId}:account:${accountName}` : `oauth:provider:${registryId}`;
|
|
8356
9381
|
const registryProvider = await withProviderMutationLock(registryId, async () => {
|
|
8357
9382
|
const existingAuthRef = await withRegistryWriteLock(
|
|
8358
9383
|
() => {
|
|
@@ -8362,7 +9387,17 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8362
9387
|
if (!existing && !getTemplateById(templateId)) {
|
|
8363
9388
|
throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
|
|
8364
9389
|
}
|
|
8365
|
-
|
|
9390
|
+
if (accountName && !existing) {
|
|
9391
|
+
throw new Error(
|
|
9392
|
+
`Provider "${registryId}" is not configured yet \u2014 run the default sign-in first: clodex providers auth openai`
|
|
9393
|
+
);
|
|
9394
|
+
}
|
|
9395
|
+
if (accountName && existing?.authType !== "oauth") {
|
|
9396
|
+
throw new Error(
|
|
9397
|
+
`Provider "${registryId}" does not currently have a default OAuth sign-in \u2014 run clodex providers auth openai without --account first.`
|
|
9398
|
+
);
|
|
9399
|
+
}
|
|
9400
|
+
return accountName ? existing ? getOAuthAccountSlot(existing, accountName)?.authRef : void 0 : existing ? providerDefaultAuthRef(existing) : void 0;
|
|
8366
9401
|
}
|
|
8367
9402
|
);
|
|
8368
9403
|
const authRef = credentialInstanceAuthRef(account);
|
|
@@ -8379,7 +9414,7 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8379
9414
|
`Could not save OAuth tokens to the credential store${diagMsg ? ` \u2014 ${diagMsg}` : " \u2014 check access and try again"}`
|
|
8380
9415
|
);
|
|
8381
9416
|
}
|
|
8382
|
-
return upsertOAuthProvider(providerId, authRef, existingAuthRef);
|
|
9417
|
+
return accountName ? upsertOAuthAccountSlot(registryId, accountName, authRef, existingAuthRef) : upsertOAuthProvider(providerId, authRef, existingAuthRef);
|
|
8383
9418
|
});
|
|
8384
9419
|
});
|
|
8385
9420
|
let credentialCleanupPending = true;
|
|
@@ -8394,11 +9429,25 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8394
9429
|
credentialCleanupPending
|
|
8395
9430
|
};
|
|
8396
9431
|
}
|
|
8397
|
-
async function authenticateProvider(providerId,
|
|
9432
|
+
async function authenticateProvider(providerId, options = {}) {
|
|
8398
9433
|
const registryId = toOAuthRegistryId(providerId);
|
|
9434
|
+
const accountName = options.account === void 0 ? void 0 : validateOAuthAccountName(options.account);
|
|
8399
9435
|
if (!supportsNativeOAuth(providerId)) {
|
|
8400
9436
|
throw new Error("OAuth sign-in is only available for openai (ChatGPT Plus/Pro).");
|
|
8401
9437
|
}
|
|
9438
|
+
if (accountName) {
|
|
9439
|
+
const existing = loadRegistryStrict().providers.find((provider) => provider.id === registryId);
|
|
9440
|
+
if (!existing) {
|
|
9441
|
+
throw new Error(
|
|
9442
|
+
`Provider "${registryId}" is not configured yet \u2014 run the default sign-in first: clodex providers auth openai`
|
|
9443
|
+
);
|
|
9444
|
+
}
|
|
9445
|
+
if (existing.authType !== "oauth") {
|
|
9446
|
+
throw new Error(
|
|
9447
|
+
`Provider "${registryId}" does not currently have a default OAuth sign-in \u2014 run clodex providers auth openai without --account first.`
|
|
9448
|
+
);
|
|
9449
|
+
}
|
|
9450
|
+
}
|
|
8402
9451
|
let storeDiagMsg = "";
|
|
8403
9452
|
const storeReady = await probeProviderCredentialStore(oauthAuthRef(registryId), (msg) => {
|
|
8404
9453
|
storeDiagMsg = msg;
|
|
@@ -8409,12 +9458,29 @@ async function authenticateProvider(providerId, _options = {}) {
|
|
|
8409
9458
|
);
|
|
8410
9459
|
}
|
|
8411
9460
|
const cred = await runNativeDeviceCode(providerId);
|
|
8412
|
-
const persisted = await persistNativeOAuthCredential(providerId, cred);
|
|
9461
|
+
const persisted = await persistNativeOAuthCredential(providerId, cred, accountName);
|
|
8413
9462
|
const refreshSpinner = p2.spinner();
|
|
8414
9463
|
refreshSpinner.start("Refreshing model list...");
|
|
8415
9464
|
try {
|
|
8416
|
-
|
|
8417
|
-
|
|
9465
|
+
const accountOverride = accountName ?? (persisted.registryProvider.activeAuthAccount === void 0 ? null : process.env[OAUTH_ACCOUNT_ENV] ?? null);
|
|
9466
|
+
const refreshResult = await refreshProviderModelsWithCredential(
|
|
9467
|
+
registryId,
|
|
9468
|
+
async (provider) => resolveProviderCredentialWithSource(
|
|
9469
|
+
provider.id,
|
|
9470
|
+
provider.authRef,
|
|
9471
|
+
void 0,
|
|
9472
|
+
{ ignoreProviderOverride: true }
|
|
9473
|
+
),
|
|
9474
|
+
accountOverride,
|
|
9475
|
+
{ ignoreProviderOverride: true }
|
|
9476
|
+
);
|
|
9477
|
+
if (refreshResult.skipped) {
|
|
9478
|
+
refreshSpinner.stop(`Models not refreshed${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
|
|
9479
|
+
} else if (refreshResult.ok) {
|
|
9480
|
+
refreshSpinner.stop("Models refreshed");
|
|
9481
|
+
} else {
|
|
9482
|
+
refreshSpinner.stop(`Could not refresh models${refreshResult.reason ? ` \u2014 ${refreshResult.reason}` : ""}`);
|
|
9483
|
+
}
|
|
8418
9484
|
} catch {
|
|
8419
9485
|
refreshSpinner.stop("Could not refresh models \u2014 run clodex providers refresh-models later");
|
|
8420
9486
|
}
|
|
@@ -8430,9 +9496,15 @@ function providerAuthHelpText() {
|
|
|
8430
9496
|
|
|
8431
9497
|
${pc3.bold("Usage:")}
|
|
8432
9498
|
clodex providers auth openai
|
|
9499
|
+
clodex providers auth openai --account work
|
|
8433
9500
|
|
|
8434
9501
|
${pc3.bold("Device code (works on SSH/VPS):")}
|
|
8435
|
-
openai ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
|
|
9502
|
+
openai ChatGPT Plus/Pro (device code at auth.openai.com/codex/device)
|
|
9503
|
+
|
|
9504
|
+
${pc3.bold("Named accounts:")}
|
|
9505
|
+
--account <name> store an additional ChatGPT account under a named slot
|
|
9506
|
+
(the default sign-in is untouched). Select one at launch:
|
|
9507
|
+
CLODEX_OAUTH_ACCOUNT=work clodex claude`;
|
|
8436
9508
|
}
|
|
8437
9509
|
|
|
8438
9510
|
// src/prompts.ts
|
|
@@ -8781,10 +9853,19 @@ function parseProvidersArgs(args) {
|
|
|
8781
9853
|
if (first === "auth") {
|
|
8782
9854
|
if (rest.length === 0) return { subcommand: "auth", showHelp: true };
|
|
8783
9855
|
let authMethod;
|
|
9856
|
+
let authAccount;
|
|
8784
9857
|
const positional = [];
|
|
8785
|
-
for (
|
|
9858
|
+
for (let i = 0; i < rest.length; i++) {
|
|
9859
|
+
const arg = rest[i];
|
|
8786
9860
|
if (arg === "--native") authMethod = "native";
|
|
8787
|
-
else if (arg
|
|
9861
|
+
else if (arg === "--account") {
|
|
9862
|
+
const value = rest[i + 1];
|
|
9863
|
+
if (!value || value.startsWith("-")) {
|
|
9864
|
+
return { subcommand: "auth", showHelp: false, error: "Usage: clodex providers auth <id> --account <name>" };
|
|
9865
|
+
}
|
|
9866
|
+
authAccount = value;
|
|
9867
|
+
i++;
|
|
9868
|
+
} else if (arg.startsWith("-")) {
|
|
8788
9869
|
return { subcommand: "auth", showHelp: false, error: `Unknown auth option: ${arg}` };
|
|
8789
9870
|
} else {
|
|
8790
9871
|
positional.push(arg);
|
|
@@ -8793,7 +9874,7 @@ function parseProvidersArgs(args) {
|
|
|
8793
9874
|
if (positional.length !== 1) {
|
|
8794
9875
|
return { subcommand: "auth", showHelp: false, error: "Usage: clodex providers auth <id>" };
|
|
8795
9876
|
}
|
|
8796
|
-
return { subcommand: "auth", showHelp: false, removeId: positional[0], authMethod };
|
|
9877
|
+
return { subcommand: "auth", showHelp: false, removeId: positional[0], authMethod, authAccount };
|
|
8797
9878
|
}
|
|
8798
9879
|
if (first === "remove") {
|
|
8799
9880
|
if (rest.length === 0) return { subcommand: "remove", showHelp: false, error: "Usage: clodex providers remove <id>" };
|
|
@@ -8826,13 +9907,133 @@ ${pc5.bold("Subcommands:")}
|
|
|
8826
9907
|
remove Remove a provider by id
|
|
8827
9908
|
refresh-models Update cached model lists`;
|
|
8828
9909
|
}
|
|
9910
|
+
function accountSwitchOutcome(providerName, saved, effective) {
|
|
9911
|
+
const savedLabel = saved ?? PROVIDER_DEFAULT_ACCOUNT_LABEL;
|
|
9912
|
+
if (effective.kind === "credential-override") {
|
|
9913
|
+
const variable = effective.credentialOverride.variable;
|
|
9914
|
+
if (effective.inactiveReason === "non-oauth") {
|
|
9915
|
+
return {
|
|
9916
|
+
ok: false,
|
|
9917
|
+
message: `Saved ${savedLabel} for ${providerName}, but this provider is not configured for OAuth account selection; ${variable} is configured and blocks launch because it has no isolated model catalog. Save that credential as a provider or unset the variable.`
|
|
9918
|
+
};
|
|
9919
|
+
}
|
|
9920
|
+
if (effective.inactiveReason === "disabled") {
|
|
9921
|
+
return {
|
|
9922
|
+
ok: false,
|
|
9923
|
+
message: `Saved ${savedLabel} for ${providerName} (provider disabled); ${variable} has no isolated model catalog, so enabling the provider in this shell will fail until that credential is saved and refreshed or the variable is unset.`
|
|
9924
|
+
};
|
|
9925
|
+
}
|
|
9926
|
+
return {
|
|
9927
|
+
ok: false,
|
|
9928
|
+
message: `Saved ${savedLabel} for ${providerName}, but ${variable} has no isolated model catalog, so launches are blocked. Save that credential as a provider or account and refresh its models, or unset the variable.`
|
|
9929
|
+
};
|
|
9930
|
+
}
|
|
9931
|
+
if (effective.inactiveReason === "non-oauth") {
|
|
9932
|
+
return {
|
|
9933
|
+
ok: true,
|
|
9934
|
+
message: `Saved ${savedLabel} for ${providerName}, but this provider is not configured for OAuth account selection.`
|
|
9935
|
+
};
|
|
9936
|
+
}
|
|
9937
|
+
if (effective.inactiveReason === "disabled") {
|
|
9938
|
+
if (effective.kind === "broken") {
|
|
9939
|
+
const blockedOverride = effective.credentialOverride ? ` ${effective.credentialOverride.variable} is configured, but OAuth account selection is validated before credential resolution.` : "";
|
|
9940
|
+
return {
|
|
9941
|
+
ok: false,
|
|
9942
|
+
message: effective.fromEnvironment ? `Saved ${savedLabel} for ${providerName} (provider disabled), but ${OAUTH_ACCOUNT_ENV}=${effective.name} names no such account \u2014 enabling it in this shell will fail until the variable is unset or corrected.${blockedOverride}` : `Saved ${savedLabel} for ${providerName} (provider disabled), but that account no longer exists \u2014 enabling the provider will fail.${blockedOverride}`
|
|
9943
|
+
};
|
|
9944
|
+
}
|
|
9945
|
+
if (effective.kind === "slot" && effective.fromEnvironment && effective.name !== saved) {
|
|
9946
|
+
return {
|
|
9947
|
+
ok: true,
|
|
9948
|
+
message: `Saved ${savedLabel} for ${providerName} (provider disabled); if enabled in this shell, ${OAUTH_ACCOUNT_ENV}=${effective.name} will override it.`
|
|
9949
|
+
};
|
|
9950
|
+
}
|
|
9951
|
+
return {
|
|
9952
|
+
ok: true,
|
|
9953
|
+
message: `Saved ${savedLabel} for ${providerName} (provider disabled).`
|
|
9954
|
+
};
|
|
9955
|
+
}
|
|
9956
|
+
if (effective.kind === "broken") {
|
|
9957
|
+
const blockedOverride = effective.credentialOverride ? ` ${effective.credentialOverride.variable} is configured, but OAuth account selection is validated before credential resolution.` : "";
|
|
9958
|
+
return {
|
|
9959
|
+
ok: false,
|
|
9960
|
+
message: effective.fromEnvironment ? `Saved ${savedLabel} for ${providerName}, but ${OAUTH_ACCOUNT_ENV}=${effective.name} names no such account \u2014 every launch fails until it is unset or corrected.${blockedOverride}` : `Saved ${savedLabel} for ${providerName}, but it names no existing account \u2014 every launch fails.${blockedOverride}`
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
if (effective.kind === "slot" && effective.fromEnvironment && effective.name !== saved) {
|
|
9964
|
+
return {
|
|
9965
|
+
ok: true,
|
|
9966
|
+
message: `Saved ${savedLabel} for ${providerName}, but ${OAUTH_ACCOUNT_ENV}=${effective.name} overrides it in this shell.`
|
|
9967
|
+
};
|
|
9968
|
+
}
|
|
9969
|
+
return {
|
|
9970
|
+
ok: true,
|
|
9971
|
+
message: `${providerName} will launch as ${savedLabel}.`,
|
|
9972
|
+
confirmsLaunch: true
|
|
9973
|
+
};
|
|
9974
|
+
}
|
|
9975
|
+
function accountSwitchServerRestartWarning(liveServerCount, selectionChanged = true) {
|
|
9976
|
+
if (!selectionChanged || !Number.isInteger(liveServerCount) || liveServerCount <= 0) return null;
|
|
9977
|
+
return `Restart ${liveServerCount} running standalone clodex server${liveServerCount === 1 ? "" : "s"} ${liveServerCount === 1 ? "because it retains" : "because they retain"} the previous provider and credential snapshot.`;
|
|
9978
|
+
}
|
|
9979
|
+
function accountSwitchHint(provider, effective) {
|
|
9980
|
+
if (effective.kind === "credential-override") {
|
|
9981
|
+
const variable = effective.credentialOverride.variable;
|
|
9982
|
+
const selected = effective.selection;
|
|
9983
|
+
const account = selected.kind === "slot" ? `account ${selected.name}` : selected.kind === "default" ? PROVIDER_DEFAULT_ACCOUNT_LABEL : `missing stored OAuth account "${selected.name}"`;
|
|
9984
|
+
const masked2 = selected.latentOrphan ? `; stored "${selected.latentOrphan}" is missing and will fail without ${OAUTH_ACCOUNT_ENV}` : "";
|
|
9985
|
+
if (effective.inactiveReason === "non-oauth") {
|
|
9986
|
+
return `${variable} is configured but launches are blocked because it has no isolated model catalog; OAuth selection (${account}) is stored but inactive because this provider is not configured for OAuth${masked2}`;
|
|
9987
|
+
}
|
|
9988
|
+
if (effective.inactiveReason === "disabled") {
|
|
9989
|
+
return `${variable} is configured for ${account} but has no isolated model catalog; enabling this provider will fail until that credential is saved and refreshed or the variable is unset${masked2}`;
|
|
9990
|
+
}
|
|
9991
|
+
return `${variable} is configured for ${account}, but launches are blocked because it has no isolated model catalog; save and refresh that credential or unset the variable${masked2}`;
|
|
9992
|
+
}
|
|
9993
|
+
if (effective.inactiveReason === "non-oauth") {
|
|
9994
|
+
if (effective.kind === "broken") {
|
|
9995
|
+
return `Stored OAuth account "${effective.name}" no longer exists \u2014 provider is not configured for OAuth selection`;
|
|
9996
|
+
}
|
|
9997
|
+
if (effective.kind === "slot") {
|
|
9998
|
+
return `Stored OAuth account: ${effective.name} (provider is not configured for OAuth selection)`;
|
|
9999
|
+
}
|
|
10000
|
+
return "OAuth account selection inactive (provider is not configured for OAuth)";
|
|
10001
|
+
}
|
|
10002
|
+
if (effective.inactiveReason === "disabled") {
|
|
10003
|
+
const masked2 = effective.latentOrphan ? `; stored "${effective.latentOrphan}" is missing and will fail if enabled without the override` : "";
|
|
10004
|
+
if (effective.kind === "broken") {
|
|
10005
|
+
const blockedOverride = effective.credentialOverride ? `; ${effective.credentialOverride.variable} cannot bypass account selection` : "";
|
|
10006
|
+
return effective.fromEnvironment ? `${OAUTH_ACCOUNT_ENV}=${effective.name} names no such account \u2014 enabling this provider will fail${masked2}${blockedOverride}` : `Selected account "${effective.name}" no longer exists \u2014 enabling this provider will fail${blockedOverride}`;
|
|
10007
|
+
}
|
|
10008
|
+
if (effective.kind === "slot") {
|
|
10009
|
+
return effective.fromEnvironment ? `If enabled, ${OAUTH_ACCOUNT_ENV}=${effective.name} overrides the stored ${provider.activeAuthAccount ?? PROVIDER_DEFAULT_ACCOUNT_LABEL}${masked2}` : `Saved account: ${effective.name} (provider disabled)`;
|
|
10010
|
+
}
|
|
10011
|
+
return `Saved account: ${PROVIDER_DEFAULT_ACCOUNT_LABEL} (provider disabled)`;
|
|
10012
|
+
}
|
|
10013
|
+
if (effective.kind === "broken") {
|
|
10014
|
+
const also = effective.latentOrphan ? ` (and stored "${effective.latentOrphan}" is missing too)` : "";
|
|
10015
|
+
const blockedOverride = effective.credentialOverride ? `; ${effective.credentialOverride.variable} cannot bypass account selection` : "";
|
|
10016
|
+
return effective.fromEnvironment ? `${OAUTH_ACCOUNT_ENV}=${effective.name} names no such account \u2014 every launch fails${also}` + blockedOverride : `Selected account "${effective.name}" no longer exists \u2014 every launch fails; clear it here${blockedOverride}`;
|
|
10017
|
+
}
|
|
10018
|
+
const masked = effective.latentOrphan ? ` \u2014 stored "${effective.latentOrphan}" no longer exists and will fail without it` : "";
|
|
10019
|
+
if (effective.kind === "default") {
|
|
10020
|
+
return `Every launch currently uses ${PROVIDER_DEFAULT_ACCOUNT_LABEL}${masked}`;
|
|
10021
|
+
}
|
|
10022
|
+
return effective.fromEnvironment ? `${OAUTH_ACCOUNT_ENV}=${effective.name} overrides the stored ${provider.activeAuthAccount ?? PROVIDER_DEFAULT_ACCOUNT_LABEL}${masked}` : `Every launch currently uses ${effective.name}${masked}`;
|
|
10023
|
+
}
|
|
10024
|
+
function shouldOfferAccountSwitch(provider) {
|
|
10025
|
+
return Object.keys(provider.authAccounts ?? {}).length > 0 || provider.activeAuthAccount !== void 0;
|
|
10026
|
+
}
|
|
8829
10027
|
function providerLabel(name, modelCount, enabled) {
|
|
8830
10028
|
return `${fmtEnabledStar(enabled)} ${fmtProvider(name)} ${pc5.dim(`(${modelCount} model${modelCount === 1 ? "" : "s"})`)}`;
|
|
8831
10029
|
}
|
|
8832
|
-
async function runProvidersAuthWithCleanupState(providerId, method, cleanupState) {
|
|
10030
|
+
async function runProvidersAuthWithCleanupState(providerId, method, cleanupState, account) {
|
|
8833
10031
|
try {
|
|
8834
|
-
const result = await authenticateProvider(providerId, { method });
|
|
8835
|
-
|
|
10032
|
+
const result = await authenticateProvider(providerId, { method, account });
|
|
10033
|
+
const slot = account === void 0 ? void 0 : validateOAuthAccountName(account);
|
|
10034
|
+
p4.log.success(
|
|
10035
|
+
slot ? `Signed in to ${result.registryProvider.name} (account "${slot}") \u2014 make it the account every launch uses with: clodex providers` : `Signed in to ${result.registryProvider.name} \u2014 credential saved to the credential store.`
|
|
10036
|
+
);
|
|
8836
10037
|
reportCredentialCleanup(result.credentialCleanupPending, cleanupState, true);
|
|
8837
10038
|
return 0;
|
|
8838
10039
|
} catch (err) {
|
|
@@ -8847,8 +10048,8 @@ async function runProvidersAuthWithCleanupState(providerId, method, cleanupState
|
|
|
8847
10048
|
async function runProvidersAuth(providerId, method) {
|
|
8848
10049
|
return runProvidersAuthWithCleanupState(providerId, method);
|
|
8849
10050
|
}
|
|
8850
|
-
async function runProvidersRefreshModels(providerId) {
|
|
8851
|
-
const resolveKey = async (provider) =>
|
|
10051
|
+
async function runProvidersRefreshModels(providerId, options = {}) {
|
|
10052
|
+
const resolveKey = async (provider) => resolveProviderCredentialWithSource(provider.id, provider.authRef);
|
|
8852
10053
|
if (providerId) {
|
|
8853
10054
|
const registry = loadRegistry();
|
|
8854
10055
|
const provider = registry.providers.find((p13) => p13.id === providerId);
|
|
@@ -8858,11 +10059,25 @@ async function runProvidersRefreshModels(providerId) {
|
|
|
8858
10059
|
}
|
|
8859
10060
|
const spinner6 = p4.spinner();
|
|
8860
10061
|
spinner6.start(`Refreshing ${provider.name}...`);
|
|
8861
|
-
const
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
10062
|
+
const accountOverride = options.accountOverride === void 0 ? process.env[OAUTH_ACCOUNT_ENV] ?? null : options.accountOverride;
|
|
10063
|
+
let result;
|
|
10064
|
+
try {
|
|
10065
|
+
result = await refreshProviderModelsWithCredential(
|
|
10066
|
+
providerId,
|
|
10067
|
+
async (candidate) => resolveProviderCredentialWithSource(
|
|
10068
|
+
candidate.id,
|
|
10069
|
+
candidate.authRef,
|
|
10070
|
+
void 0,
|
|
10071
|
+
{ ignoreProviderOverride: options.ignoreProviderCredentialOverride }
|
|
10072
|
+
),
|
|
10073
|
+
accountOverride,
|
|
10074
|
+
{ ignoreProviderOverride: options.ignoreProviderCredentialOverride }
|
|
10075
|
+
);
|
|
10076
|
+
} catch (err) {
|
|
10077
|
+
spinner6.stop("");
|
|
10078
|
+
p4.log.error(err instanceof Error ? err.message : String(err));
|
|
10079
|
+
return 1;
|
|
10080
|
+
}
|
|
8866
10081
|
spinner6.stop("");
|
|
8867
10082
|
if (result.skipped) {
|
|
8868
10083
|
const countNote = result.modelCount ? ` (${result.modelCount} cached models kept)` : "";
|
|
@@ -9036,8 +10251,22 @@ async function runProviderDetail(id) {
|
|
|
9036
10251
|
const registry = loadRegistry();
|
|
9037
10252
|
const provider = registry.providers.find((pr) => pr.id === id);
|
|
9038
10253
|
if (!provider) return "back";
|
|
9039
|
-
const
|
|
9040
|
-
|
|
10254
|
+
const effective = resolveActiveAccount(provider);
|
|
10255
|
+
let modelProvider;
|
|
10256
|
+
try {
|
|
10257
|
+
modelProvider = projectSelectedOAuthAccount(provider);
|
|
10258
|
+
if (effective.kind === "credential-override") {
|
|
10259
|
+
modelProvider = { ...modelProvider };
|
|
10260
|
+
delete modelProvider.modelsCache;
|
|
10261
|
+
delete modelProvider.refreshedAt;
|
|
10262
|
+
}
|
|
10263
|
+
} catch {
|
|
10264
|
+
modelProvider = { ...provider };
|
|
10265
|
+
delete modelProvider.modelsCache;
|
|
10266
|
+
delete modelProvider.refreshedAt;
|
|
10267
|
+
}
|
|
10268
|
+
const modelCount = modelProvider.modelsCache?.models.length ?? 0;
|
|
10269
|
+
const authLabel = (await resolveProvidersForDisplay()).find((entry) => entry.id === id)?.authLabel ?? formatRegistryAuthLabel(provider);
|
|
9041
10270
|
printProviderDetailPanel(provider.name, modelCount, authLabel);
|
|
9042
10271
|
const detailOptions = [];
|
|
9043
10272
|
if (modelCount > 0) {
|
|
@@ -9052,11 +10281,29 @@ async function runProviderDetail(id) {
|
|
|
9052
10281
|
label: "Refresh model list",
|
|
9053
10282
|
hint: "Fetch latest models from the provider API"
|
|
9054
10283
|
});
|
|
10284
|
+
const accountSlots = Object.keys(provider.authAccounts ?? {}).sort();
|
|
9055
10285
|
if (supportsNativeOAuth(id) || provider.authType === "oauth") {
|
|
9056
10286
|
detailOptions.push({
|
|
9057
10287
|
value: "auth",
|
|
9058
10288
|
label: "Sign in again (OAuth)",
|
|
9059
|
-
|
|
10289
|
+
// Says what the action DOES. It calls the auth flow with no account
|
|
10290
|
+
// name, so it re-authenticates the provider's own credential and cannot
|
|
10291
|
+
// create or refresh a named slot — the previous wording advertised
|
|
10292
|
+
// exactly the thing it does not do, which is worst when the account
|
|
10293
|
+
// needing reauthentication is a named one that this would leave broken
|
|
10294
|
+
// while overwriting the default.
|
|
10295
|
+
hint: accountSlots.length > 0 ? `Re-authenticate ${PROVIDER_DEFAULT_ACCOUNT_LABEL} only \u2014 for a named account: clodex providers auth ${id} --account <name>` : `Re-authenticate ${PROVIDER_DEFAULT_ACCOUNT_LABEL}`
|
|
10296
|
+
});
|
|
10297
|
+
}
|
|
10298
|
+
if (shouldOfferAccountSwitch(provider)) {
|
|
10299
|
+
detailOptions.push({
|
|
10300
|
+
value: "account",
|
|
10301
|
+
label: "Switch account",
|
|
10302
|
+
// Same resolver the list view uses, so this screen cannot contradict it
|
|
10303
|
+
// about which identity is live — including when the answer is "none of
|
|
10304
|
+
// them, the launch fails", which this hint previously reported as a
|
|
10305
|
+
// working account.
|
|
10306
|
+
hint: accountSwitchHint(provider, effective)
|
|
9060
10307
|
});
|
|
9061
10308
|
}
|
|
9062
10309
|
detailOptions.push(
|
|
@@ -9074,8 +10321,8 @@ async function runProviderDetail(id) {
|
|
|
9074
10321
|
});
|
|
9075
10322
|
if (p4.isCancel(action) || action === "back") return "back";
|
|
9076
10323
|
if (action === "browse") {
|
|
9077
|
-
const cachedModels =
|
|
9078
|
-
const localModels = cachedModels.map((m) => cachedModelToLocal(m,
|
|
10324
|
+
const cachedModels = modelProvider.modelsCache?.models ?? [];
|
|
10325
|
+
const localModels = cachedModels.map((m) => cachedModelToLocal(m, modelProvider)).filter((m) => m !== null);
|
|
9079
10326
|
const localProvider = {
|
|
9080
10327
|
id: provider.id,
|
|
9081
10328
|
name: provider.name,
|
|
@@ -9093,6 +10340,71 @@ async function runProviderDetail(id) {
|
|
|
9093
10340
|
await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, void 0, state));
|
|
9094
10341
|
return "back";
|
|
9095
10342
|
}
|
|
10343
|
+
if (action === "account") {
|
|
10344
|
+
const providerDefault = "<default>";
|
|
10345
|
+
const stored = provider.activeAuthAccount;
|
|
10346
|
+
const current = stored !== void 0 && accountSlots.includes(stored) ? stored : providerDefault;
|
|
10347
|
+
const chosen = await p4.select({
|
|
10348
|
+
message: "Which account should every launch use?",
|
|
10349
|
+
initialValue: current,
|
|
10350
|
+
options: [
|
|
10351
|
+
{
|
|
10352
|
+
value: providerDefault,
|
|
10353
|
+
label: PROVIDER_DEFAULT_ACCOUNT_LABEL,
|
|
10354
|
+
hint: "the provider's original sign-in"
|
|
10355
|
+
},
|
|
10356
|
+
...accountSlots.map((name) => ({
|
|
10357
|
+
value: name,
|
|
10358
|
+
label: name,
|
|
10359
|
+
hint: effective.kind === "slot" && effective.name === name ? effective.fromEnvironment ? `active via ${OAUTH_ACCOUNT_ENV}` : "current" : name === provider.activeAuthAccount ? "stored" : ""
|
|
10360
|
+
}))
|
|
10361
|
+
]
|
|
10362
|
+
});
|
|
10363
|
+
if (p4.isCancel(chosen)) return "back";
|
|
10364
|
+
return withProviderMutationLock(id, async () => {
|
|
10365
|
+
const result = await setActiveOAuthAccount(id, chosen === providerDefault ? void 0 : chosen);
|
|
10366
|
+
if (!result.updated) {
|
|
10367
|
+
p4.log.error(result.error ?? "Could not switch account.");
|
|
10368
|
+
return "back";
|
|
10369
|
+
}
|
|
10370
|
+
if (!result.provider) {
|
|
10371
|
+
p4.log.error("Account selection was saved, but the resulting provider state could not be read.");
|
|
10372
|
+
return "back";
|
|
10373
|
+
}
|
|
10374
|
+
const refreshExitCode = await runProvidersRefreshModels(id, {
|
|
10375
|
+
accountOverride: null,
|
|
10376
|
+
ignoreProviderCredentialOverride: true
|
|
10377
|
+
});
|
|
10378
|
+
const currentProvider = loadRegistry().providers.find((candidate) => candidate.id === id);
|
|
10379
|
+
if (!currentProvider) {
|
|
10380
|
+
p4.log.error("Account selection was saved, but the resulting provider state could not be read.");
|
|
10381
|
+
return "back";
|
|
10382
|
+
}
|
|
10383
|
+
const outcome = accountSwitchOutcome(
|
|
10384
|
+
currentProvider.name,
|
|
10385
|
+
result.account,
|
|
10386
|
+
resolveActiveAccount(currentProvider)
|
|
10387
|
+
);
|
|
10388
|
+
const selectedCatalogReady = Boolean(currentProvider.modelsCache?.models.length);
|
|
10389
|
+
if (outcome.ok && currentProvider.enabled && currentProvider.authType === "oauth" && (refreshExitCode !== 0 || !selectedCatalogReady)) {
|
|
10390
|
+
const savedLabel = result.account ?? PROVIDER_DEFAULT_ACCOUNT_LABEL;
|
|
10391
|
+
const savedContext = outcome.confirmsLaunch ? `Saved ${savedLabel} for ${currentProvider.name}.` : outcome.message;
|
|
10392
|
+
p4.log.warn(
|
|
10393
|
+
`${savedContext} Automatic model refresh for the saved selection did not produce a usable catalog; choose Switch account again to retry it before relying on that selection for launches.`
|
|
10394
|
+
);
|
|
10395
|
+
} else if (outcome.ok) {
|
|
10396
|
+
p4.log.success(outcome.message);
|
|
10397
|
+
} else {
|
|
10398
|
+
p4.log.warn(outcome.message);
|
|
10399
|
+
}
|
|
10400
|
+
const restartWarning = accountSwitchServerRestartWarning(
|
|
10401
|
+
readLiveServerRuntimeStates().length,
|
|
10402
|
+
result.changed
|
|
10403
|
+
);
|
|
10404
|
+
if (restartWarning) p4.log.warn(restartWarning);
|
|
10405
|
+
return "back";
|
|
10406
|
+
});
|
|
10407
|
+
}
|
|
9096
10408
|
if (action === "toggle") {
|
|
9097
10409
|
const result = toggleProviderEnabled(id);
|
|
9098
10410
|
if (result.toggled) {
|
|
@@ -9121,6 +10433,12 @@ async function runProvidersHub() {
|
|
|
9121
10433
|
const configuredIds = new Set(entries.map((entry) => entry.id));
|
|
9122
10434
|
if (listVisibleOAuthTemplates(configuredIds).length > 0) {
|
|
9123
10435
|
options.push({ value: "auth-menu", label: "\u2192 Sign in with ChatGPT (OAuth)", hint: "device code" });
|
|
10436
|
+
} else if (configuredIds.has("openai-oauth")) {
|
|
10437
|
+
options.push({
|
|
10438
|
+
value: "auth-account",
|
|
10439
|
+
label: "\u2192 Add another ChatGPT account",
|
|
10440
|
+
hint: "named slot; pick which one launches via Switch account"
|
|
10441
|
+
});
|
|
9124
10442
|
}
|
|
9125
10443
|
if (entries.length > 0) {
|
|
9126
10444
|
options.push({ value: "refresh-all", label: "\u21BA Refresh all models", hint: "Update model lists for all providers" });
|
|
@@ -9145,6 +10463,23 @@ async function runProvidersHub() {
|
|
|
9145
10463
|
await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state));
|
|
9146
10464
|
continue;
|
|
9147
10465
|
}
|
|
10466
|
+
if (choice === "auth-account") {
|
|
10467
|
+
const name = await p4.text({
|
|
10468
|
+
message: "Name for this account (choose which one launches with: clodex providers)",
|
|
10469
|
+
placeholder: "work",
|
|
10470
|
+
validate: (value) => {
|
|
10471
|
+
try {
|
|
10472
|
+
validateOAuthAccountName(String(value ?? ""));
|
|
10473
|
+
return void 0;
|
|
10474
|
+
} catch (err) {
|
|
10475
|
+
return err instanceof Error ? err.message : String(err);
|
|
10476
|
+
}
|
|
10477
|
+
}
|
|
10478
|
+
});
|
|
10479
|
+
if (p4.isCancel(name)) continue;
|
|
10480
|
+
await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state, String(name)));
|
|
10481
|
+
continue;
|
|
10482
|
+
}
|
|
9148
10483
|
if (typeof choice === "string" && choice.startsWith("provider:")) {
|
|
9149
10484
|
const id = choice.slice("provider:".length);
|
|
9150
10485
|
const outcome = await runProviderDetail(id);
|
|
@@ -9179,7 +10514,7 @@ async function runProvidersCommand(args) {
|
|
|
9179
10514
|
console.log(providerAuthHelpText());
|
|
9180
10515
|
return 0;
|
|
9181
10516
|
}
|
|
9182
|
-
return runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(parsed.removeId, parsed.authMethod, state));
|
|
10517
|
+
return runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(parsed.removeId, parsed.authMethod, state, parsed.authAccount));
|
|
9183
10518
|
}
|
|
9184
10519
|
relayIntro("Your OpenAI providers");
|
|
9185
10520
|
return runProvidersHub();
|
|
@@ -9220,7 +10555,7 @@ async function runFirstRunWizard(_trace = false) {
|
|
|
9220
10555
|
|
|
9221
10556
|
// src/proxy.ts
|
|
9222
10557
|
import { createServer } from "http";
|
|
9223
|
-
import { appendFileSync, openSync as openSync3, writeSync, closeSync as closeSync3 } from "fs";
|
|
10558
|
+
import { appendFileSync as appendFileSync2, openSync as openSync3, writeSync as writeSync2, closeSync as closeSync3 } from "fs";
|
|
9224
10559
|
|
|
9225
10560
|
// src/http-utils.ts
|
|
9226
10561
|
import * as zlib from "zlib";
|
|
@@ -9462,7 +10797,8 @@ function localModelToRoute(lp, model) {
|
|
|
9462
10797
|
reasoning: model.reasoning,
|
|
9463
10798
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
9464
10799
|
useResponsesLite: model.useResponsesLite,
|
|
9465
|
-
preferWebSockets: model.preferWebSockets
|
|
10800
|
+
preferWebSockets: model.preferWebSockets,
|
|
10801
|
+
compatibility: model.compatibility
|
|
9466
10802
|
};
|
|
9467
10803
|
}
|
|
9468
10804
|
function makeRouteResolver(localProviders) {
|
|
@@ -9535,7 +10871,8 @@ function buildHttpProxyRoutes(providers, favorites, modelAliases = void 0, max =
|
|
|
9535
10871
|
unavailable.push(favorite);
|
|
9536
10872
|
continue;
|
|
9537
10873
|
}
|
|
9538
|
-
|
|
10874
|
+
const supported = model.modelFormat === "anthropic" ? Boolean(model.baseUrl) : isSdkMigratedNpm(model.npm);
|
|
10875
|
+
if (!supported) {
|
|
9539
10876
|
unsupported.push(favorite);
|
|
9540
10877
|
continue;
|
|
9541
10878
|
}
|
|
@@ -9708,7 +11045,8 @@ function routeUnavailableMessage(modelId, reason) {
|
|
|
9708
11045
|
}
|
|
9709
11046
|
|
|
9710
11047
|
// src/upstream-forward.ts
|
|
9711
|
-
import { Readable } from "stream";
|
|
11048
|
+
import { Readable, Transform } from "stream";
|
|
11049
|
+
import { StringDecoder } from "string_decoder";
|
|
9712
11050
|
|
|
9713
11051
|
// src/server/auth.ts
|
|
9714
11052
|
function sanitizeCredential(value) {
|
|
@@ -9789,6 +11127,34 @@ async function fetchWithOAuthRetry(apiKey, request3, refreshToken) {
|
|
|
9789
11127
|
response = await request3(refreshed);
|
|
9790
11128
|
return { response, apiKey: refreshed, refreshed: true };
|
|
9791
11129
|
}
|
|
11130
|
+
function anthropicSseModelRewrite(override) {
|
|
11131
|
+
const decoder = new StringDecoder("utf8");
|
|
11132
|
+
let tail = "";
|
|
11133
|
+
const rewriteLine = (line) => {
|
|
11134
|
+
if (!line.startsWith("data:") || !line.includes('"message_start"')) return line;
|
|
11135
|
+
try {
|
|
11136
|
+
const parsed = JSON.parse(line.slice(5));
|
|
11137
|
+
if (parsed.type === "message_start" && parsed.message && typeof parsed.message.model === "string") {
|
|
11138
|
+
parsed.message.model = override;
|
|
11139
|
+
return "data: " + JSON.stringify(parsed);
|
|
11140
|
+
}
|
|
11141
|
+
} catch {
|
|
11142
|
+
}
|
|
11143
|
+
return line;
|
|
11144
|
+
};
|
|
11145
|
+
return new Transform({
|
|
11146
|
+
transform(chunk, _encoding, callback) {
|
|
11147
|
+
const lines = (tail + decoder.write(chunk)).split("\n");
|
|
11148
|
+
tail = lines.pop() ?? "";
|
|
11149
|
+
const rewritten = lines.map(rewriteLine);
|
|
11150
|
+
callback(null, rewritten.length ? rewritten.join("\n") + "\n" : "");
|
|
11151
|
+
},
|
|
11152
|
+
flush(callback) {
|
|
11153
|
+
const rest = tail + decoder.end();
|
|
11154
|
+
callback(null, rest ? rewriteLine(rest) : "");
|
|
11155
|
+
}
|
|
11156
|
+
});
|
|
11157
|
+
}
|
|
9792
11158
|
async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWantsStream, options = {}) {
|
|
9793
11159
|
const doFetch = (key) => fetch(messagesUrl, {
|
|
9794
11160
|
method: "POST",
|
|
@@ -9825,7 +11191,12 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
9825
11191
|
"Cache-Control": "no-cache",
|
|
9826
11192
|
"Connection": "keep-alive"
|
|
9827
11193
|
});
|
|
9828
|
-
Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy())
|
|
11194
|
+
const upstream = Readable.fromWeb(upstreamRes.body).on("error", () => res.destroy());
|
|
11195
|
+
if (options.responseModelOverride) {
|
|
11196
|
+
upstream.pipe(anthropicSseModelRewrite(options.responseModelOverride)).on("error", () => res.destroy()).pipe(res);
|
|
11197
|
+
} else {
|
|
11198
|
+
upstream.pipe(res);
|
|
11199
|
+
}
|
|
9829
11200
|
return;
|
|
9830
11201
|
}
|
|
9831
11202
|
if (!upstreamRes.body) {
|
|
@@ -9833,19 +11204,24 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
9833
11204
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
|
|
9834
11205
|
return;
|
|
9835
11206
|
}
|
|
9836
|
-
|
|
11207
|
+
let text5 = await upstreamRes.text();
|
|
11208
|
+
let parsed;
|
|
9837
11209
|
try {
|
|
9838
|
-
JSON.parse(
|
|
11210
|
+
parsed = JSON.parse(text5);
|
|
9839
11211
|
} catch {
|
|
9840
11212
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
9841
11213
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
|
|
9842
11214
|
return;
|
|
9843
11215
|
}
|
|
11216
|
+
if (options.responseModelOverride && parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.model === "string") {
|
|
11217
|
+
parsed.model = options.responseModelOverride;
|
|
11218
|
+
text5 = JSON.stringify(parsed);
|
|
11219
|
+
}
|
|
9844
11220
|
res.writeHead(200, {
|
|
9845
11221
|
"Content-Type": "application/json",
|
|
9846
|
-
"Content-Length": Buffer.byteLength(
|
|
11222
|
+
"Content-Length": Buffer.byteLength(text5).toString()
|
|
9847
11223
|
});
|
|
9848
|
-
res.end(
|
|
11224
|
+
res.end(text5);
|
|
9849
11225
|
}
|
|
9850
11226
|
|
|
9851
11227
|
// src/proxy.ts
|
|
@@ -10001,7 +11377,7 @@ function reportOnce(raw, message, warn) {
|
|
|
10001
11377
|
} catch {
|
|
10002
11378
|
}
|
|
10003
11379
|
}
|
|
10004
|
-
function upstreamMaxRetries(env = process.env, warn = (message) =>
|
|
11380
|
+
function upstreamMaxRetries(env = process.env, warn = (message) => emitParentNotice(`clodex: ${message}`)) {
|
|
10005
11381
|
const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
|
|
10006
11382
|
if (raw === void 0 || raw === "") return void 0;
|
|
10007
11383
|
const value = Number(raw);
|
|
@@ -10070,10 +11446,10 @@ function supportsOpenAiPromptCacheBreakpoints(modelId) {
|
|
|
10070
11446
|
const minor = Number(match[2] ?? 0);
|
|
10071
11447
|
return major > 5 || major === 5 && minor >= 6;
|
|
10072
11448
|
}
|
|
10073
|
-
function stripClaudeCodeBillingHeader(
|
|
10074
|
-
if (!
|
|
10075
|
-
const newline =
|
|
10076
|
-
return newline === -1 ? void 0 :
|
|
11449
|
+
function stripClaudeCodeBillingHeader(text5) {
|
|
11450
|
+
if (!text5.startsWith(CLAUDE_CODE_BILLING_HEADER_PREFIX)) return text5;
|
|
11451
|
+
const newline = text5.indexOf("\n");
|
|
11452
|
+
return newline === -1 ? void 0 : text5.slice(newline + 1);
|
|
10077
11453
|
}
|
|
10078
11454
|
function systemToString(system, stripAnthropicBillingHeader = false) {
|
|
10079
11455
|
if (!system) return void 0;
|
|
@@ -10082,8 +11458,8 @@ function systemToString(system, stripAnthropicBillingHeader = false) {
|
|
|
10082
11458
|
}
|
|
10083
11459
|
const blocks = system.map((b) => typeof b === "string" ? b : b.text ?? "");
|
|
10084
11460
|
if (!stripAnthropicBillingHeader) return blocks.join("\n");
|
|
10085
|
-
return blocks.flatMap((
|
|
10086
|
-
const stripped = stripClaudeCodeBillingHeader(
|
|
11461
|
+
return blocks.flatMap((text5) => {
|
|
11462
|
+
const stripped = stripClaudeCodeBillingHeader(text5);
|
|
10087
11463
|
return stripped === void 0 ? [] : [stripped];
|
|
10088
11464
|
}).join("\n");
|
|
10089
11465
|
}
|
|
@@ -10099,12 +11475,12 @@ function translateTopLevelSystemForOpenAi(system) {
|
|
|
10099
11475
|
}
|
|
10100
11476
|
return system.flatMap((block) => {
|
|
10101
11477
|
const raw = typeof block === "string" ? block : block.text ?? "";
|
|
10102
|
-
const
|
|
10103
|
-
if (!
|
|
11478
|
+
const text5 = stripClaudeCodeBillingHeader(raw) ?? "";
|
|
11479
|
+
if (!text5.trim()) return [];
|
|
10104
11480
|
const cacheControl = typeof block === "string" ? void 0 : block.cache_control;
|
|
10105
11481
|
return [{
|
|
10106
11482
|
role: "system",
|
|
10107
|
-
content:
|
|
11483
|
+
content: text5,
|
|
10108
11484
|
...cacheControl ? { providerOptions: { openai: { promptCacheBreakpoint: { mode: "explicit" } } } } : {}
|
|
10109
11485
|
}];
|
|
10110
11486
|
});
|
|
@@ -10161,9 +11537,9 @@ function annotateToolNames(messages) {
|
|
|
10161
11537
|
}
|
|
10162
11538
|
}
|
|
10163
11539
|
function thinkingToSdkPart(block, npm) {
|
|
10164
|
-
const
|
|
10165
|
-
if (npm === "@ai-sdk/openai" && !block.signature && !
|
|
10166
|
-
const part = { type: "reasoning", text:
|
|
11540
|
+
const text5 = block.thinking ?? "";
|
|
11541
|
+
if (npm === "@ai-sdk/openai" && !block.signature && !text5.trim()) return null;
|
|
11542
|
+
const part = { type: "reasoning", text: text5 };
|
|
10167
11543
|
if (block.signature) {
|
|
10168
11544
|
if (npm === "@ai-sdk/google") {
|
|
10169
11545
|
part.providerOptions = { google: { thoughtSignature: block.signature } };
|
|
@@ -10279,8 +11655,8 @@ function isClaudeCodeStructuredOutputCompactRequest(body) {
|
|
|
10279
11655
|
if (!body.tools?.some((candidate) => candidate.name === "StructuredOutput")) return false;
|
|
10280
11656
|
const finalMessage = body.messages.at(-1);
|
|
10281
11657
|
if (!finalMessage || finalMessage.role !== "user") return false;
|
|
10282
|
-
const
|
|
10283
|
-
return
|
|
11658
|
+
const text5 = typeof finalMessage.content === "string" ? finalMessage.content : finalMessage.content.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n");
|
|
11659
|
+
return text5.includes(COMPACT_TEXT_ONLY_START) && text5.includes(COMPACT_TEXT_ONLY_END);
|
|
10284
11660
|
}
|
|
10285
11661
|
function translateRequest(body, npm, options) {
|
|
10286
11662
|
const messages = body.messages ?? [];
|
|
@@ -10309,10 +11685,12 @@ function translateRequest(body, npm, options) {
|
|
|
10309
11685
|
const supportsExplicitOpenAiCaching = !options?.openAiOAuth && supportsOpenAiPromptCacheBreakpoints(upstreamModelId2);
|
|
10310
11686
|
if (npm === "@ai-sdk/openai") {
|
|
10311
11687
|
const claudeSessionId = extractClaudeSessionId(body, options?.claudeSessionId);
|
|
11688
|
+
const serviceTier = options?.openAiOAuth ? oauthServiceTier() : void 0;
|
|
10312
11689
|
providerOptions = deepMergeProviderOptions(providerOptions, {
|
|
10313
11690
|
openai: {
|
|
10314
11691
|
promptCacheKey: claudeSessionId ? claudeSessionPromptCacheKey(claudeSessionId) : openAiPromptCacheKey(baseSystem, upstreamTools),
|
|
10315
|
-
...supportsExplicitOpenAiCaching ? { promptCacheOptions: { mode: "implicit", ttl: "30m" } } : {}
|
|
11692
|
+
...supportsExplicitOpenAiCaching ? { promptCacheOptions: { mode: "implicit", ttl: "30m" } } : {},
|
|
11693
|
+
...serviceTier ? { serviceTier } : {}
|
|
10316
11694
|
}
|
|
10317
11695
|
});
|
|
10318
11696
|
}
|
|
@@ -10330,6 +11708,35 @@ function translateRequest(body, npm, options) {
|
|
|
10330
11708
|
providerOptions
|
|
10331
11709
|
};
|
|
10332
11710
|
}
|
|
11711
|
+
function isOpenAiOAuthRoute(route) {
|
|
11712
|
+
return route?.npm === "@ai-sdk/openai" && route.authType === "oauth";
|
|
11713
|
+
}
|
|
11714
|
+
var SERVICE_TIERS = /* @__PURE__ */ new Set(["auto", "default", "flex", "priority"]);
|
|
11715
|
+
var warnedInvalidServiceTier = false;
|
|
11716
|
+
var warnedUnsupportedServiceTier = false;
|
|
11717
|
+
function oauthServiceTier() {
|
|
11718
|
+
const raw = process.env.CLODEX_SERVICE_TIER;
|
|
11719
|
+
if (raw === void 0 || raw.trim() === "") return void 0;
|
|
11720
|
+
const normalized = raw.trim().toLowerCase() === "fast" ? "priority" : raw.trim().toLowerCase();
|
|
11721
|
+
if (!SERVICE_TIERS.has(normalized)) {
|
|
11722
|
+
if (!warnedInvalidServiceTier) {
|
|
11723
|
+
warnedInvalidServiceTier = true;
|
|
11724
|
+
emitParentNotice("clodex: ignoring CLODEX_SERVICE_TIER (expected auto, default, flex, priority, or fast)");
|
|
11725
|
+
}
|
|
11726
|
+
return void 0;
|
|
11727
|
+
}
|
|
11728
|
+
return normalized;
|
|
11729
|
+
}
|
|
11730
|
+
function reportUnsupportedServiceTier(params, warnings) {
|
|
11731
|
+
if (warnedUnsupportedServiceTier || !params.providerOptions?.openai?.serviceTier) return;
|
|
11732
|
+
if (!Array.isArray(warnings) || !warnings.some((warning) => {
|
|
11733
|
+
if (!warning || typeof warning !== "object") return false;
|
|
11734
|
+
const candidate = warning;
|
|
11735
|
+
return candidate.type === "unsupported" && candidate.feature === "serviceTier";
|
|
11736
|
+
})) return;
|
|
11737
|
+
warnedUnsupportedServiceTier = true;
|
|
11738
|
+
emitParentNotice("clodex: requested service tier was not sent for this model; the backend default will be used");
|
|
11739
|
+
}
|
|
10333
11740
|
function toAnthropicUsage(u) {
|
|
10334
11741
|
const total = u?.inputTokens ?? 0;
|
|
10335
11742
|
const cacheRead = u?.inputTokenDetails?.cacheReadTokens ?? u?.cachedInputTokens ?? 0;
|
|
@@ -10579,7 +11986,8 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10579
11986
|
maxRetries: upstreamMaxRetries(),
|
|
10580
11987
|
abortSignal,
|
|
10581
11988
|
onError: () => {
|
|
10582
|
-
}
|
|
11989
|
+
},
|
|
11990
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
10583
11991
|
});
|
|
10584
11992
|
const watchedStream = (async function* () {
|
|
10585
11993
|
try {
|
|
@@ -10605,10 +12013,11 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10605
12013
|
}
|
|
10606
12014
|
}
|
|
10607
12015
|
async function generateAnthropicResponse(model, params, modelId, options) {
|
|
10608
|
-
let
|
|
12016
|
+
let text5;
|
|
10609
12017
|
let toolCalls;
|
|
10610
12018
|
let finishReason;
|
|
10611
12019
|
let usage;
|
|
12020
|
+
let warnings;
|
|
10612
12021
|
if (options?.forceStream) {
|
|
10613
12022
|
const forceAbort = new AbortController();
|
|
10614
12023
|
const stopForwardingAbort = forwardAbortSignal(options.abortSignal, forceAbort);
|
|
@@ -10628,7 +12037,8 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10628
12037
|
maxRetries: upstreamMaxRetries(),
|
|
10629
12038
|
abortSignal,
|
|
10630
12039
|
onError: () => {
|
|
10631
|
-
}
|
|
12040
|
+
},
|
|
12041
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
10632
12042
|
});
|
|
10633
12043
|
const streamedText = [];
|
|
10634
12044
|
const streamedToolCalls = [];
|
|
@@ -10667,7 +12077,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10667
12077
|
clearTimeout(totalTimer);
|
|
10668
12078
|
if (!forceAbort.signal.aborted) forceAbort.abort();
|
|
10669
12079
|
}
|
|
10670
|
-
|
|
12080
|
+
text5 = streamedText.join("");
|
|
10671
12081
|
toolCalls = streamedToolCalls;
|
|
10672
12082
|
finishReason = streamedFinishReason;
|
|
10673
12083
|
usage = streamedUsage;
|
|
@@ -10685,13 +12095,14 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10685
12095
|
maxRetries: upstreamMaxRetries(),
|
|
10686
12096
|
abortSignal: generateAbort.signal
|
|
10687
12097
|
});
|
|
10688
|
-
({ text:
|
|
12098
|
+
({ text: text5, toolCalls, finishReason, usage, warnings } = r);
|
|
10689
12099
|
} finally {
|
|
10690
12100
|
stopForwardingAbort();
|
|
10691
12101
|
clearTimeout(totalTimer);
|
|
10692
12102
|
if (!generateAbort.signal.aborted) generateAbort.abort();
|
|
10693
12103
|
}
|
|
10694
12104
|
}
|
|
12105
|
+
reportUnsupportedServiceTier(params, warnings);
|
|
10695
12106
|
const requiredProps = toolRequiredProps(params.tools);
|
|
10696
12107
|
return {
|
|
10697
12108
|
id: "msg_" + Date.now(),
|
|
@@ -10699,7 +12110,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10699
12110
|
role: "assistant",
|
|
10700
12111
|
model: modelId,
|
|
10701
12112
|
content: [
|
|
10702
|
-
...
|
|
12113
|
+
...text5 ? [{ type: "text", text: text5 }] : [],
|
|
10703
12114
|
...toolCalls.map((tc) => ({
|
|
10704
12115
|
type: "tool_use",
|
|
10705
12116
|
id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
|
|
@@ -10860,14 +12271,14 @@ function appendSecureLog(logPath, line) {
|
|
|
10860
12271
|
try {
|
|
10861
12272
|
const fd = openSync3(logPath, "a", 384);
|
|
10862
12273
|
try {
|
|
10863
|
-
|
|
12274
|
+
writeSync2(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
|
|
10864
12275
|
`);
|
|
10865
12276
|
} finally {
|
|
10866
12277
|
closeSync3(fd);
|
|
10867
12278
|
}
|
|
10868
12279
|
} catch {
|
|
10869
12280
|
try {
|
|
10870
|
-
|
|
12281
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
|
|
10871
12282
|
`);
|
|
10872
12283
|
} catch {
|
|
10873
12284
|
}
|
|
@@ -11033,8 +12444,9 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11033
12444
|
}
|
|
11034
12445
|
const upstreamUrl = route.upstreamUrl;
|
|
11035
12446
|
const routeAuthType = route.authType ?? "api";
|
|
12447
|
+
const loggedTier = isOpenAiOAuthRoute(route) ? oauthServiceTier() : void 0;
|
|
11036
12448
|
plog(
|
|
11037
|
-
() => `POST /v1/messages - alias=${originalModel} route=${route.realModelId} format=${route.modelFormat} key=${routeAuthType === "none" ? "none" : apiKey ? `len:${apiKey.length}` : "MISSING"}`
|
|
12449
|
+
() => `POST /v1/messages - alias=${originalModel} route=${route.realModelId} format=${route.modelFormat} key=${routeAuthType === "none" ? "none" : apiKey ? `len:${apiKey.length}` : "MISSING"}` + (loggedTier ? ` tier=${loggedTier}` : "")
|
|
11038
12450
|
);
|
|
11039
12451
|
const usesSdkAdapter = isSdkMigratedNpm(route.npm);
|
|
11040
12452
|
if (messagesEndpoint === "count_tokens") {
|
|
@@ -11102,6 +12514,10 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11102
12514
|
route.apiKey = refreshed;
|
|
11103
12515
|
},
|
|
11104
12516
|
signal: clientAbort.signal,
|
|
12517
|
+
// A route selected through a clodex: id or short alias must echo the
|
|
12518
|
+
// exact requested id back, or patched Claude Code misses the alias
|
|
12519
|
+
// context-window key and can skip auto-compaction.
|
|
12520
|
+
responseModelOverride: typeof originalModel === "string" && originalModel !== route.realModelId ? originalModel : void 0,
|
|
11105
12521
|
onUpstreamError: inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(inferenceLogPath, {
|
|
11106
12522
|
modelId: originalModel,
|
|
11107
12523
|
provider: route.providerId ?? route.aliasId.split(":")[1] ?? "unknown",
|
|
@@ -11119,7 +12535,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11119
12535
|
return;
|
|
11120
12536
|
}
|
|
11121
12537
|
if (usesSdkAdapter) {
|
|
11122
|
-
const openAiOAuth = route
|
|
12538
|
+
const openAiOAuth = isOpenAiOAuthRoute(route);
|
|
11123
12539
|
const claudeSessionIdHeader = Array.isArray(req.headers["x-claude-code-session-id"]) ? req.headers["x-claude-code-session-id"][0] : req.headers["x-claude-code-session-id"];
|
|
11124
12540
|
const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
|
|
11125
12541
|
const translationLifecycle = createTranslationLifecycle(
|
|
@@ -11140,6 +12556,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11140
12556
|
supportedParameters: route.supportedParameters,
|
|
11141
12557
|
reasoning: route.reasoning,
|
|
11142
12558
|
interleavedReasoningField: route.interleavedReasoningField,
|
|
12559
|
+
compatibility: route.compatibility,
|
|
11143
12560
|
upstreamModelId: route.realModelId
|
|
11144
12561
|
}
|
|
11145
12562
|
});
|
|
@@ -11158,6 +12575,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11158
12575
|
headers: route.headers,
|
|
11159
12576
|
useResponsesLite: route.useResponsesLite,
|
|
11160
12577
|
preferWebSockets: route.preferWebSockets,
|
|
12578
|
+
compatibility: route.compatibility,
|
|
11161
12579
|
onDebug: (msg) => plog(() => msg),
|
|
11162
12580
|
onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
|
|
11163
12581
|
});
|
|
@@ -11367,13 +12785,14 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
11367
12785
|
interleavedReasoningField: sdk?.interleavedReasoningField,
|
|
11368
12786
|
useResponsesLite: sdk?.useResponsesLite,
|
|
11369
12787
|
preferWebSockets: sdk?.preferWebSockets,
|
|
12788
|
+
compatibility: sdk?.compatibility,
|
|
11370
12789
|
headers: sdk?.headers
|
|
11371
12790
|
}], clientModelId, debug);
|
|
11372
12791
|
}
|
|
11373
12792
|
|
|
11374
12793
|
// src/server/index.ts
|
|
11375
12794
|
import pc10 from "picocolors";
|
|
11376
|
-
import { networkInterfaces } from "os";
|
|
12795
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
11377
12796
|
import * as p9 from "@clack/prompts";
|
|
11378
12797
|
|
|
11379
12798
|
// src/target-compatibility.ts
|
|
@@ -11586,6 +13005,7 @@ function translateOpenAiRequest(body, options) {
|
|
|
11586
13005
|
}
|
|
11587
13006
|
if (options?.openAiOAuth) {
|
|
11588
13007
|
const instructions = system?.trim() || "You are a coding assistant.";
|
|
13008
|
+
const serviceTier = oauthServiceTier();
|
|
11589
13009
|
return {
|
|
11590
13010
|
messages,
|
|
11591
13011
|
tools,
|
|
@@ -11595,7 +13015,8 @@ function translateOpenAiRequest(body, options) {
|
|
|
11595
13015
|
openai: {
|
|
11596
13016
|
store: false,
|
|
11597
13017
|
include: ["reasoning.encrypted_content"],
|
|
11598
|
-
instructions
|
|
13018
|
+
instructions,
|
|
13019
|
+
...serviceTier ? { serviceTier } : {}
|
|
11599
13020
|
}
|
|
11600
13021
|
}
|
|
11601
13022
|
};
|
|
@@ -11642,7 +13063,8 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11642
13063
|
...params,
|
|
11643
13064
|
maxRetries: upstreamMaxRetries(),
|
|
11644
13065
|
onError: () => {
|
|
11645
|
-
}
|
|
13066
|
+
},
|
|
13067
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
11646
13068
|
});
|
|
11647
13069
|
result = await collectOpenAiStream(stream);
|
|
11648
13070
|
} else {
|
|
@@ -11652,6 +13074,7 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11652
13074
|
maxRetries: upstreamMaxRetries()
|
|
11653
13075
|
});
|
|
11654
13076
|
}
|
|
13077
|
+
reportUnsupportedServiceTier(params, result.warnings);
|
|
11655
13078
|
const message = { role: "assistant", content: result.text || null };
|
|
11656
13079
|
if (result.toolCalls?.length) {
|
|
11657
13080
|
message.tool_calls = result.toolCalls.map((tc) => ({
|
|
@@ -11677,7 +13100,8 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
|
11677
13100
|
const { stream } = streamText2({
|
|
11678
13101
|
model,
|
|
11679
13102
|
...params,
|
|
11680
|
-
maxRetries: upstreamMaxRetries()
|
|
13103
|
+
maxRetries: upstreamMaxRetries(),
|
|
13104
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
11681
13105
|
});
|
|
11682
13106
|
const baseData = {
|
|
11683
13107
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -11922,6 +13346,9 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11922
13346
|
onTokenRefreshed: (refreshed) => {
|
|
11923
13347
|
model.apiKey = refreshed;
|
|
11924
13348
|
},
|
|
13349
|
+
// Echo the exact requested id when it differs from the upstream id, so
|
|
13350
|
+
// clients that key context windows on the response model still resolve.
|
|
13351
|
+
responseModelOverride: typeof body.model === "string" && body.model !== upstreamModelId(model) ? body.model : void 0,
|
|
11925
13352
|
onUpstreamError: options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
11926
13353
|
requestId,
|
|
11927
13354
|
modelId: body.model,
|
|
@@ -11952,6 +13379,9 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11952
13379
|
modelId: body.model,
|
|
11953
13380
|
effort: anthropicEffortFromRequest(body) ?? model.defaultEffort,
|
|
11954
13381
|
claudeSessionId,
|
|
13382
|
+
// Use the adapter's route predicate and resolver so this records the same
|
|
13383
|
+
// pre-dispatch request intent. It does not prove SDK serialization.
|
|
13384
|
+
serviceTier: isOpenAiOAuthRoute(model) ? oauthServiceTier() : void 0,
|
|
11955
13385
|
provider: inferenceProvider(model),
|
|
11956
13386
|
route: "translated",
|
|
11957
13387
|
requestPreview: getLatestMessagePreview(body.messages, body.system)
|
|
@@ -11961,7 +13391,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11961
13391
|
if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
|
|
11962
13392
|
plog(`tools truncated: ${toolCount} \u2192 ${npmMaxTools} (provider limit)`);
|
|
11963
13393
|
}
|
|
11964
|
-
const openAiOAuth = model
|
|
13394
|
+
const openAiOAuth = isOpenAiOAuthRoute(model);
|
|
11965
13395
|
const params = translateRequest(body, model.npm, {
|
|
11966
13396
|
defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
|
|
11967
13397
|
openAiOAuth,
|
|
@@ -11972,6 +13402,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
11972
13402
|
supportedParameters: model.supportedParameters,
|
|
11973
13403
|
reasoning: model.reasoning,
|
|
11974
13404
|
interleavedReasoningField: model.interleavedReasoningField,
|
|
13405
|
+
compatibility: model.compatibility,
|
|
11975
13406
|
upstreamModelId: upstreamModelId(model)
|
|
11976
13407
|
},
|
|
11977
13408
|
maxTools: npmMaxTools
|
|
@@ -12147,12 +13578,13 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
12147
13578
|
auditInference(options, {
|
|
12148
13579
|
modelId: body.model,
|
|
12149
13580
|
effort: openAiEffort(body),
|
|
13581
|
+
serviceTier: isOpenAiOAuthRoute(model) ? oauthServiceTier() : void 0,
|
|
12150
13582
|
provider: inferenceProvider(model),
|
|
12151
13583
|
route: "translated",
|
|
12152
13584
|
requestPreview: getLatestMessagePreview(body.messages, body.system)
|
|
12153
13585
|
});
|
|
12154
13586
|
const baseURL = model.modelFormat === "anthropic" ? model.baseUrl : model.apiBaseUrl;
|
|
12155
|
-
const openAiOAuth =
|
|
13587
|
+
const openAiOAuth = isOpenAiOAuthRoute(model);
|
|
12156
13588
|
const params = translateOpenAiRequest(body, { openAiOAuth });
|
|
12157
13589
|
const clientWantsStream = Boolean(body.stream);
|
|
12158
13590
|
const responseModelId = getResponseModelId(body.model, model, options);
|
|
@@ -12252,6 +13684,7 @@ async function getOrInitLanguageModel(modelCache, model, npm, baseURL, apiKey, w
|
|
|
12252
13684
|
headers: model.headers,
|
|
12253
13685
|
useResponsesLite: model.useResponsesLite,
|
|
12254
13686
|
preferWebSockets: model.preferWebSockets,
|
|
13687
|
+
compatibility: model.compatibility,
|
|
12255
13688
|
onWebSocketDiagnostic: webSocketDiagnosticsLogPath ? (event) => writeWebSocketDiagnosticLog(webSocketDiagnosticsLogPath, event) : void 0
|
|
12256
13689
|
});
|
|
12257
13690
|
cached = { apiKey, languageModel };
|
|
@@ -12670,7 +14103,7 @@ function requestHeadersWithoutProxyHeaders(req) {
|
|
|
12670
14103
|
}
|
|
12671
14104
|
return headers;
|
|
12672
14105
|
}
|
|
12673
|
-
function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false) {
|
|
14106
|
+
function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorized, agent, onErrorResponse, onResponseUsage, lifecycle, isLocalShutdown = () => false) {
|
|
12674
14107
|
return new Promise((resolve3) => {
|
|
12675
14108
|
const startedAt = Date.now();
|
|
12676
14109
|
let lastActivityAt = startedAt;
|
|
@@ -12730,7 +14163,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
12730
14163
|
path: req.url,
|
|
12731
14164
|
headers: requestHeadersWithoutProxyHeaders(req),
|
|
12732
14165
|
servername: net.isIP(origin.hostname) ? void 0 : origin.hostname,
|
|
12733
|
-
rejectUnauthorized
|
|
14166
|
+
rejectUnauthorized,
|
|
14167
|
+
agent
|
|
12734
14168
|
}, (upstreamRes) => {
|
|
12735
14169
|
headersReceived = true;
|
|
12736
14170
|
statusCode = upstreamRes.statusCode ?? 502;
|
|
@@ -13071,6 +14505,8 @@ async function startHttpProxy(options) {
|
|
|
13071
14505
|
reservedModelIds.add(normalizeRouteLookupId(modelId));
|
|
13072
14506
|
}
|
|
13073
14507
|
const anthropicOrigin = new URL2(options.anthropicOrigin ?? "https://api.anthropic.com");
|
|
14508
|
+
const anthropicProxyUrl = outboundProxyUrlForTarget(anthropicOrigin.href);
|
|
14509
|
+
let anthropicAgent;
|
|
13074
14510
|
let adapter = options.adapterHandle ?? null;
|
|
13075
14511
|
if (options.routes.length > 0) {
|
|
13076
14512
|
adapter ??= await startProxyCatalog(
|
|
@@ -13153,6 +14589,9 @@ async function startHttpProxy(options) {
|
|
|
13153
14589
|
claudeSessionId,
|
|
13154
14590
|
modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
|
|
13155
14591
|
effort: parsed ? anthropicEffortFromRequest(parsed) : void 0,
|
|
14592
|
+
// Only for the route that actually carries one, using the same
|
|
14593
|
+
// predicate and the same resolver the adapter applies.
|
|
14594
|
+
serviceTier: isOpenAiOAuthRoute(route) ? oauthServiceTier() : void 0,
|
|
13156
14595
|
provider,
|
|
13157
14596
|
route: route ? "translated" : "passthrough",
|
|
13158
14597
|
stream: Boolean(parsed?.stream),
|
|
@@ -13196,6 +14635,7 @@ async function startHttpProxy(options) {
|
|
|
13196
14635
|
rawBody,
|
|
13197
14636
|
anthropicOrigin,
|
|
13198
14637
|
options.anthropicRejectUnauthorized ?? true,
|
|
14638
|
+
anthropicAgent,
|
|
13199
14639
|
messagesEndpoint === "messages" && options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
13200
14640
|
requestId,
|
|
13201
14641
|
modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
|
|
@@ -13230,7 +14670,8 @@ async function startHttpProxy(options) {
|
|
|
13230
14670
|
res,
|
|
13231
14671
|
rawBody,
|
|
13232
14672
|
anthropicOrigin,
|
|
13233
|
-
options.anthropicRejectUnauthorized ?? true
|
|
14673
|
+
options.anthropicRejectUnauthorized ?? true,
|
|
14674
|
+
anthropicAgent
|
|
13234
14675
|
);
|
|
13235
14676
|
});
|
|
13236
14677
|
const sockets = /* @__PURE__ */ new Set();
|
|
@@ -13289,6 +14730,17 @@ async function startHttpProxy(options) {
|
|
|
13289
14730
|
adapter?.close();
|
|
13290
14731
|
throw err;
|
|
13291
14732
|
}
|
|
14733
|
+
if (anthropicProxyUrl && proxyUrlTargetsListener(
|
|
14734
|
+
anthropicProxyUrl,
|
|
14735
|
+
address.address,
|
|
14736
|
+
address.port
|
|
14737
|
+
)) {
|
|
14738
|
+
console.error(
|
|
14739
|
+
"clodex: HTTP(S)_PROXY points at this proxy; sending Anthropic passthrough direct"
|
|
14740
|
+
);
|
|
14741
|
+
} else {
|
|
14742
|
+
anthropicAgent = outboundHttpProxyAgent(anthropicOrigin.href);
|
|
14743
|
+
}
|
|
13292
14744
|
return {
|
|
13293
14745
|
host: options.host ?? "127.0.0.1",
|
|
13294
14746
|
port: address.port,
|
|
@@ -13304,6 +14756,7 @@ async function startHttpProxy(options) {
|
|
|
13304
14756
|
for (const socket of sockets) socket.destroy();
|
|
13305
14757
|
await new Promise((resolve3) => proxyServer.close(() => resolve3()));
|
|
13306
14758
|
mitmServer.close();
|
|
14759
|
+
anthropicAgent?.destroy();
|
|
13307
14760
|
adapter?.close();
|
|
13308
14761
|
}
|
|
13309
14762
|
};
|
|
@@ -13509,7 +14962,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
|
|
|
13509
14962
|
|
|
13510
14963
|
// src/server/index.ts
|
|
13511
14964
|
function getLocalIps() {
|
|
13512
|
-
const ifaces =
|
|
14965
|
+
const ifaces = networkInterfaces2();
|
|
13513
14966
|
const result = [];
|
|
13514
14967
|
for (const [name, iface] of Object.entries(ifaces)) {
|
|
13515
14968
|
for (const addr of iface ?? []) {
|
|
@@ -13595,7 +15048,8 @@ function enrichServerModelReasoning(model) {
|
|
|
13595
15048
|
apiBaseUrl: model.apiBaseUrl,
|
|
13596
15049
|
supportedParameters: model.supportedParameters,
|
|
13597
15050
|
reasoning: model.reasoning,
|
|
13598
|
-
interleavedReasoningField: model.interleavedReasoningField
|
|
15051
|
+
interleavedReasoningField: model.interleavedReasoningField,
|
|
15052
|
+
compatibility: model.compatibility
|
|
13599
15053
|
});
|
|
13600
15054
|
if (!caps.defaultLevel) return model;
|
|
13601
15055
|
return { ...model, defaultEffort: caps.defaultLevel };
|
|
@@ -14521,6 +15975,10 @@ function captureBuiltInPatchProofs(source, config, results) {
|
|
|
14521
15975
|
"PATCH 9: default effort",
|
|
14522
15976
|
/\/\*ccpatch:default-effort\*\/var _cce=Object\.assign\(Object\.create\(null\),\{[^{}]*\}\)\[String\([\w$]+\|\|""\)\.trim\(\)\.toLowerCase\(\)\];if\(_cce!==void 0\)return _cce;/
|
|
14523
15977
|
);
|
|
15978
|
+
addPattern(
|
|
15979
|
+
"PATCH 10: child network environment",
|
|
15980
|
+
/\/\*ccpatch:child-network-env\*\/let _clodexChildEnv=process\.env,[\s\S]*?catch\(_clodexError\)\{\}\}/
|
|
15981
|
+
);
|
|
14524
15982
|
return proofs;
|
|
14525
15983
|
}
|
|
14526
15984
|
function builtInPatchProofsChanged(source, proofs) {
|
|
@@ -14683,7 +16141,7 @@ function collectPristineFacts(args) {
|
|
|
14683
16141
|
}
|
|
14684
16142
|
|
|
14685
16143
|
// src/patch-transforms.ts
|
|
14686
|
-
var PATCH_TRANSFORMS_VERSION =
|
|
16144
|
+
var PATCH_TRANSFORMS_VERSION = 5;
|
|
14687
16145
|
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
14688
16146
|
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
14689
16147
|
function projectNativeEffort(effort) {
|
|
@@ -14978,6 +16436,36 @@ function applyClodexPatches(source, config) {
|
|
|
14978
16436
|
);
|
|
14979
16437
|
}
|
|
14980
16438
|
}
|
|
16439
|
+
{
|
|
16440
|
+
const patchName = "PATCH 10: child network environment";
|
|
16441
|
+
const marker = "/*ccpatch:child-network-env*/";
|
|
16442
|
+
const contractVar = q(NETWORK_ENV_CONTRACT_VAR);
|
|
16443
|
+
const networkVars = JSON.stringify(CHILD_NETWORK_ENV_VARS);
|
|
16444
|
+
const requiredBodyLiterals = [
|
|
16445
|
+
"{...process.env",
|
|
16446
|
+
"CLAUDE_CODE_REMOTE",
|
|
16447
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
16448
|
+
"CLAUDE_CODE_SUBSCRIPTION_TYPE",
|
|
16449
|
+
"CLAUDE_BG_PTY_AUTH",
|
|
16450
|
+
'"OTEL_"',
|
|
16451
|
+
"CLAUDE_CODE_OTEL_DIAG_STDERR"
|
|
16452
|
+
];
|
|
16453
|
+
applyOnce(
|
|
16454
|
+
patchName,
|
|
16455
|
+
/(function [\w$]+\(\)\{)(let [\w$]+=[\w$]+\(\),[\w$]+=Object\.keys\([\w$]+\)\.length>0,[\w$]+=Object\.keys\([\w$]+\)\.length>0,[\w$]+=[\w$]+\(process\.env\.CLAUDE_CODE_REMOTE\)\?(?:(?!\}\s*function )[\s\S])*?for\(let [\w$]+ of [\w$]+\)delete [\w$]+\[[\w$]+\],delete [\w$]+\[`INPUT_\$\{[\w$]+\}`\];return [\w$]+)(\})/,
|
|
16456
|
+
(_match, head, body, tail) => {
|
|
16457
|
+
const targetIsValid = requiredBodyLiterals.every((literal) => body.includes(literal)) && !/\bfunction\s*[\w$]*\(/.test(body);
|
|
16458
|
+
if (!targetIsValid) {
|
|
16459
|
+
log12("FAIL", patchName, "target validation failed");
|
|
16460
|
+
fail("clodex patch: child network environment target validation failed");
|
|
16461
|
+
}
|
|
16462
|
+
const restoredBody = body.replace(/process\.env/g, "_clodexChildEnv");
|
|
16463
|
+
const restore = marker + "let _clodexChildEnv=process.env,_clodexNetworkRaw=_clodexChildEnv[" + contractVar + "];if(_clodexNetworkRaw!==void 0){_clodexChildEnv={..._clodexChildEnv};delete _clodexChildEnv[" + contractVar + '];try{let _clodexNetwork=JSON.parse(_clodexNetworkRaw);if(_clodexNetwork&&typeof _clodexNetwork==="object"&&!Array.isArray(_clodexNetwork)&&_clodexNetwork.version===1&&_clodexNetwork.original&&typeof _clodexNetwork.original==="object"&&!Array.isArray(_clodexNetwork.original)&&_clodexNetwork.injected&&typeof _clodexNetwork.injected==="object"&&!Array.isArray(_clodexNetwork.injected)&&Object.keys(_clodexNetwork.original).every(_clodexKey=>' + networkVars + '.includes(_clodexKey)&&(typeof _clodexNetwork.original[_clodexKey]==="string"||_clodexNetwork.original[_clodexKey]===null)&&Object.prototype.hasOwnProperty.call(_clodexNetwork.injected,_clodexKey))&&Object.keys(_clodexNetwork.injected).every(_clodexKey=>' + networkVars + '.includes(_clodexKey)&&(typeof _clodexNetwork.injected[_clodexKey]==="string"||_clodexNetwork.injected[_clodexKey]===null)&&Object.prototype.hasOwnProperty.call(_clodexNetwork.original,_clodexKey)))for(let _clodexKey of ' + networkVars + '){if(Object.prototype.hasOwnProperty.call(_clodexNetwork.original,_clodexKey)&&Object.prototype.hasOwnProperty.call(_clodexNetwork.injected,_clodexKey)){let _clodexOriginal=_clodexNetwork.original[_clodexKey],_clodexInjected=_clodexNetwork.injected[_clodexKey],_clodexCurrent=_clodexChildEnv[_clodexKey]===void 0?null:_clodexChildEnv[_clodexKey];if((typeof _clodexOriginal==="string"||_clodexOriginal===null)&&(typeof _clodexInjected==="string"||_clodexInjected===null)&&_clodexCurrent===_clodexInjected){if(_clodexOriginal===null)delete _clodexChildEnv[_clodexKey];else _clodexChildEnv[_clodexKey]=_clodexOriginal}}}}catch(_clodexError){}}';
|
|
16464
|
+
return head + restore + restoredBody + tail;
|
|
16465
|
+
},
|
|
16466
|
+
{ marker, required: true }
|
|
16467
|
+
);
|
|
16468
|
+
}
|
|
14981
16469
|
return { content: js, results: report };
|
|
14982
16470
|
}
|
|
14983
16471
|
|
|
@@ -15091,6 +16579,7 @@ function buildDesiredPatchConfig() {
|
|
|
15091
16579
|
supportedParameters: model.supportedParameters,
|
|
15092
16580
|
reasoning: model.reasoning ?? modelsDev?.reasoning,
|
|
15093
16581
|
interleavedReasoningField: model.interleavedReasoningField ?? modelsDev?.interleaved?.field,
|
|
16582
|
+
compatibility: model.compatibility,
|
|
15094
16583
|
upstreamModelId: upstreamModelId2
|
|
15095
16584
|
});
|
|
15096
16585
|
meta.set(`${provider.id}:${model.id}`, {
|
|
@@ -15570,7 +17059,7 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
15570
17059
|
}
|
|
15571
17060
|
|
|
15572
17061
|
// src/cli.ts
|
|
15573
|
-
var STARTER_CLAUDE_FLAGS = /* @__PURE__ */ new Set(["--dry-run", "--trace", "--endpoint", "--proxy", "--save-mode", "--help", "-h", "--version", "-v"]);
|
|
17062
|
+
var STARTER_CLAUDE_FLAGS = /* @__PURE__ */ new Set(["--dry-run", "--trace", "--fast", "--endpoint", "--proxy", "--save-mode", "--help", "-h", "--version", "-v"]);
|
|
15574
17063
|
var CLODEX_LAUNCH_FLAGS = /* @__PURE__ */ new Set(["--provider", "--model"]);
|
|
15575
17064
|
function parseClodexLaunchFlag(arg, rest, index, parsed) {
|
|
15576
17065
|
if (arg === "--provider" || arg === "--model") {
|
|
@@ -15797,6 +17286,7 @@ function parseArgs(args) {
|
|
|
15797
17286
|
}
|
|
15798
17287
|
if (arg === "--dry-run") parsed.dryRun = true;
|
|
15799
17288
|
if (arg === "--trace") parsed.trace = true;
|
|
17289
|
+
if (arg === "--fast") parsed.fast = true;
|
|
15800
17290
|
consumeBridgeModeFlag(arg, parsed);
|
|
15801
17291
|
if (arg === "--save-mode") parsed.saveBridgeMode = true;
|
|
15802
17292
|
if (arg === "--help" || arg === "-h") parsed.showHelp = true;
|
|
@@ -15866,6 +17356,8 @@ ${pc13.bold("Options:")}
|
|
|
15866
17356
|
--save-mode With --endpoint/--proxy: save that mode as the claude default
|
|
15867
17357
|
--dry-run Run the wizard but show a preview instead of launching Claude Code
|
|
15868
17358
|
--trace Write debug logs to ~/.clodex/logs/ and show errors on exit
|
|
17359
|
+
--fast Request Codex fast mode (service_tier=priority) on ChatGPT-OAuth models
|
|
17360
|
+
(equivalent to CLODEX_SERVICE_TIER=fast; warns if the SDK omits it)
|
|
15869
17361
|
--provider Boot provider id (skip wizard when paired with --model or in print mode)
|
|
15870
17362
|
--model Boot model id (skip wizard when paired with --provider or in print mode)
|
|
15871
17363
|
--help Show this command help
|
|
@@ -16046,9 +17538,9 @@ ${pc13.bold("Behavior:")}
|
|
|
16046
17538
|
permissions. Local failures are reported but never block the built-ins.
|
|
16047
17539
|
Run clodex patch again after every claude update.`;
|
|
16048
17540
|
}
|
|
16049
|
-
function printHelp(
|
|
17541
|
+
function printHelp(text5) {
|
|
16050
17542
|
console.log(`
|
|
16051
|
-
${
|
|
17543
|
+
${text5}
|
|
16052
17544
|
`);
|
|
16053
17545
|
}
|
|
16054
17546
|
function reportInactiveCatalogAliases(modelAliases) {
|
|
@@ -16446,6 +17938,7 @@ async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
|
|
|
16446
17938
|
}
|
|
16447
17939
|
async function runClaudeCommand(parsed) {
|
|
16448
17940
|
const { dryRun, trace, launchProvider, launchModel } = parsed;
|
|
17941
|
+
if (parsed.fast) process.env.CLODEX_SERVICE_TIER = "fast";
|
|
16449
17942
|
const claudeArgs = normalizeClaudeAgentArgs(parsed.claudeArgs);
|
|
16450
17943
|
const agentStdout = wantsCleanAgentStdout("claude", claudeArgs);
|
|
16451
17944
|
setAgentStdoutMode(agentStdout);
|
|
@@ -16515,7 +18008,12 @@ Error: ${launchPlan.error}
|
|
|
16515
18008
|
catalogSpinner.stop("");
|
|
16516
18009
|
}
|
|
16517
18010
|
const allProviders = providersForTarget(providersForPicker(catalog), "claude");
|
|
18011
|
+
const blockedLaunchReason = launchPlan.skip && launchPlan.target?.providerId ? catalog.blockedProviders.get(launchPlan.target.providerId) : void 0;
|
|
16518
18012
|
if (allProviders.length === 0) {
|
|
18013
|
+
if (blockedLaunchReason) {
|
|
18014
|
+
p12.log.error(blockedLaunchReason);
|
|
18015
|
+
return 1;
|
|
18016
|
+
}
|
|
16519
18017
|
p12.log.warn("No providers available.");
|
|
16520
18018
|
p12.log.info(pc13.dim("Run clodex providers to get started."));
|
|
16521
18019
|
return 0;
|
|
@@ -16535,7 +18033,7 @@ Error: ${launchPlan.error}
|
|
|
16535
18033
|
const resolved = findProviderAndModel(allProviders, launchPlan.target);
|
|
16536
18034
|
if (!resolved) {
|
|
16537
18035
|
p12.log.error(
|
|
16538
|
-
`Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
|
|
18036
|
+
blockedLaunchReason ?? `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
|
|
16539
18037
|
);
|
|
16540
18038
|
return 1;
|
|
16541
18039
|
}
|
|
@@ -16730,6 +18228,7 @@ Error: ${launchPlan.error}
|
|
|
16730
18228
|
interleavedReasoningField: selectedModel.interleavedReasoningField,
|
|
16731
18229
|
useResponsesLite: selectedModel.useResponsesLite,
|
|
16732
18230
|
preferWebSockets: selectedModel.preferWebSockets,
|
|
18231
|
+
compatibility: selectedModel.compatibility,
|
|
16733
18232
|
headers: activeProvider.headers
|
|
16734
18233
|
},
|
|
16735
18234
|
launchApiKey ?? ""
|