@bman654/clodex 2.4.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-LBEJOEUY.js → chunk-MRO3KE3P.js} +504 -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 +1392 -213
- package/dist/cli.js.map +1 -1
- package/docs/background-agents.md +15 -0
- package/package.json +1 -1
- package/dist/chunk-LBEJOEUY.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"] }
|
|
@@ -902,8 +1058,9 @@ function buildChildEnv(baseUrl, model, apiKey, proxyPort, contextWindow, enableG
|
|
|
902
1058
|
applyClaudeCodeThirdPartyCompat(env);
|
|
903
1059
|
return env;
|
|
904
1060
|
}
|
|
905
|
-
function buildHttpProxyChildEnv(proxyPort, caCertPath) {
|
|
906
|
-
const
|
|
1061
|
+
function buildHttpProxyChildEnv(proxyPort, caCertPath, baseEnv = process.env) {
|
|
1062
|
+
const baseline = networkEnvBaseline(baseEnv);
|
|
1063
|
+
const env = { ...baseline };
|
|
907
1064
|
for (const name of CONFLICTING_ENV_VARS) {
|
|
908
1065
|
if (name === "ANTHROPIC_API_KEY" || name === "ANTHROPIC_AUTH_TOKEN" || name === "ANTHROPIC_MODEL") continue;
|
|
909
1066
|
delete env[name];
|
|
@@ -915,6 +1072,7 @@ function buildHttpProxyChildEnv(proxyPort, caCertPath) {
|
|
|
915
1072
|
env["http_proxy"] = proxyUrl;
|
|
916
1073
|
env["NODE_EXTRA_CA_CERTS"] = caCertPath;
|
|
917
1074
|
removeAnthropicProxyBypass(env);
|
|
1075
|
+
recordNetworkEnvMutation(baseline, env);
|
|
918
1076
|
return env;
|
|
919
1077
|
}
|
|
920
1078
|
function classifyKeyringError(err) {
|
|
@@ -952,7 +1110,10 @@ var KEYRING_GENERATION_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0
|
|
|
952
1110
|
function oauthProviderIdFromAccount(account) {
|
|
953
1111
|
const prefix = "oauth:provider:";
|
|
954
1112
|
const baseAccount = credentialAccountBase(account);
|
|
955
|
-
|
|
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);
|
|
956
1117
|
}
|
|
957
1118
|
var oauthRefreshInflight = /* @__PURE__ */ new Map();
|
|
958
1119
|
var OAUTH_CREDENTIAL_CACHE_MAX_AGE_MS = 3e4;
|
|
@@ -992,8 +1153,8 @@ function parseAuthRef(authRef) {
|
|
|
992
1153
|
function clodexKeyEnvVar(providerId) {
|
|
993
1154
|
return `CLODEX_KEY_${providerId.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
994
1155
|
}
|
|
995
|
-
function readEnvCredential(varName) {
|
|
996
|
-
const raw =
|
|
1156
|
+
function readEnvCredential(varName, env = process.env) {
|
|
1157
|
+
const raw = env[varName];
|
|
997
1158
|
if (!raw?.trim()) return null;
|
|
998
1159
|
return raw.trim().split(/\r?\n/)[0]?.trim() || null;
|
|
999
1160
|
}
|
|
@@ -1017,6 +1178,26 @@ function usableEnvCredential(source, value, rejectedAccessToken) {
|
|
|
1017
1178
|
}
|
|
1018
1179
|
return value;
|
|
1019
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
|
+
}
|
|
1020
1201
|
function readKeyringEntry(keyring, service, account) {
|
|
1021
1202
|
const value = new keyring.Entry(service, account).getPassword();
|
|
1022
1203
|
if (value !== null) return value;
|
|
@@ -2505,25 +2686,37 @@ async function deleteStoredCredential(ref, diag, blockLegacy = true) {
|
|
|
2505
2686
|
return false;
|
|
2506
2687
|
}
|
|
2507
2688
|
}
|
|
2508
|
-
async function
|
|
2689
|
+
async function resolveProviderCredentialWithSource(providerId, authRef, diag, options = {}) {
|
|
2509
2690
|
const parsed = parseAuthRef(authRef);
|
|
2510
|
-
if (parsed?.kind === "none") return null;
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
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 };
|
|
2519
2700
|
if (parsed.kind === "env") {
|
|
2520
|
-
return
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2701
|
+
return {
|
|
2702
|
+
credential: usableEnvCredential(
|
|
2703
|
+
`provider:${providerId}:env:${parsed.varName}`,
|
|
2704
|
+
readEnvCredential(parsed.varName),
|
|
2705
|
+
options.rejectedAccessToken
|
|
2706
|
+
)
|
|
2707
|
+
};
|
|
2525
2708
|
}
|
|
2526
|
-
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;
|
|
2527
2720
|
}
|
|
2528
2721
|
async function resolveProviderOAuthAccountId(authRef, diag) {
|
|
2529
2722
|
const parsed = parseAuthRef(authRef);
|
|
@@ -3461,12 +3654,23 @@ function applyPricingToRegistryProviders(registry, cache) {
|
|
|
3461
3654
|
let changed = false;
|
|
3462
3655
|
for (const provider of registry.providers) {
|
|
3463
3656
|
if (provider.preserveModelPricing) continue;
|
|
3464
|
-
if (!provider.modelsCache?.models.length) continue;
|
|
3465
3657
|
const platform = TEMPLATE_TO_PRICING_PLATFORM[provider.templateId] ?? TEMPLATE_TO_PRICING_PLATFORM[provider.id];
|
|
3466
|
-
const
|
|
3467
|
-
|
|
3468
|
-
|
|
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;
|
|
3469
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
|
+
}
|
|
3470
3674
|
}
|
|
3471
3675
|
}
|
|
3472
3676
|
if (changed) {
|
|
@@ -3774,6 +3978,48 @@ function isAnonymousProvider(provider) {
|
|
|
3774
3978
|
function isLegacyAnonymousCustomEndpoint(provider, credential) {
|
|
3775
3979
|
return provider.authType === void 0 && (provider.templateId === "custom-openai" || provider.templateId === "custom-anthropic") && provider.authRef === `keyring:provider:${provider.id}` && credential === "local";
|
|
3776
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
|
+
}
|
|
3777
4023
|
function materializeOne(provider, resolveCredential, agent) {
|
|
3778
4024
|
if (!provider.enabled) return null;
|
|
3779
4025
|
if (!isValidProviderId(provider.id)) return null;
|
|
@@ -3821,19 +4067,37 @@ function materializeRegistry(registry, resolveCredential, opts) {
|
|
|
3821
4067
|
|
|
3822
4068
|
// src/registry/load.ts
|
|
3823
4069
|
async function loadRegistryProviders(diag, opts) {
|
|
3824
|
-
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 };
|
|
3825
4077
|
const keys = /* @__PURE__ */ new Map();
|
|
3826
4078
|
const oauthAccountIds = /* @__PURE__ */ new Map();
|
|
3827
4079
|
const oauthProviderData = /* @__PURE__ */ new Map();
|
|
3828
|
-
|
|
3829
|
-
|
|
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;
|
|
3830
4084
|
try {
|
|
3831
|
-
|
|
3832
|
-
if (key) keys.set(provider.id, key);
|
|
4085
|
+
resolved = await resolveProviderCredentialWithSource(provider.id, provider.authRef, diag);
|
|
3833
4086
|
} catch (err) {
|
|
3834
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;
|
|
3835
4097
|
}
|
|
3836
|
-
|
|
4098
|
+
const credentialAvailable = Boolean(resolved.credential);
|
|
4099
|
+
if (resolved.credential) keys.set(provider.id, resolved.credential);
|
|
4100
|
+
if (provider.authType === "oauth" && credentialAvailable && !credentialOverride) {
|
|
3837
4101
|
try {
|
|
3838
4102
|
const accountId = await resolveProviderOAuthAccountId(provider.authRef, diag);
|
|
3839
4103
|
if (accountId) oauthAccountIds.set(provider.id, accountId);
|
|
@@ -3843,11 +4107,16 @@ async function loadRegistryProviders(diag, opts) {
|
|
|
3843
4107
|
}
|
|
3844
4108
|
}
|
|
3845
4109
|
}));
|
|
3846
|
-
|
|
4110
|
+
const materialized = materializeRegistry(selectedRegistry, (provider) => keys.get(provider.id) ?? null, opts).map((provider) => ({
|
|
3847
4111
|
...provider,
|
|
3848
4112
|
oauthAccountId: oauthAccountIds.get(provider.id),
|
|
3849
4113
|
providerData: oauthProviderData.get(provider.id)
|
|
3850
4114
|
}));
|
|
4115
|
+
Object.defineProperty(materialized, "blockedProviders", {
|
|
4116
|
+
value: blockedProviders,
|
|
4117
|
+
enumerable: false
|
|
4118
|
+
});
|
|
4119
|
+
return materialized;
|
|
3851
4120
|
}
|
|
3852
4121
|
|
|
3853
4122
|
// src/provider-templates.ts
|
|
@@ -3889,7 +4158,8 @@ function getTemplateById(id) {
|
|
|
3889
4158
|
|
|
3890
4159
|
// src/provider-catalog.ts
|
|
3891
4160
|
async function fetchProviderCatalog(opts) {
|
|
3892
|
-
|
|
4161
|
+
const warn = (message) => console.warn(message);
|
|
4162
|
+
return loadRegistryProviders(warn, { ...opts, warn });
|
|
3893
4163
|
}
|
|
3894
4164
|
function providersForPicker(providers) {
|
|
3895
4165
|
for (const p13 of providers) {
|
|
@@ -3932,16 +4202,115 @@ function formatRegistryAuthLabel(provider) {
|
|
|
3932
4202
|
}
|
|
3933
4203
|
return provider.authRef;
|
|
3934
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
|
+
}
|
|
3935
4250
|
async function resolveProvidersForDisplay() {
|
|
3936
4251
|
const reg = loadRegistry();
|
|
3937
4252
|
const entries = [];
|
|
3938
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;
|
|
3939
4298
|
entries.push({
|
|
3940
4299
|
id: provider.id,
|
|
3941
4300
|
name: provider.name,
|
|
3942
|
-
|
|
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
|
+
})(),
|
|
3943
4312
|
enabled: provider.enabled,
|
|
3944
|
-
authLabel
|
|
4313
|
+
authLabel,
|
|
3945
4314
|
inRegistry: true
|
|
3946
4315
|
});
|
|
3947
4316
|
}
|
|
@@ -3990,6 +4359,8 @@ import { createHash as createHash3 } from "crypto";
|
|
|
3990
4359
|
import { AsyncLocalStorage } from "async_hooks";
|
|
3991
4360
|
|
|
3992
4361
|
// src/outbound-proxy.ts
|
|
4362
|
+
import { networkInterfaces } from "os";
|
|
4363
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
3993
4364
|
function hasOutboundProxyEnv(env = process.env) {
|
|
3994
4365
|
return Boolean(
|
|
3995
4366
|
env["HTTPS_PROXY"]?.trim() || env["https_proxy"]?.trim() || env["HTTP_PROXY"]?.trim() || env["http_proxy"]?.trim()
|
|
@@ -4025,6 +4396,26 @@ function outboundProxyUrlForTarget(targetUrl, env = process.env) {
|
|
|
4025
4396
|
if (noProxyBypasses(parsed.hostname, env)) return void 0;
|
|
4026
4397
|
return proxy.trim();
|
|
4027
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
|
+
}
|
|
4028
4419
|
var dispatcherInstalled = false;
|
|
4029
4420
|
async function installOutboundProxyDispatcher() {
|
|
4030
4421
|
if (dispatcherInstalled) return true;
|
|
@@ -4041,11 +4432,24 @@ async function installOutboundProxyDispatcher() {
|
|
|
4041
4432
|
return false;
|
|
4042
4433
|
}
|
|
4043
4434
|
}
|
|
4044
|
-
|
|
4045
|
-
const proxyUrl = outboundProxyUrlForTarget(
|
|
4435
|
+
function outboundHttpProxyAgent(targetUrl, env = process.env) {
|
|
4436
|
+
const proxyUrl = outboundProxyUrlForTarget(targetUrl, env);
|
|
4046
4437
|
if (!proxyUrl) return void 0;
|
|
4047
|
-
|
|
4048
|
-
|
|
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);
|
|
4049
4453
|
}
|
|
4050
4454
|
|
|
4051
4455
|
// src/upstream-error.ts
|
|
@@ -4537,13 +4941,9 @@ function warnReasoningNormalizationGap(fields, log12) {
|
|
|
4537
4941
|
if (warnedReasoningGaps.has(signature)) return;
|
|
4538
4942
|
if (warnedReasoningGaps.size >= MAX_REASONING_GAP_WARNINGS) return;
|
|
4539
4943
|
warnedReasoningGaps.add(signature);
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
if (warnedReasoningGaps.size === MAX_REASONING_GAP_WARNINGS) {
|
|
4544
|
-
process.stderr.write("clodex: warning: further reasoning-normalization warnings suppressed.\n");
|
|
4545
|
-
}
|
|
4546
|
-
} catch {
|
|
4944
|
+
emitParentNotice(message);
|
|
4945
|
+
if (warnedReasoningGaps.size === MAX_REASONING_GAP_WARNINGS) {
|
|
4946
|
+
emitParentNotice("clodex: warning: further reasoning-normalization warnings suppressed.");
|
|
4547
4947
|
}
|
|
4548
4948
|
}
|
|
4549
4949
|
function toolArgumentNormalizationGap(expected, actual, requiredProps) {
|
|
@@ -4592,13 +4992,9 @@ function warnToolArgumentNormalizationGap(gap, log12) {
|
|
|
4592
4992
|
if (warnedToolArgumentGaps.has(signature)) return;
|
|
4593
4993
|
if (warnedToolArgumentGaps.size >= MAX_TOOL_ARGUMENT_GAP_WARNINGS) return;
|
|
4594
4994
|
warnedToolArgumentGaps.add(signature);
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4599
|
-
process.stderr.write("clodex: warning: further tool-argument normalization warnings suppressed.\n");
|
|
4600
|
-
}
|
|
4601
|
-
} catch {
|
|
4995
|
+
emitParentNotice(message);
|
|
4996
|
+
if (warnedToolArgumentGaps.size === MAX_TOOL_ARGUMENT_GAP_WARNINGS) {
|
|
4997
|
+
emitParentNotice("clodex: warning: further tool-argument normalization warnings suppressed.");
|
|
4602
4998
|
}
|
|
4603
4999
|
}
|
|
4604
5000
|
function continuationMismatchDetails(entry, payload, log12, warnOnGap = false) {
|
|
@@ -5036,12 +5432,12 @@ function expectedAssistantItems(ctx) {
|
|
|
5036
5432
|
const type = accumulator.type ?? (typeof done.type === "string" ? done.type : void 0);
|
|
5037
5433
|
if (type === "message") {
|
|
5038
5434
|
const doneContent = Array.isArray(done.content) ? done.content : void 0;
|
|
5039
|
-
const
|
|
5040
|
-
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 }] });
|
|
5041
5437
|
continue;
|
|
5042
5438
|
}
|
|
5043
5439
|
if (type === "reasoning") {
|
|
5044
|
-
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 : [];
|
|
5045
5441
|
output.push({ ...withoutEphemeralFields(done), type: "reasoning", summary });
|
|
5046
5442
|
continue;
|
|
5047
5443
|
}
|
|
@@ -5260,7 +5656,7 @@ function transportReplaySafe(ctx) {
|
|
|
5260
5656
|
function handleSocketMessage(entry, data) {
|
|
5261
5657
|
const ctx = entry.current;
|
|
5262
5658
|
if (!ctx || ctx.closed) return;
|
|
5263
|
-
const
|
|
5659
|
+
const text5 = Array.isArray(data) ? Buffer.concat(data).toString("utf8") : data.toString("utf8");
|
|
5264
5660
|
ctx.frameCount += 1;
|
|
5265
5661
|
if (ctx.transportRetryPending) {
|
|
5266
5662
|
ctx.transportRetryPending = false;
|
|
@@ -5272,9 +5668,9 @@ function handleSocketMessage(entry, data) {
|
|
|
5272
5668
|
}
|
|
5273
5669
|
let event;
|
|
5274
5670
|
try {
|
|
5275
|
-
event = JSON.parse(
|
|
5671
|
+
event = JSON.parse(text5);
|
|
5276
5672
|
} catch {
|
|
5277
|
-
ctx.pendingEvents.push(
|
|
5673
|
+
ctx.pendingEvents.push(text5.replace(/\r?\n/g, " "));
|
|
5278
5674
|
flushPending(ctx);
|
|
5279
5675
|
return;
|
|
5280
5676
|
}
|
|
@@ -5815,8 +6211,8 @@ function buildClaudeCodeBillingSystemLine() {
|
|
|
5815
6211
|
function systemBlockText(block) {
|
|
5816
6212
|
if (typeof block === "string") return block;
|
|
5817
6213
|
if (block && typeof block === "object" && "text" in block) {
|
|
5818
|
-
const
|
|
5819
|
-
return typeof
|
|
6214
|
+
const text5 = block.text;
|
|
6215
|
+
return typeof text5 === "string" ? text5 : void 0;
|
|
5820
6216
|
}
|
|
5821
6217
|
return void 0;
|
|
5822
6218
|
}
|
|
@@ -6734,7 +7130,12 @@ function isManagedCredentialAccount(account) {
|
|
|
6734
7130
|
const base = credentialAccountBase2(account);
|
|
6735
7131
|
if (!base) return false;
|
|
6736
7132
|
const oauth = /^oauth:provider:(.+)$/.exec(base);
|
|
6737
|
-
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
|
+
}
|
|
6738
7139
|
const provider = /^provider:([^:]+)(?::(.+))?$/.exec(base);
|
|
6739
7140
|
if (!provider || !isValidProviderId(provider[1])) return false;
|
|
6740
7141
|
const suffix = provider[2];
|
|
@@ -6886,7 +7287,7 @@ function appendError(errors, context, error) {
|
|
|
6886
7287
|
errors.push(`${context}: ${errorMessage(error)}`);
|
|
6887
7288
|
}
|
|
6888
7289
|
function credentialIsReferenced(registry, authRef) {
|
|
6889
|
-
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));
|
|
6890
7291
|
}
|
|
6891
7292
|
async function journalCredentialWrite(authRef) {
|
|
6892
7293
|
if (!await queueCredentialDelete(authRef)) {
|
|
@@ -7075,8 +7476,8 @@ function compactLogValueWithMarker(value, max) {
|
|
|
7075
7476
|
function systemPreview(system) {
|
|
7076
7477
|
if (typeof system === "string") return compactLogValue(system, REQUEST_PREVIEW_MAX) || void 0;
|
|
7077
7478
|
if (!Array.isArray(system)) return void 0;
|
|
7078
|
-
const
|
|
7079
|
-
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;
|
|
7080
7481
|
}
|
|
7081
7482
|
function inlineSystemPreview(messages) {
|
|
7082
7483
|
if (!Array.isArray(messages)) return void 0;
|
|
@@ -7102,9 +7503,9 @@ function getLatestMessagePreview(messages, system) {
|
|
|
7102
7503
|
if (typeof content === "string") {
|
|
7103
7504
|
summary = content;
|
|
7104
7505
|
} else if (Array.isArray(content)) {
|
|
7105
|
-
const
|
|
7106
|
-
if (
|
|
7107
|
-
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;
|
|
7108
7509
|
} else {
|
|
7109
7510
|
const blockTypes = [...new Set(content.filter((block) => Boolean(block && typeof block === "object")).map((block) => typeof block.type === "string" ? block.type : "unknown"))];
|
|
7110
7511
|
if (blockTypes.length > 0) blockSummary = `${role}: [${blockTypes.join(", ")}]`;
|
|
@@ -7200,6 +7601,7 @@ function writeInferenceRequestLog(path, entry) {
|
|
|
7200
7601
|
...claudeSessionId ? { claudeSessionId } : {},
|
|
7201
7602
|
modelId: compactLogValue(entry.modelId),
|
|
7202
7603
|
...entry.effort ? { effort: compactLogValue(entry.effort, 100) } : {},
|
|
7604
|
+
...entry.serviceTier ? { serviceTier: compactLogValue(entry.serviceTier, 40) } : {},
|
|
7203
7605
|
provider: compactLogValue(entry.provider, 200),
|
|
7204
7606
|
route: entry.route,
|
|
7205
7607
|
...entry.stream !== void 0 ? { stream: entry.stream } : {},
|
|
@@ -7624,6 +8026,25 @@ async function fetchTemplateModels(template, apiKey, baseUrlOverride, extraHeade
|
|
|
7624
8026
|
}
|
|
7625
8027
|
|
|
7626
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
|
+
}
|
|
7627
8048
|
async function probeTemplatePackage(template) {
|
|
7628
8049
|
if (!template.supported) return template.unsupportedReason ?? "Provider is not supported yet.";
|
|
7629
8050
|
if (!template.npm) return "Template is missing an SDK package.";
|
|
@@ -7657,13 +8078,10 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7657
8078
|
const existingState = await withRegistryWriteLock(() => {
|
|
7658
8079
|
const registry = loadRegistryStrict();
|
|
7659
8080
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7660
|
-
|
|
8081
|
+
const error = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8082
|
+
if (error) {
|
|
7661
8083
|
return {
|
|
7662
|
-
error
|
|
7663
|
-
added: false,
|
|
7664
|
-
error: `${template.name} is already configured.`,
|
|
7665
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7666
|
-
}
|
|
8084
|
+
error
|
|
7667
8085
|
};
|
|
7668
8086
|
}
|
|
7669
8087
|
return { authRef: existing?.authRef ?? null, error: null };
|
|
@@ -7697,14 +8115,11 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7697
8115
|
const currentState = await withRegistryWriteLock(() => {
|
|
7698
8116
|
const registry = loadRegistryStrict();
|
|
7699
8117
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7700
|
-
|
|
8118
|
+
const error = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8119
|
+
if (error) {
|
|
7701
8120
|
return {
|
|
7702
8121
|
existingAuthRef: null,
|
|
7703
|
-
error
|
|
7704
|
-
added: false,
|
|
7705
|
-
error: `${template.name} is already configured.`,
|
|
7706
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7707
|
-
}
|
|
8122
|
+
error
|
|
7708
8123
|
};
|
|
7709
8124
|
}
|
|
7710
8125
|
return { existingAuthRef: existing?.authRef ?? null, error: null };
|
|
@@ -7726,13 +8141,8 @@ async function addProviderFromTemplate(template, apiKey, opts) {
|
|
|
7726
8141
|
return withRegistryWriteLock(async () => {
|
|
7727
8142
|
const registry = loadRegistryStrict();
|
|
7728
8143
|
const existing = registry.providers.find((p13) => p13.id === template.id);
|
|
7729
|
-
|
|
7730
|
-
|
|
7731
|
-
added: false,
|
|
7732
|
-
error: `${template.name} is already configured.`,
|
|
7733
|
-
hint: `Remove it first with: clodex providers remove ${template.id}`
|
|
7734
|
-
};
|
|
7735
|
-
}
|
|
8144
|
+
const existingError = existingProviderError(template, existing, opts?.replaceExisting);
|
|
8145
|
+
if (existingError) return existingError;
|
|
7736
8146
|
if ((existing?.authRef ?? null) !== currentState.existingAuthRef) {
|
|
7737
8147
|
return {
|
|
7738
8148
|
added: false,
|
|
@@ -7823,11 +8233,21 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7823
8233
|
credentialDeleted: false,
|
|
7824
8234
|
error: `Provider not found: ${id}`
|
|
7825
8235
|
},
|
|
7826
|
-
|
|
8236
|
+
queuedRefs: []
|
|
7827
8237
|
};
|
|
7828
8238
|
}
|
|
7829
8239
|
const [removedProvider] = registry.providers.splice(index, 1);
|
|
7830
|
-
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
|
+
}
|
|
7831
8251
|
saveRegistry(registry);
|
|
7832
8252
|
return {
|
|
7833
8253
|
result: {
|
|
@@ -7836,14 +8256,14 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7836
8256
|
name: removedProvider.name,
|
|
7837
8257
|
credentialDeleted: false
|
|
7838
8258
|
},
|
|
7839
|
-
|
|
8259
|
+
queuedRefs
|
|
7840
8260
|
};
|
|
7841
8261
|
});
|
|
7842
|
-
if (removal.
|
|
8262
|
+
if (removal.queuedRefs.length > 0) {
|
|
7843
8263
|
try {
|
|
7844
8264
|
const cleanup = await reconcilePendingCredentialDeletes();
|
|
7845
|
-
removal.result.credentialDeleted = cleanup.deleted.includes(
|
|
7846
|
-
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;
|
|
7847
8267
|
} catch {
|
|
7848
8268
|
removal.result.credentialCleanupPending = true;
|
|
7849
8269
|
}
|
|
@@ -7854,6 +8274,60 @@ async function removeProviderWithinLifecycle(id, opts) {
|
|
|
7854
8274
|
async function removeProviderFromRegistry(id, opts) {
|
|
7855
8275
|
return withProviderMutationLock(id, () => removeProviderWithinLifecycle(id, opts));
|
|
7856
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
|
+
}
|
|
7857
8331
|
function toggleProviderEnabled(id) {
|
|
7858
8332
|
return withRegistryWriteLockSync(() => {
|
|
7859
8333
|
const registry = loadRegistryStrict();
|
|
@@ -8117,6 +8591,41 @@ function resolveModelSource(provider) {
|
|
|
8117
8591
|
}
|
|
8118
8592
|
|
|
8119
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
|
+
}
|
|
8120
8629
|
var PLACEHOLDER_KEYS = /* @__PURE__ */ new Set([
|
|
8121
8630
|
"anything",
|
|
8122
8631
|
"local",
|
|
@@ -8156,20 +8665,36 @@ function skipWithCachedModels(provider, reason) {
|
|
|
8156
8665
|
reason
|
|
8157
8666
|
};
|
|
8158
8667
|
}
|
|
8159
|
-
async function
|
|
8160
|
-
|
|
8161
|
-
|
|
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;
|
|
8162
8672
|
try {
|
|
8163
|
-
|
|
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
|
+
};
|
|
8164
8687
|
} catch {
|
|
8165
|
-
|
|
8688
|
+
resolved = { credential: null };
|
|
8166
8689
|
}
|
|
8167
|
-
if (!isLikelyPlaceholderKey(
|
|
8168
|
-
|
|
8690
|
+
if (resolved.credentialOverride || !isLikelyPlaceholderKey(resolved.credential)) {
|
|
8691
|
+
return resolved;
|
|
8692
|
+
}
|
|
8693
|
+
for (const envVar of ENV_FALLBACK_BY_PROVIDER[effectiveProvider.id] ?? []) {
|
|
8169
8694
|
const fromEnv = process.env[envVar]?.trim();
|
|
8170
|
-
if (fromEnv && !isLikelyPlaceholderKey(fromEnv)) return fromEnv;
|
|
8695
|
+
if (fromEnv && !isLikelyPlaceholderKey(fromEnv)) return { credential: fromEnv };
|
|
8171
8696
|
}
|
|
8172
|
-
return
|
|
8697
|
+
return resolved;
|
|
8173
8698
|
}
|
|
8174
8699
|
|
|
8175
8700
|
// src/data/openai-oauth-models.ts
|
|
@@ -8319,10 +8844,13 @@ async function refreshOpenAiOAuthModels(accessToken) {
|
|
|
8319
8844
|
if (chatGptEntries.length > 0) {
|
|
8320
8845
|
return { models: toModels(chatGptEntries), source: "live" };
|
|
8321
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));
|
|
8322
8849
|
return {
|
|
8323
8850
|
models: [...seedById.values()],
|
|
8324
8851
|
source: "seed",
|
|
8325
|
-
failureReason: chatGptResult.error ?? codexResult.error
|
|
8852
|
+
failureReason: credentialFailure ?? chatGptResult.error ?? codexResult.error,
|
|
8853
|
+
credentialRejected: credentialFailure !== void 0
|
|
8326
8854
|
};
|
|
8327
8855
|
}
|
|
8328
8856
|
async function refreshApiListProvider(provider, apiKey) {
|
|
@@ -8375,32 +8903,111 @@ async function refreshApiListProvider(provider, apiKey) {
|
|
|
8375
8903
|
baseUrl: fetched.baseUrl
|
|
8376
8904
|
};
|
|
8377
8905
|
}
|
|
8378
|
-
function updateProviderCache(registry, providerId, models, baseUrl) {
|
|
8906
|
+
function updateProviderCache(registry, providerId, models, baseUrl, credentialSnapshot) {
|
|
8379
8907
|
const idx = registry.providers.findIndex((p13) => p13.id === providerId);
|
|
8380
8908
|
if (idx < 0) return;
|
|
8381
8909
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8382
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;
|
|
8383
8922
|
registry.providers[idx] = {
|
|
8384
8923
|
...existing,
|
|
8385
|
-
refreshedAt: now,
|
|
8386
8924
|
api: baseUrl ? { ...existing.api, url: baseUrl } : existing.api,
|
|
8387
|
-
|
|
8388
|
-
|
|
8389
|
-
models
|
|
8390
|
-
}
|
|
8925
|
+
...authAccounts ? { authAccounts } : {},
|
|
8926
|
+
...!temporaryAccount ? { refreshedAt: now, modelsCache } : {}
|
|
8391
8927
|
};
|
|
8392
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
|
+
}
|
|
8393
8944
|
function providerDiscoveryInputsMatch(current, started) {
|
|
8394
|
-
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
|
+
}
|
|
8395
8975
|
}
|
|
8396
|
-
async function refreshProviderModels(providerId, apiKey, registry) {
|
|
8976
|
+
async function refreshProviderModels(providerId, apiKey, registry, credentialSnapshot) {
|
|
8397
8977
|
const workingRegistry = registry ?? loadRegistryStrict();
|
|
8398
8978
|
const provider = workingRegistry.providers.find((p13) => p13.id === providerId);
|
|
8399
8979
|
if (!provider) {
|
|
8400
8980
|
return { id: providerId, name: providerId, ok: false, reason: "Provider not found." };
|
|
8401
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
|
+
}
|
|
8402
9001
|
const source = resolveModelSource(provider);
|
|
8403
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
|
+
}
|
|
8404
9011
|
return {
|
|
8405
9012
|
id: provider.id,
|
|
8406
9013
|
name: provider.name,
|
|
@@ -8410,7 +9017,8 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8410
9017
|
};
|
|
8411
9018
|
}
|
|
8412
9019
|
try {
|
|
8413
|
-
const previousModelCount =
|
|
9020
|
+
const previousModelCount = cacheProvider.modelsCache?.models.length ?? 0;
|
|
9021
|
+
const hadPreviousRefresh = isTemporaryAccountSelection(credentialSnapshot) ? cacheProvider.modelsCache !== void 0 : provider.refreshedAt !== void 0;
|
|
8414
9022
|
let models = [];
|
|
8415
9023
|
let baseUrl;
|
|
8416
9024
|
let oauthFallbackReason;
|
|
@@ -8423,11 +9031,21 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8423
9031
|
reason: "OAuth token not available \u2014 try signing in again with clodex providers auth."
|
|
8424
9032
|
};
|
|
8425
9033
|
}
|
|
8426
|
-
const oauthResult = await refreshOAuthProvider(
|
|
9034
|
+
const oauthResult = await refreshOAuthProvider(cacheProvider, apiKey);
|
|
8427
9035
|
const failureDetail = oauthResult.failureReason ? ` (${oauthResult.failureReason})` : "";
|
|
8428
|
-
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) {
|
|
8429
9047
|
return skipWithCachedModels(
|
|
8430
|
-
|
|
9048
|
+
cacheProvider,
|
|
8431
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.`
|
|
8432
9050
|
);
|
|
8433
9051
|
}
|
|
@@ -8448,11 +9066,21 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8448
9066
|
const keyOptional = template?.apiKeyOptional === true;
|
|
8449
9067
|
const effectiveKey = keyOptional && isLikelyPlaceholderKey(apiKey) ? "" : apiKey;
|
|
8450
9068
|
if (!keyOptional && isLikelyPlaceholderKey(effectiveKey)) {
|
|
8451
|
-
if (cachedModelCount(
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
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
|
+
};
|
|
8456
9084
|
}
|
|
8457
9085
|
return {
|
|
8458
9086
|
id: provider.id,
|
|
@@ -8471,11 +9099,15 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8471
9099
|
}
|
|
8472
9100
|
const fetched = await refreshApiListProvider(provider, effectiveKey ?? "");
|
|
8473
9101
|
if (fetched.error) {
|
|
8474
|
-
if ((fetched.error.includes("rejected") || fetched.error.includes("401") || fetched.error.includes("403")) && cachedModelCount(
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
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
|
+
};
|
|
8479
9111
|
}
|
|
8480
9112
|
return { id: provider.id, name: provider.name, ok: false, reason: fetched.error };
|
|
8481
9113
|
}
|
|
@@ -8489,13 +9121,16 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8489
9121
|
const currentRegistry = loadRegistryStrict();
|
|
8490
9122
|
const currentProvider = currentRegistry.providers.find((candidate) => candidate.id === providerId);
|
|
8491
9123
|
if (!currentProvider) throw new Error("Provider was removed while models were refreshing.");
|
|
9124
|
+
if (credentialSnapshot) {
|
|
9125
|
+
assertRefreshCredentialStillCurrent(currentProvider, credentialSnapshot);
|
|
9126
|
+
}
|
|
8492
9127
|
if (currentProvider.authRef !== provider.authRef) {
|
|
8493
9128
|
throw new Error("Provider credentials changed while models were refreshing.");
|
|
8494
9129
|
}
|
|
8495
9130
|
if (!providerDiscoveryInputsMatch(currentProvider, provider)) {
|
|
8496
9131
|
throw new Error("Provider configuration changed while models were refreshing.");
|
|
8497
9132
|
}
|
|
8498
|
-
updateProviderCache(currentRegistry, providerId, enriched, baseUrl);
|
|
9133
|
+
updateProviderCache(currentRegistry, providerId, enriched, baseUrl, credentialSnapshot);
|
|
8499
9134
|
saveRegistry(currentRegistry);
|
|
8500
9135
|
});
|
|
8501
9136
|
enrichPricingAsync();
|
|
@@ -8504,7 +9139,7 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8504
9139
|
name: provider.name,
|
|
8505
9140
|
ok: true,
|
|
8506
9141
|
modelCount: enriched.length,
|
|
8507
|
-
previousModelCount:
|
|
9142
|
+
previousModelCount: hadPreviousRefresh ? previousModelCount : void 0,
|
|
8508
9143
|
reason: oauthFallbackReason
|
|
8509
9144
|
};
|
|
8510
9145
|
} catch (err) {
|
|
@@ -8516,13 +9151,63 @@ async function refreshProviderModels(providerId, apiKey, registry) {
|
|
|
8516
9151
|
};
|
|
8517
9152
|
}
|
|
8518
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
|
+
}
|
|
8519
9190
|
async function refreshAllProviderModels(resolveKey) {
|
|
8520
9191
|
const refreshed = [];
|
|
8521
9192
|
const registry = loadRegistryStrict();
|
|
8522
9193
|
const enabledProviders = registry.providers.filter((p13) => p13.enabled);
|
|
8523
9194
|
for (const provider of enabledProviders) {
|
|
8524
|
-
const
|
|
8525
|
-
|
|
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
|
+
}
|
|
8526
9211
|
}
|
|
8527
9212
|
return { refreshed };
|
|
8528
9213
|
}
|
|
@@ -8531,6 +9216,15 @@ async function refreshAllProviderModels(resolveKey) {
|
|
|
8531
9216
|
import pc3 from "picocolors";
|
|
8532
9217
|
import * as p2 from "@clack/prompts";
|
|
8533
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
|
+
}
|
|
8534
9228
|
var OPENAI_DISPLAY = "OpenAI ChatGPT Plus/Pro";
|
|
8535
9229
|
var PROVIDER_DISPLAY = {
|
|
8536
9230
|
openai: OPENAI_DISPLAY,
|
|
@@ -8560,6 +9254,47 @@ async function runNativeDeviceCode(providerId) {
|
|
|
8560
9254
|
throw err;
|
|
8561
9255
|
}
|
|
8562
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
|
+
}
|
|
8563
9298
|
function oauthDisplayName(registryId, fallbackName) {
|
|
8564
9299
|
if (registryId === "openai-oauth") return "OpenAI (ChatGPT)";
|
|
8565
9300
|
return fallbackName;
|
|
@@ -8571,7 +9306,7 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8571
9306
|
const registry = loadRegistryStrict();
|
|
8572
9307
|
const template = getTemplateById(templateId);
|
|
8573
9308
|
let entry = registry.providers.find((pr) => pr.id === registryId);
|
|
8574
|
-
if (entry
|
|
9309
|
+
if ((entry ? providerDefaultAuthRef(entry) : void 0) !== expectedAuthRef) {
|
|
8575
9310
|
throw new Error(`Provider "${registryId}" changed while its credential was being saved`);
|
|
8576
9311
|
}
|
|
8577
9312
|
if (!entry) {
|
|
@@ -8579,7 +9314,7 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8579
9314
|
throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
|
|
8580
9315
|
}
|
|
8581
9316
|
}
|
|
8582
|
-
const previousAuthRef = entry
|
|
9317
|
+
const previousAuthRef = entry ? providerDefaultAuthRef(entry) : void 0;
|
|
8583
9318
|
if (!entry) {
|
|
8584
9319
|
if (!template) throw new Error(`Provider "${providerId}" has no template`);
|
|
8585
9320
|
const displayName = oauthDisplayName(registryId, template.name);
|
|
@@ -8597,8 +9332,34 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8597
9332
|
},
|
|
8598
9333
|
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8599
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
|
+
}
|
|
8600
9357
|
} else {
|
|
8601
9358
|
entry = { ...entry, authType: "oauth", authRef, templateId };
|
|
9359
|
+
delete entry.defaultAuthRef;
|
|
9360
|
+
delete entry.defaultModelsCache;
|
|
9361
|
+
delete entry.modelsCache;
|
|
9362
|
+
delete entry.refreshedAt;
|
|
8602
9363
|
}
|
|
8603
9364
|
const idx = registry.providers.findIndex((provider) => provider.id === registryId);
|
|
8604
9365
|
if (idx >= 0) registry.providers[idx] = entry;
|
|
@@ -8614,9 +9375,9 @@ async function upsertOAuthProvider(providerId, authRef, expectedAuthRef) {
|
|
|
8614
9375
|
return entry;
|
|
8615
9376
|
});
|
|
8616
9377
|
}
|
|
8617
|
-
async function persistNativeOAuthCredential(providerId, cred) {
|
|
9378
|
+
async function persistNativeOAuthCredential(providerId, cred, accountName) {
|
|
8618
9379
|
const registryId = toOAuthRegistryId(providerId);
|
|
8619
|
-
const account = `oauth:provider:${registryId}`;
|
|
9380
|
+
const account = accountName ? `oauth:provider:${registryId}:account:${accountName}` : `oauth:provider:${registryId}`;
|
|
8620
9381
|
const registryProvider = await withProviderMutationLock(registryId, async () => {
|
|
8621
9382
|
const existingAuthRef = await withRegistryWriteLock(
|
|
8622
9383
|
() => {
|
|
@@ -8626,7 +9387,17 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8626
9387
|
if (!existing && !getTemplateById(templateId)) {
|
|
8627
9388
|
throw new Error(`Provider "${providerId}" is not in your registry and has no template`);
|
|
8628
9389
|
}
|
|
8629
|
-
|
|
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;
|
|
8630
9401
|
}
|
|
8631
9402
|
);
|
|
8632
9403
|
const authRef = credentialInstanceAuthRef(account);
|
|
@@ -8643,7 +9414,7 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8643
9414
|
`Could not save OAuth tokens to the credential store${diagMsg ? ` \u2014 ${diagMsg}` : " \u2014 check access and try again"}`
|
|
8644
9415
|
);
|
|
8645
9416
|
}
|
|
8646
|
-
return upsertOAuthProvider(providerId, authRef, existingAuthRef);
|
|
9417
|
+
return accountName ? upsertOAuthAccountSlot(registryId, accountName, authRef, existingAuthRef) : upsertOAuthProvider(providerId, authRef, existingAuthRef);
|
|
8647
9418
|
});
|
|
8648
9419
|
});
|
|
8649
9420
|
let credentialCleanupPending = true;
|
|
@@ -8658,11 +9429,25 @@ async function persistNativeOAuthCredential(providerId, cred) {
|
|
|
8658
9429
|
credentialCleanupPending
|
|
8659
9430
|
};
|
|
8660
9431
|
}
|
|
8661
|
-
async function authenticateProvider(providerId,
|
|
9432
|
+
async function authenticateProvider(providerId, options = {}) {
|
|
8662
9433
|
const registryId = toOAuthRegistryId(providerId);
|
|
9434
|
+
const accountName = options.account === void 0 ? void 0 : validateOAuthAccountName(options.account);
|
|
8663
9435
|
if (!supportsNativeOAuth(providerId)) {
|
|
8664
9436
|
throw new Error("OAuth sign-in is only available for openai (ChatGPT Plus/Pro).");
|
|
8665
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
|
+
}
|
|
8666
9451
|
let storeDiagMsg = "";
|
|
8667
9452
|
const storeReady = await probeProviderCredentialStore(oauthAuthRef(registryId), (msg) => {
|
|
8668
9453
|
storeDiagMsg = msg;
|
|
@@ -8673,12 +9458,29 @@ async function authenticateProvider(providerId, _options = {}) {
|
|
|
8673
9458
|
);
|
|
8674
9459
|
}
|
|
8675
9460
|
const cred = await runNativeDeviceCode(providerId);
|
|
8676
|
-
const persisted = await persistNativeOAuthCredential(providerId, cred);
|
|
9461
|
+
const persisted = await persistNativeOAuthCredential(providerId, cred, accountName);
|
|
8677
9462
|
const refreshSpinner = p2.spinner();
|
|
8678
9463
|
refreshSpinner.start("Refreshing model list...");
|
|
8679
9464
|
try {
|
|
8680
|
-
|
|
8681
|
-
|
|
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
|
+
}
|
|
8682
9484
|
} catch {
|
|
8683
9485
|
refreshSpinner.stop("Could not refresh models \u2014 run clodex providers refresh-models later");
|
|
8684
9486
|
}
|
|
@@ -8694,9 +9496,15 @@ function providerAuthHelpText() {
|
|
|
8694
9496
|
|
|
8695
9497
|
${pc3.bold("Usage:")}
|
|
8696
9498
|
clodex providers auth openai
|
|
9499
|
+
clodex providers auth openai --account work
|
|
8697
9500
|
|
|
8698
9501
|
${pc3.bold("Device code (works on SSH/VPS):")}
|
|
8699
|
-
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`;
|
|
8700
9508
|
}
|
|
8701
9509
|
|
|
8702
9510
|
// src/prompts.ts
|
|
@@ -9045,10 +9853,19 @@ function parseProvidersArgs(args) {
|
|
|
9045
9853
|
if (first === "auth") {
|
|
9046
9854
|
if (rest.length === 0) return { subcommand: "auth", showHelp: true };
|
|
9047
9855
|
let authMethod;
|
|
9856
|
+
let authAccount;
|
|
9048
9857
|
const positional = [];
|
|
9049
|
-
for (
|
|
9858
|
+
for (let i = 0; i < rest.length; i++) {
|
|
9859
|
+
const arg = rest[i];
|
|
9050
9860
|
if (arg === "--native") authMethod = "native";
|
|
9051
|
-
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("-")) {
|
|
9052
9869
|
return { subcommand: "auth", showHelp: false, error: `Unknown auth option: ${arg}` };
|
|
9053
9870
|
} else {
|
|
9054
9871
|
positional.push(arg);
|
|
@@ -9057,7 +9874,7 @@ function parseProvidersArgs(args) {
|
|
|
9057
9874
|
if (positional.length !== 1) {
|
|
9058
9875
|
return { subcommand: "auth", showHelp: false, error: "Usage: clodex providers auth <id>" };
|
|
9059
9876
|
}
|
|
9060
|
-
return { subcommand: "auth", showHelp: false, removeId: positional[0], authMethod };
|
|
9877
|
+
return { subcommand: "auth", showHelp: false, removeId: positional[0], authMethod, authAccount };
|
|
9061
9878
|
}
|
|
9062
9879
|
if (first === "remove") {
|
|
9063
9880
|
if (rest.length === 0) return { subcommand: "remove", showHelp: false, error: "Usage: clodex providers remove <id>" };
|
|
@@ -9090,13 +9907,133 @@ ${pc5.bold("Subcommands:")}
|
|
|
9090
9907
|
remove Remove a provider by id
|
|
9091
9908
|
refresh-models Update cached model lists`;
|
|
9092
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
|
+
}
|
|
9093
10027
|
function providerLabel(name, modelCount, enabled) {
|
|
9094
10028
|
return `${fmtEnabledStar(enabled)} ${fmtProvider(name)} ${pc5.dim(`(${modelCount} model${modelCount === 1 ? "" : "s"})`)}`;
|
|
9095
10029
|
}
|
|
9096
|
-
async function runProvidersAuthWithCleanupState(providerId, method, cleanupState) {
|
|
10030
|
+
async function runProvidersAuthWithCleanupState(providerId, method, cleanupState, account) {
|
|
9097
10031
|
try {
|
|
9098
|
-
const result = await authenticateProvider(providerId, { method });
|
|
9099
|
-
|
|
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
|
+
);
|
|
9100
10037
|
reportCredentialCleanup(result.credentialCleanupPending, cleanupState, true);
|
|
9101
10038
|
return 0;
|
|
9102
10039
|
} catch (err) {
|
|
@@ -9111,8 +10048,8 @@ async function runProvidersAuthWithCleanupState(providerId, method, cleanupState
|
|
|
9111
10048
|
async function runProvidersAuth(providerId, method) {
|
|
9112
10049
|
return runProvidersAuthWithCleanupState(providerId, method);
|
|
9113
10050
|
}
|
|
9114
|
-
async function runProvidersRefreshModels(providerId) {
|
|
9115
|
-
const resolveKey = async (provider) =>
|
|
10051
|
+
async function runProvidersRefreshModels(providerId, options = {}) {
|
|
10052
|
+
const resolveKey = async (provider) => resolveProviderCredentialWithSource(provider.id, provider.authRef);
|
|
9116
10053
|
if (providerId) {
|
|
9117
10054
|
const registry = loadRegistry();
|
|
9118
10055
|
const provider = registry.providers.find((p13) => p13.id === providerId);
|
|
@@ -9122,11 +10059,25 @@ async function runProvidersRefreshModels(providerId) {
|
|
|
9122
10059
|
}
|
|
9123
10060
|
const spinner6 = p4.spinner();
|
|
9124
10061
|
spinner6.start(`Refreshing ${provider.name}...`);
|
|
9125
|
-
const
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
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
|
+
}
|
|
9130
10081
|
spinner6.stop("");
|
|
9131
10082
|
if (result.skipped) {
|
|
9132
10083
|
const countNote = result.modelCount ? ` (${result.modelCount} cached models kept)` : "";
|
|
@@ -9300,8 +10251,22 @@ async function runProviderDetail(id) {
|
|
|
9300
10251
|
const registry = loadRegistry();
|
|
9301
10252
|
const provider = registry.providers.find((pr) => pr.id === id);
|
|
9302
10253
|
if (!provider) return "back";
|
|
9303
|
-
const
|
|
9304
|
-
|
|
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);
|
|
9305
10270
|
printProviderDetailPanel(provider.name, modelCount, authLabel);
|
|
9306
10271
|
const detailOptions = [];
|
|
9307
10272
|
if (modelCount > 0) {
|
|
@@ -9316,11 +10281,29 @@ async function runProviderDetail(id) {
|
|
|
9316
10281
|
label: "Refresh model list",
|
|
9317
10282
|
hint: "Fetch latest models from the provider API"
|
|
9318
10283
|
});
|
|
10284
|
+
const accountSlots = Object.keys(provider.authAccounts ?? {}).sort();
|
|
9319
10285
|
if (supportsNativeOAuth(id) || provider.authType === "oauth") {
|
|
9320
10286
|
detailOptions.push({
|
|
9321
10287
|
value: "auth",
|
|
9322
10288
|
label: "Sign in again (OAuth)",
|
|
9323
|
-
|
|
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)
|
|
9324
10307
|
});
|
|
9325
10308
|
}
|
|
9326
10309
|
detailOptions.push(
|
|
@@ -9338,8 +10321,8 @@ async function runProviderDetail(id) {
|
|
|
9338
10321
|
});
|
|
9339
10322
|
if (p4.isCancel(action) || action === "back") return "back";
|
|
9340
10323
|
if (action === "browse") {
|
|
9341
|
-
const cachedModels =
|
|
9342
|
-
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);
|
|
9343
10326
|
const localProvider = {
|
|
9344
10327
|
id: provider.id,
|
|
9345
10328
|
name: provider.name,
|
|
@@ -9357,6 +10340,71 @@ async function runProviderDetail(id) {
|
|
|
9357
10340
|
await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(id, void 0, state));
|
|
9358
10341
|
return "back";
|
|
9359
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
|
+
}
|
|
9360
10408
|
if (action === "toggle") {
|
|
9361
10409
|
const result = toggleProviderEnabled(id);
|
|
9362
10410
|
if (result.toggled) {
|
|
@@ -9385,6 +10433,12 @@ async function runProvidersHub() {
|
|
|
9385
10433
|
const configuredIds = new Set(entries.map((entry) => entry.id));
|
|
9386
10434
|
if (listVisibleOAuthTemplates(configuredIds).length > 0) {
|
|
9387
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
|
+
});
|
|
9388
10442
|
}
|
|
9389
10443
|
if (entries.length > 0) {
|
|
9390
10444
|
options.push({ value: "refresh-all", label: "\u21BA Refresh all models", hint: "Update model lists for all providers" });
|
|
@@ -9409,6 +10463,23 @@ async function runProvidersHub() {
|
|
|
9409
10463
|
await runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState("openai", void 0, state));
|
|
9410
10464
|
continue;
|
|
9411
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
|
+
}
|
|
9412
10483
|
if (typeof choice === "string" && choice.startsWith("provider:")) {
|
|
9413
10484
|
const id = choice.slice("provider:".length);
|
|
9414
10485
|
const outcome = await runProviderDetail(id);
|
|
@@ -9443,7 +10514,7 @@ async function runProvidersCommand(args) {
|
|
|
9443
10514
|
console.log(providerAuthHelpText());
|
|
9444
10515
|
return 0;
|
|
9445
10516
|
}
|
|
9446
|
-
return runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(parsed.removeId, parsed.authMethod, state));
|
|
10517
|
+
return runWithCredentialCleanup((state) => runProvidersAuthWithCleanupState(parsed.removeId, parsed.authMethod, state, parsed.authAccount));
|
|
9447
10518
|
}
|
|
9448
10519
|
relayIntro("Your OpenAI providers");
|
|
9449
10520
|
return runProvidersHub();
|
|
@@ -9484,7 +10555,7 @@ async function runFirstRunWizard(_trace = false) {
|
|
|
9484
10555
|
|
|
9485
10556
|
// src/proxy.ts
|
|
9486
10557
|
import { createServer } from "http";
|
|
9487
|
-
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";
|
|
9488
10559
|
|
|
9489
10560
|
// src/http-utils.ts
|
|
9490
10561
|
import * as zlib from "zlib";
|
|
@@ -10133,10 +11204,10 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
10133
11204
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream returned empty response body" } }));
|
|
10134
11205
|
return;
|
|
10135
11206
|
}
|
|
10136
|
-
let
|
|
11207
|
+
let text5 = await upstreamRes.text();
|
|
10137
11208
|
let parsed;
|
|
10138
11209
|
try {
|
|
10139
|
-
parsed = JSON.parse(
|
|
11210
|
+
parsed = JSON.parse(text5);
|
|
10140
11211
|
} catch {
|
|
10141
11212
|
res.writeHead(502, { "Content-Type": "application/json" });
|
|
10142
11213
|
res.end(JSON.stringify({ type: "error", error: { type: "api_error", message: "Upstream response was not valid JSON" } }));
|
|
@@ -10144,13 +11215,13 @@ async function relayAnthropicMessages(res, messagesUrl, body, apiKey, clientWant
|
|
|
10144
11215
|
}
|
|
10145
11216
|
if (options.responseModelOverride && parsed && typeof parsed === "object" && !Array.isArray(parsed) && typeof parsed.model === "string") {
|
|
10146
11217
|
parsed.model = options.responseModelOverride;
|
|
10147
|
-
|
|
11218
|
+
text5 = JSON.stringify(parsed);
|
|
10148
11219
|
}
|
|
10149
11220
|
res.writeHead(200, {
|
|
10150
11221
|
"Content-Type": "application/json",
|
|
10151
|
-
"Content-Length": Buffer.byteLength(
|
|
11222
|
+
"Content-Length": Buffer.byteLength(text5).toString()
|
|
10152
11223
|
});
|
|
10153
|
-
res.end(
|
|
11224
|
+
res.end(text5);
|
|
10154
11225
|
}
|
|
10155
11226
|
|
|
10156
11227
|
// src/proxy.ts
|
|
@@ -10306,7 +11377,7 @@ function reportOnce(raw, message, warn) {
|
|
|
10306
11377
|
} catch {
|
|
10307
11378
|
}
|
|
10308
11379
|
}
|
|
10309
|
-
function upstreamMaxRetries(env = process.env, warn = (message) =>
|
|
11380
|
+
function upstreamMaxRetries(env = process.env, warn = (message) => emitParentNotice(`clodex: ${message}`)) {
|
|
10310
11381
|
const raw = env[UPSTREAM_MAX_RETRIES_ENV]?.trim();
|
|
10311
11382
|
if (raw === void 0 || raw === "") return void 0;
|
|
10312
11383
|
const value = Number(raw);
|
|
@@ -10375,10 +11446,10 @@ function supportsOpenAiPromptCacheBreakpoints(modelId) {
|
|
|
10375
11446
|
const minor = Number(match[2] ?? 0);
|
|
10376
11447
|
return major > 5 || major === 5 && minor >= 6;
|
|
10377
11448
|
}
|
|
10378
|
-
function stripClaudeCodeBillingHeader(
|
|
10379
|
-
if (!
|
|
10380
|
-
const newline =
|
|
10381
|
-
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);
|
|
10382
11453
|
}
|
|
10383
11454
|
function systemToString(system, stripAnthropicBillingHeader = false) {
|
|
10384
11455
|
if (!system) return void 0;
|
|
@@ -10387,8 +11458,8 @@ function systemToString(system, stripAnthropicBillingHeader = false) {
|
|
|
10387
11458
|
}
|
|
10388
11459
|
const blocks = system.map((b) => typeof b === "string" ? b : b.text ?? "");
|
|
10389
11460
|
if (!stripAnthropicBillingHeader) return blocks.join("\n");
|
|
10390
|
-
return blocks.flatMap((
|
|
10391
|
-
const stripped = stripClaudeCodeBillingHeader(
|
|
11461
|
+
return blocks.flatMap((text5) => {
|
|
11462
|
+
const stripped = stripClaudeCodeBillingHeader(text5);
|
|
10392
11463
|
return stripped === void 0 ? [] : [stripped];
|
|
10393
11464
|
}).join("\n");
|
|
10394
11465
|
}
|
|
@@ -10404,12 +11475,12 @@ function translateTopLevelSystemForOpenAi(system) {
|
|
|
10404
11475
|
}
|
|
10405
11476
|
return system.flatMap((block) => {
|
|
10406
11477
|
const raw = typeof block === "string" ? block : block.text ?? "";
|
|
10407
|
-
const
|
|
10408
|
-
if (!
|
|
11478
|
+
const text5 = stripClaudeCodeBillingHeader(raw) ?? "";
|
|
11479
|
+
if (!text5.trim()) return [];
|
|
10409
11480
|
const cacheControl = typeof block === "string" ? void 0 : block.cache_control;
|
|
10410
11481
|
return [{
|
|
10411
11482
|
role: "system",
|
|
10412
|
-
content:
|
|
11483
|
+
content: text5,
|
|
10413
11484
|
...cacheControl ? { providerOptions: { openai: { promptCacheBreakpoint: { mode: "explicit" } } } } : {}
|
|
10414
11485
|
}];
|
|
10415
11486
|
});
|
|
@@ -10466,9 +11537,9 @@ function annotateToolNames(messages) {
|
|
|
10466
11537
|
}
|
|
10467
11538
|
}
|
|
10468
11539
|
function thinkingToSdkPart(block, npm) {
|
|
10469
|
-
const
|
|
10470
|
-
if (npm === "@ai-sdk/openai" && !block.signature && !
|
|
10471
|
-
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 };
|
|
10472
11543
|
if (block.signature) {
|
|
10473
11544
|
if (npm === "@ai-sdk/google") {
|
|
10474
11545
|
part.providerOptions = { google: { thoughtSignature: block.signature } };
|
|
@@ -10584,8 +11655,8 @@ function isClaudeCodeStructuredOutputCompactRequest(body) {
|
|
|
10584
11655
|
if (!body.tools?.some((candidate) => candidate.name === "StructuredOutput")) return false;
|
|
10585
11656
|
const finalMessage = body.messages.at(-1);
|
|
10586
11657
|
if (!finalMessage || finalMessage.role !== "user") return false;
|
|
10587
|
-
const
|
|
10588
|
-
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);
|
|
10589
11660
|
}
|
|
10590
11661
|
function translateRequest(body, npm, options) {
|
|
10591
11662
|
const messages = body.messages ?? [];
|
|
@@ -10614,10 +11685,12 @@ function translateRequest(body, npm, options) {
|
|
|
10614
11685
|
const supportsExplicitOpenAiCaching = !options?.openAiOAuth && supportsOpenAiPromptCacheBreakpoints(upstreamModelId2);
|
|
10615
11686
|
if (npm === "@ai-sdk/openai") {
|
|
10616
11687
|
const claudeSessionId = extractClaudeSessionId(body, options?.claudeSessionId);
|
|
11688
|
+
const serviceTier = options?.openAiOAuth ? oauthServiceTier() : void 0;
|
|
10617
11689
|
providerOptions = deepMergeProviderOptions(providerOptions, {
|
|
10618
11690
|
openai: {
|
|
10619
11691
|
promptCacheKey: claudeSessionId ? claudeSessionPromptCacheKey(claudeSessionId) : openAiPromptCacheKey(baseSystem, upstreamTools),
|
|
10620
|
-
...supportsExplicitOpenAiCaching ? { promptCacheOptions: { mode: "implicit", ttl: "30m" } } : {}
|
|
11692
|
+
...supportsExplicitOpenAiCaching ? { promptCacheOptions: { mode: "implicit", ttl: "30m" } } : {},
|
|
11693
|
+
...serviceTier ? { serviceTier } : {}
|
|
10621
11694
|
}
|
|
10622
11695
|
});
|
|
10623
11696
|
}
|
|
@@ -10635,6 +11708,35 @@ function translateRequest(body, npm, options) {
|
|
|
10635
11708
|
providerOptions
|
|
10636
11709
|
};
|
|
10637
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
|
+
}
|
|
10638
11740
|
function toAnthropicUsage(u) {
|
|
10639
11741
|
const total = u?.inputTokens ?? 0;
|
|
10640
11742
|
const cacheRead = u?.inputTokenDetails?.cacheReadTokens ?? u?.cachedInputTokens ?? 0;
|
|
@@ -10884,7 +11986,8 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10884
11986
|
maxRetries: upstreamMaxRetries(),
|
|
10885
11987
|
abortSignal,
|
|
10886
11988
|
onError: () => {
|
|
10887
|
-
}
|
|
11989
|
+
},
|
|
11990
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
10888
11991
|
});
|
|
10889
11992
|
const watchedStream = (async function* () {
|
|
10890
11993
|
try {
|
|
@@ -10910,10 +12013,11 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
|
|
|
10910
12013
|
}
|
|
10911
12014
|
}
|
|
10912
12015
|
async function generateAnthropicResponse(model, params, modelId, options) {
|
|
10913
|
-
let
|
|
12016
|
+
let text5;
|
|
10914
12017
|
let toolCalls;
|
|
10915
12018
|
let finishReason;
|
|
10916
12019
|
let usage;
|
|
12020
|
+
let warnings;
|
|
10917
12021
|
if (options?.forceStream) {
|
|
10918
12022
|
const forceAbort = new AbortController();
|
|
10919
12023
|
const stopForwardingAbort = forwardAbortSignal(options.abortSignal, forceAbort);
|
|
@@ -10933,7 +12037,8 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10933
12037
|
maxRetries: upstreamMaxRetries(),
|
|
10934
12038
|
abortSignal,
|
|
10935
12039
|
onError: () => {
|
|
10936
|
-
}
|
|
12040
|
+
},
|
|
12041
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
10937
12042
|
});
|
|
10938
12043
|
const streamedText = [];
|
|
10939
12044
|
const streamedToolCalls = [];
|
|
@@ -10972,7 +12077,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10972
12077
|
clearTimeout(totalTimer);
|
|
10973
12078
|
if (!forceAbort.signal.aborted) forceAbort.abort();
|
|
10974
12079
|
}
|
|
10975
|
-
|
|
12080
|
+
text5 = streamedText.join("");
|
|
10976
12081
|
toolCalls = streamedToolCalls;
|
|
10977
12082
|
finishReason = streamedFinishReason;
|
|
10978
12083
|
usage = streamedUsage;
|
|
@@ -10990,13 +12095,14 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
10990
12095
|
maxRetries: upstreamMaxRetries(),
|
|
10991
12096
|
abortSignal: generateAbort.signal
|
|
10992
12097
|
});
|
|
10993
|
-
({ text:
|
|
12098
|
+
({ text: text5, toolCalls, finishReason, usage, warnings } = r);
|
|
10994
12099
|
} finally {
|
|
10995
12100
|
stopForwardingAbort();
|
|
10996
12101
|
clearTimeout(totalTimer);
|
|
10997
12102
|
if (!generateAbort.signal.aborted) generateAbort.abort();
|
|
10998
12103
|
}
|
|
10999
12104
|
}
|
|
12105
|
+
reportUnsupportedServiceTier(params, warnings);
|
|
11000
12106
|
const requiredProps = toolRequiredProps(params.tools);
|
|
11001
12107
|
return {
|
|
11002
12108
|
id: "msg_" + Date.now(),
|
|
@@ -11004,7 +12110,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
|
|
|
11004
12110
|
role: "assistant",
|
|
11005
12111
|
model: modelId,
|
|
11006
12112
|
content: [
|
|
11007
|
-
...
|
|
12113
|
+
...text5 ? [{ type: "text", text: text5 }] : [],
|
|
11008
12114
|
...toolCalls.map((tc) => ({
|
|
11009
12115
|
type: "tool_use",
|
|
11010
12116
|
id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
|
|
@@ -11165,14 +12271,14 @@ function appendSecureLog(logPath, line) {
|
|
|
11165
12271
|
try {
|
|
11166
12272
|
const fd = openSync3(logPath, "a", 384);
|
|
11167
12273
|
try {
|
|
11168
|
-
|
|
12274
|
+
writeSync2(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
|
|
11169
12275
|
`);
|
|
11170
12276
|
} finally {
|
|
11171
12277
|
closeSync3(fd);
|
|
11172
12278
|
}
|
|
11173
12279
|
} catch {
|
|
11174
12280
|
try {
|
|
11175
|
-
|
|
12281
|
+
appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
|
|
11176
12282
|
`);
|
|
11177
12283
|
} catch {
|
|
11178
12284
|
}
|
|
@@ -11338,8 +12444,9 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11338
12444
|
}
|
|
11339
12445
|
const upstreamUrl = route.upstreamUrl;
|
|
11340
12446
|
const routeAuthType = route.authType ?? "api";
|
|
12447
|
+
const loggedTier = isOpenAiOAuthRoute(route) ? oauthServiceTier() : void 0;
|
|
11341
12448
|
plog(
|
|
11342
|
-
() => `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}` : "")
|
|
11343
12450
|
);
|
|
11344
12451
|
const usesSdkAdapter = isSdkMigratedNpm(route.npm);
|
|
11345
12452
|
if (messagesEndpoint === "count_tokens") {
|
|
@@ -11428,7 +12535,7 @@ async function startProxyCatalog(routes, defaultAliasId, debug = false, inferenc
|
|
|
11428
12535
|
return;
|
|
11429
12536
|
}
|
|
11430
12537
|
if (usesSdkAdapter) {
|
|
11431
|
-
const openAiOAuth = route
|
|
12538
|
+
const openAiOAuth = isOpenAiOAuthRoute(route);
|
|
11432
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"];
|
|
11433
12540
|
const claudeSessionId = extractClaudeSessionId(anthropicBody, claudeSessionIdHeader);
|
|
11434
12541
|
const translationLifecycle = createTranslationLifecycle(
|
|
@@ -11685,7 +12792,7 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
|
|
|
11685
12792
|
|
|
11686
12793
|
// src/server/index.ts
|
|
11687
12794
|
import pc10 from "picocolors";
|
|
11688
|
-
import { networkInterfaces } from "os";
|
|
12795
|
+
import { networkInterfaces as networkInterfaces2 } from "os";
|
|
11689
12796
|
import * as p9 from "@clack/prompts";
|
|
11690
12797
|
|
|
11691
12798
|
// src/target-compatibility.ts
|
|
@@ -11898,6 +13005,7 @@ function translateOpenAiRequest(body, options) {
|
|
|
11898
13005
|
}
|
|
11899
13006
|
if (options?.openAiOAuth) {
|
|
11900
13007
|
const instructions = system?.trim() || "You are a coding assistant.";
|
|
13008
|
+
const serviceTier = oauthServiceTier();
|
|
11901
13009
|
return {
|
|
11902
13010
|
messages,
|
|
11903
13011
|
tools,
|
|
@@ -11907,7 +13015,8 @@ function translateOpenAiRequest(body, options) {
|
|
|
11907
13015
|
openai: {
|
|
11908
13016
|
store: false,
|
|
11909
13017
|
include: ["reasoning.encrypted_content"],
|
|
11910
|
-
instructions
|
|
13018
|
+
instructions,
|
|
13019
|
+
...serviceTier ? { serviceTier } : {}
|
|
11911
13020
|
}
|
|
11912
13021
|
}
|
|
11913
13022
|
};
|
|
@@ -11954,7 +13063,8 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11954
13063
|
...params,
|
|
11955
13064
|
maxRetries: upstreamMaxRetries(),
|
|
11956
13065
|
onError: () => {
|
|
11957
|
-
}
|
|
13066
|
+
},
|
|
13067
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
11958
13068
|
});
|
|
11959
13069
|
result = await collectOpenAiStream(stream);
|
|
11960
13070
|
} else {
|
|
@@ -11964,6 +13074,7 @@ async function generateOpenAiResponse(model, params, responseModelId, options) {
|
|
|
11964
13074
|
maxRetries: upstreamMaxRetries()
|
|
11965
13075
|
});
|
|
11966
13076
|
}
|
|
13077
|
+
reportUnsupportedServiceTier(params, result.warnings);
|
|
11967
13078
|
const message = { role: "assistant", content: result.text || null };
|
|
11968
13079
|
if (result.toolCalls?.length) {
|
|
11969
13080
|
message.tool_calls = result.toolCalls.map((tc) => ({
|
|
@@ -11989,7 +13100,8 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
|
|
|
11989
13100
|
const { stream } = streamText2({
|
|
11990
13101
|
model,
|
|
11991
13102
|
...params,
|
|
11992
|
-
maxRetries: upstreamMaxRetries()
|
|
13103
|
+
maxRetries: upstreamMaxRetries(),
|
|
13104
|
+
onStepFinish: (step) => reportUnsupportedServiceTier(params, step.warnings)
|
|
11993
13105
|
});
|
|
11994
13106
|
const baseData = {
|
|
11995
13107
|
id: `chatcmpl-${Date.now()}`,
|
|
@@ -12267,6 +13379,9 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
12267
13379
|
modelId: body.model,
|
|
12268
13380
|
effort: anthropicEffortFromRequest(body) ?? model.defaultEffort,
|
|
12269
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,
|
|
12270
13385
|
provider: inferenceProvider(model),
|
|
12271
13386
|
route: "translated",
|
|
12272
13387
|
requestPreview: getLatestMessagePreview(body.messages, body.system)
|
|
@@ -12276,7 +13391,7 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
|
|
|
12276
13391
|
if (npmMaxTools !== void 0 && toolCount > npmMaxTools) {
|
|
12277
13392
|
plog(`tools truncated: ${toolCount} \u2192 ${npmMaxTools} (provider limit)`);
|
|
12278
13393
|
}
|
|
12279
|
-
const openAiOAuth = model
|
|
13394
|
+
const openAiOAuth = isOpenAiOAuthRoute(model);
|
|
12280
13395
|
const params = translateRequest(body, model.npm, {
|
|
12281
13396
|
defaultEffort: anthropicEffortFromRequest(body) ? void 0 : model.defaultEffort,
|
|
12282
13397
|
openAiOAuth,
|
|
@@ -12463,12 +13578,13 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
|
|
|
12463
13578
|
auditInference(options, {
|
|
12464
13579
|
modelId: body.model,
|
|
12465
13580
|
effort: openAiEffort(body),
|
|
13581
|
+
serviceTier: isOpenAiOAuthRoute(model) ? oauthServiceTier() : void 0,
|
|
12466
13582
|
provider: inferenceProvider(model),
|
|
12467
13583
|
route: "translated",
|
|
12468
13584
|
requestPreview: getLatestMessagePreview(body.messages, body.system)
|
|
12469
13585
|
});
|
|
12470
13586
|
const baseURL = model.modelFormat === "anthropic" ? model.baseUrl : model.apiBaseUrl;
|
|
12471
|
-
const openAiOAuth =
|
|
13587
|
+
const openAiOAuth = isOpenAiOAuthRoute(model);
|
|
12472
13588
|
const params = translateOpenAiRequest(body, { openAiOAuth });
|
|
12473
13589
|
const clientWantsStream = Boolean(body.stream);
|
|
12474
13590
|
const responseModelId = getResponseModelId(body.model, model, options);
|
|
@@ -12987,7 +14103,7 @@ function requestHeadersWithoutProxyHeaders(req) {
|
|
|
12987
14103
|
}
|
|
12988
14104
|
return headers;
|
|
12989
14105
|
}
|
|
12990
|
-
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) {
|
|
12991
14107
|
return new Promise((resolve3) => {
|
|
12992
14108
|
const startedAt = Date.now();
|
|
12993
14109
|
let lastActivityAt = startedAt;
|
|
@@ -13047,7 +14163,8 @@ function forwardRawAnthropicRequest(req, res, rawBody, origin, rejectUnauthorize
|
|
|
13047
14163
|
path: req.url,
|
|
13048
14164
|
headers: requestHeadersWithoutProxyHeaders(req),
|
|
13049
14165
|
servername: net.isIP(origin.hostname) ? void 0 : origin.hostname,
|
|
13050
|
-
rejectUnauthorized
|
|
14166
|
+
rejectUnauthorized,
|
|
14167
|
+
agent
|
|
13051
14168
|
}, (upstreamRes) => {
|
|
13052
14169
|
headersReceived = true;
|
|
13053
14170
|
statusCode = upstreamRes.statusCode ?? 502;
|
|
@@ -13388,6 +14505,8 @@ async function startHttpProxy(options) {
|
|
|
13388
14505
|
reservedModelIds.add(normalizeRouteLookupId(modelId));
|
|
13389
14506
|
}
|
|
13390
14507
|
const anthropicOrigin = new URL2(options.anthropicOrigin ?? "https://api.anthropic.com");
|
|
14508
|
+
const anthropicProxyUrl = outboundProxyUrlForTarget(anthropicOrigin.href);
|
|
14509
|
+
let anthropicAgent;
|
|
13391
14510
|
let adapter = options.adapterHandle ?? null;
|
|
13392
14511
|
if (options.routes.length > 0) {
|
|
13393
14512
|
adapter ??= await startProxyCatalog(
|
|
@@ -13470,6 +14589,9 @@ async function startHttpProxy(options) {
|
|
|
13470
14589
|
claudeSessionId,
|
|
13471
14590
|
modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
|
|
13472
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,
|
|
13473
14595
|
provider,
|
|
13474
14596
|
route: route ? "translated" : "passthrough",
|
|
13475
14597
|
stream: Boolean(parsed?.stream),
|
|
@@ -13513,6 +14635,7 @@ async function startHttpProxy(options) {
|
|
|
13513
14635
|
rawBody,
|
|
13514
14636
|
anthropicOrigin,
|
|
13515
14637
|
options.anthropicRejectUnauthorized ?? true,
|
|
14638
|
+
anthropicAgent,
|
|
13516
14639
|
messagesEndpoint === "messages" && options.inferenceLogPath ? (statusCode, errorContent) => writeInferenceResponseErrorLog(options.inferenceLogPath, {
|
|
13517
14640
|
requestId,
|
|
13518
14641
|
modelId: typeof parsed?.model === "string" ? parsed.model : "unknown",
|
|
@@ -13547,7 +14670,8 @@ async function startHttpProxy(options) {
|
|
|
13547
14670
|
res,
|
|
13548
14671
|
rawBody,
|
|
13549
14672
|
anthropicOrigin,
|
|
13550
|
-
options.anthropicRejectUnauthorized ?? true
|
|
14673
|
+
options.anthropicRejectUnauthorized ?? true,
|
|
14674
|
+
anthropicAgent
|
|
13551
14675
|
);
|
|
13552
14676
|
});
|
|
13553
14677
|
const sockets = /* @__PURE__ */ new Set();
|
|
@@ -13606,6 +14730,17 @@ async function startHttpProxy(options) {
|
|
|
13606
14730
|
adapter?.close();
|
|
13607
14731
|
throw err;
|
|
13608
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
|
+
}
|
|
13609
14744
|
return {
|
|
13610
14745
|
host: options.host ?? "127.0.0.1",
|
|
13611
14746
|
port: address.port,
|
|
@@ -13621,6 +14756,7 @@ async function startHttpProxy(options) {
|
|
|
13621
14756
|
for (const socket of sockets) socket.destroy();
|
|
13622
14757
|
await new Promise((resolve3) => proxyServer.close(() => resolve3()));
|
|
13623
14758
|
mitmServer.close();
|
|
14759
|
+
anthropicAgent?.destroy();
|
|
13624
14760
|
adapter?.close();
|
|
13625
14761
|
}
|
|
13626
14762
|
};
|
|
@@ -13826,7 +14962,7 @@ async function runHttpProxyServerCommand(debug = false, webSocketDiagnostics = f
|
|
|
13826
14962
|
|
|
13827
14963
|
// src/server/index.ts
|
|
13828
14964
|
function getLocalIps() {
|
|
13829
|
-
const ifaces =
|
|
14965
|
+
const ifaces = networkInterfaces2();
|
|
13830
14966
|
const result = [];
|
|
13831
14967
|
for (const [name, iface] of Object.entries(ifaces)) {
|
|
13832
14968
|
for (const addr of iface ?? []) {
|
|
@@ -14839,6 +15975,10 @@ function captureBuiltInPatchProofs(source, config, results) {
|
|
|
14839
15975
|
"PATCH 9: default effort",
|
|
14840
15976
|
/\/\*ccpatch:default-effort\*\/var _cce=Object\.assign\(Object\.create\(null\),\{[^{}]*\}\)\[String\([\w$]+\|\|""\)\.trim\(\)\.toLowerCase\(\)\];if\(_cce!==void 0\)return _cce;/
|
|
14841
15977
|
);
|
|
15978
|
+
addPattern(
|
|
15979
|
+
"PATCH 10: child network environment",
|
|
15980
|
+
/\/\*ccpatch:child-network-env\*\/let _clodexChildEnv=process\.env,[\s\S]*?catch\(_clodexError\)\{\}\}/
|
|
15981
|
+
);
|
|
14842
15982
|
return proofs;
|
|
14843
15983
|
}
|
|
14844
15984
|
function builtInPatchProofsChanged(source, proofs) {
|
|
@@ -15001,7 +16141,7 @@ function collectPristineFacts(args) {
|
|
|
15001
16141
|
}
|
|
15002
16142
|
|
|
15003
16143
|
// src/patch-transforms.ts
|
|
15004
|
-
var PATCH_TRANSFORMS_VERSION =
|
|
16144
|
+
var PATCH_TRANSFORMS_VERSION = 5;
|
|
15005
16145
|
var NATIVE_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
15006
16146
|
var BASE_EFFORT_LEVELS = ["low", "medium", "high"];
|
|
15007
16147
|
function projectNativeEffort(effort) {
|
|
@@ -15296,6 +16436,36 @@ function applyClodexPatches(source, config) {
|
|
|
15296
16436
|
);
|
|
15297
16437
|
}
|
|
15298
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
|
+
}
|
|
15299
16469
|
return { content: js, results: report };
|
|
15300
16470
|
}
|
|
15301
16471
|
|
|
@@ -15889,7 +17059,7 @@ async function runLaunchPatchCheck(opts = {}) {
|
|
|
15889
17059
|
}
|
|
15890
17060
|
|
|
15891
17061
|
// src/cli.ts
|
|
15892
|
-
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"]);
|
|
15893
17063
|
var CLODEX_LAUNCH_FLAGS = /* @__PURE__ */ new Set(["--provider", "--model"]);
|
|
15894
17064
|
function parseClodexLaunchFlag(arg, rest, index, parsed) {
|
|
15895
17065
|
if (arg === "--provider" || arg === "--model") {
|
|
@@ -16116,6 +17286,7 @@ function parseArgs(args) {
|
|
|
16116
17286
|
}
|
|
16117
17287
|
if (arg === "--dry-run") parsed.dryRun = true;
|
|
16118
17288
|
if (arg === "--trace") parsed.trace = true;
|
|
17289
|
+
if (arg === "--fast") parsed.fast = true;
|
|
16119
17290
|
consumeBridgeModeFlag(arg, parsed);
|
|
16120
17291
|
if (arg === "--save-mode") parsed.saveBridgeMode = true;
|
|
16121
17292
|
if (arg === "--help" || arg === "-h") parsed.showHelp = true;
|
|
@@ -16185,6 +17356,8 @@ ${pc13.bold("Options:")}
|
|
|
16185
17356
|
--save-mode With --endpoint/--proxy: save that mode as the claude default
|
|
16186
17357
|
--dry-run Run the wizard but show a preview instead of launching Claude Code
|
|
16187
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)
|
|
16188
17361
|
--provider Boot provider id (skip wizard when paired with --model or in print mode)
|
|
16189
17362
|
--model Boot model id (skip wizard when paired with --provider or in print mode)
|
|
16190
17363
|
--help Show this command help
|
|
@@ -16365,9 +17538,9 @@ ${pc13.bold("Behavior:")}
|
|
|
16365
17538
|
permissions. Local failures are reported but never block the built-ins.
|
|
16366
17539
|
Run clodex patch again after every claude update.`;
|
|
16367
17540
|
}
|
|
16368
|
-
function printHelp(
|
|
17541
|
+
function printHelp(text5) {
|
|
16369
17542
|
console.log(`
|
|
16370
|
-
${
|
|
17543
|
+
${text5}
|
|
16371
17544
|
`);
|
|
16372
17545
|
}
|
|
16373
17546
|
function reportInactiveCatalogAliases(modelAliases) {
|
|
@@ -16765,6 +17938,7 @@ async function runClaudeHttpProxyCommand(parsed, claudeArgs, agentStdout) {
|
|
|
16765
17938
|
}
|
|
16766
17939
|
async function runClaudeCommand(parsed) {
|
|
16767
17940
|
const { dryRun, trace, launchProvider, launchModel } = parsed;
|
|
17941
|
+
if (parsed.fast) process.env.CLODEX_SERVICE_TIER = "fast";
|
|
16768
17942
|
const claudeArgs = normalizeClaudeAgentArgs(parsed.claudeArgs);
|
|
16769
17943
|
const agentStdout = wantsCleanAgentStdout("claude", claudeArgs);
|
|
16770
17944
|
setAgentStdoutMode(agentStdout);
|
|
@@ -16834,7 +18008,12 @@ Error: ${launchPlan.error}
|
|
|
16834
18008
|
catalogSpinner.stop("");
|
|
16835
18009
|
}
|
|
16836
18010
|
const allProviders = providersForTarget(providersForPicker(catalog), "claude");
|
|
18011
|
+
const blockedLaunchReason = launchPlan.skip && launchPlan.target?.providerId ? catalog.blockedProviders.get(launchPlan.target.providerId) : void 0;
|
|
16837
18012
|
if (allProviders.length === 0) {
|
|
18013
|
+
if (blockedLaunchReason) {
|
|
18014
|
+
p12.log.error(blockedLaunchReason);
|
|
18015
|
+
return 1;
|
|
18016
|
+
}
|
|
16838
18017
|
p12.log.warn("No providers available.");
|
|
16839
18018
|
p12.log.info(pc13.dim("Run clodex providers to get started."));
|
|
16840
18019
|
return 0;
|
|
@@ -16854,7 +18033,7 @@ Error: ${launchPlan.error}
|
|
|
16854
18033
|
const resolved = findProviderAndModel(allProviders, launchPlan.target);
|
|
16855
18034
|
if (!resolved) {
|
|
16856
18035
|
p12.log.error(
|
|
16857
|
-
`Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
|
|
18036
|
+
blockedLaunchReason ?? `Provider/model not found: ${launchPlan.target.providerId} / ${launchPlan.target.modelId}`
|
|
16858
18037
|
);
|
|
16859
18038
|
return 1;
|
|
16860
18039
|
}
|