@yansigit/opencodex 2.36.1-dev.20260829.47 → 2.36.1-dev.20260829.48
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/gui/dist/assets/{ApiKeys-DAuYQsGJ.js → ApiKeys-LOOFiZfv.js} +1 -1
- package/gui/dist/assets/{Claude-bN_80RT8.js → Claude-BwKzpXe3.js} +1 -1
- package/gui/dist/assets/{CodexSet-D3dWX_ay.js → CodexSet-B5qi9KeE.js} +1 -1
- package/gui/dist/assets/{FileIntegrationPage-BGyzkPs2.js → FileIntegrationPage-B0sUef6W.js} +1 -1
- package/gui/dist/assets/{Grok-CuaKd1TT.js → Grok-bsb4n-f4.js} +1 -1
- package/gui/dist/assets/{Integrations-C7NGQoxR.js → Integrations-CSMwFDvM.js} +2 -2
- package/gui/dist/assets/{IntegrationsOverview-DjVorYO5.js → IntegrationsOverview-CTORdKJA.js} +1 -1
- package/gui/dist/assets/{Logs-B21a-9xe.js → Logs-BWYbfELf.js} +1 -1
- package/gui/dist/assets/{Models-CN7Q3kuK.js → Models-CexEtdT1.js} +1 -1
- package/gui/dist/assets/{NumberStepper-BWg90tEs.js → NumberStepper-BapSFQnW.js} +1 -1
- package/gui/dist/assets/{Providers-CP_i-7LS.js → Providers-BwUFeAgA.js} +1 -1
- package/gui/dist/assets/{RestoreDialog-3M0NqCiD.js → RestoreDialog-CD8piq90.js} +1 -1
- package/gui/dist/assets/{Startup-BVPFoP2s.js → Startup-BgX731C2.js} +1 -1
- package/gui/dist/assets/{Storage-BdsG9ypz.js → Storage-Bgq7HphP.js} +1 -1
- package/gui/dist/assets/{Subagents-CguR_bGQ.js → Subagents-ChxItbeF.js} +1 -1
- package/gui/dist/assets/{Usage-C0kKi9_H.js → Usage-BkylzP50.js} +1 -1
- package/gui/dist/assets/{data-surface-BCycbObp.js → data-surface-pCXChiBf.js} +1 -1
- package/gui/dist/assets/{index-CU1jE0st.js → index-CNopOid3.js} +2 -2
- package/gui/dist/assets/{model-display-D2elXkuE.js → model-display-CiUpg8T9.js} +1 -1
- package/gui/dist/assets/{provider-payload-DfkXWE7v.js → provider-payload-b2jlS-On.js} +1 -1
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/cli/config-command.ts +7 -3
- package/src/cli/init.ts +2 -2
- package/src/cli/provider.ts +58 -74
- package/src/codex/convergence.ts +110 -15
- package/src/config.ts +209 -11
- package/src/generated/compatibility-version.json +24 -20
- package/src/oauth/index.ts +143 -82
- package/src/oauth/login-cli.ts +53 -11
- package/src/providers/alibaba-region-startup.ts +31 -11
- package/src/providers/api-keys.ts +58 -34
- package/src/providers/key-failover.ts +59 -46
- package/src/providers/model-rename-startup.ts +25 -7
- package/src/providers/openai-tier-startup.ts +38 -14
- package/src/server/index.ts +10 -0
- package/src/server/management/context.ts +10 -3
- package/src/server/management/logs-usage-routes.ts +19 -16
- package/src/server/management/model-routes.ts +284 -165
- package/src/server/management/oauth-account-routes.ts +93 -15
- package/src/server/management/provider-routes.ts +200 -87
- package/src/storage/policy-input.ts +166 -0
- package/src/storage/policy.ts +59 -211
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"path": "package.json",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "c223c9a248049f4e19e7dee2837311746d85ba6e2b79498d6b1a3b07a2201c03"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"path": "scripts/model-metadata.source.json",
|
|
@@ -526,7 +526,7 @@
|
|
|
526
526
|
},
|
|
527
527
|
{
|
|
528
528
|
"path": "src/cli/config-command.ts",
|
|
529
|
-
"sha256": "
|
|
529
|
+
"sha256": "1fbca0cbbdfbdab499c354f4e76d063b709ee7da99ad2334061bea580506a04c"
|
|
530
530
|
},
|
|
531
531
|
{
|
|
532
532
|
"path": "src/cli/debug.ts",
|
|
@@ -558,7 +558,7 @@
|
|
|
558
558
|
},
|
|
559
559
|
{
|
|
560
560
|
"path": "src/cli/init.ts",
|
|
561
|
-
"sha256": "
|
|
561
|
+
"sha256": "ced149008c298c1167f8e710055ec5a293522e81dcc9e3bba2c8049dd32d3ec5"
|
|
562
562
|
},
|
|
563
563
|
{
|
|
564
564
|
"path": "src/cli/integrations.ts",
|
|
@@ -606,7 +606,7 @@
|
|
|
606
606
|
},
|
|
607
607
|
{
|
|
608
608
|
"path": "src/cli/provider.ts",
|
|
609
|
-
"sha256": "
|
|
609
|
+
"sha256": "51f3110937eb259c93e5e04d4a1cdb07b0d546be8c8adcc9c1a410f728265d2c"
|
|
610
610
|
},
|
|
611
611
|
{
|
|
612
612
|
"path": "src/cli/ready.ts",
|
|
@@ -826,7 +826,7 @@
|
|
|
826
826
|
},
|
|
827
827
|
{
|
|
828
828
|
"path": "src/codex/convergence.ts",
|
|
829
|
-
"sha256": "
|
|
829
|
+
"sha256": "f6ae4b0bdacfc59e9227d8bf3b5f118d9264abb1ec8224c8dc1203bcfacadf8e"
|
|
830
830
|
},
|
|
831
831
|
{
|
|
832
832
|
"path": "src/codex/coordinator-doctor.ts",
|
|
@@ -1170,7 +1170,7 @@
|
|
|
1170
1170
|
},
|
|
1171
1171
|
{
|
|
1172
1172
|
"path": "src/config.ts",
|
|
1173
|
-
"sha256": "
|
|
1173
|
+
"sha256": "ab945b93888473c8165ca9cfe43b5ddca53c01d6de26b5c76b89903471aa7e24"
|
|
1174
1174
|
},
|
|
1175
1175
|
{
|
|
1176
1176
|
"path": "src/config/atomic-write.ts",
|
|
@@ -2178,7 +2178,7 @@
|
|
|
2178
2178
|
},
|
|
2179
2179
|
{
|
|
2180
2180
|
"path": "src/oauth/index.ts",
|
|
2181
|
-
"sha256": "
|
|
2181
|
+
"sha256": "608e24a1883561d5638e21aa0cc66530c512e37afbf2917c9510c5ee19fac6b3"
|
|
2182
2182
|
},
|
|
2183
2183
|
{
|
|
2184
2184
|
"path": "src/oauth/key-providers.ts",
|
|
@@ -2206,7 +2206,7 @@
|
|
|
2206
2206
|
},
|
|
2207
2207
|
{
|
|
2208
2208
|
"path": "src/oauth/login-cli.ts",
|
|
2209
|
-
"sha256": "
|
|
2209
|
+
"sha256": "331024c25a8e2df86879dfa465b905bc35c5157b21f1da711c82618cb9f5e4c5"
|
|
2210
2210
|
},
|
|
2211
2211
|
{
|
|
2212
2212
|
"path": "src/oauth/nous.ts",
|
|
@@ -2246,7 +2246,7 @@
|
|
|
2246
2246
|
},
|
|
2247
2247
|
{
|
|
2248
2248
|
"path": "src/providers/alibaba-region-startup.ts",
|
|
2249
|
-
"sha256": "
|
|
2249
|
+
"sha256": "0915c6e813c955ddefbd610891a4d91e546f904aaf7777514563a66bff0b5cbe"
|
|
2250
2250
|
},
|
|
2251
2251
|
{
|
|
2252
2252
|
"path": "src/providers/antigravity-models.ts",
|
|
@@ -2258,7 +2258,7 @@
|
|
|
2258
2258
|
},
|
|
2259
2259
|
{
|
|
2260
2260
|
"path": "src/providers/api-keys.ts",
|
|
2261
|
-
"sha256": "
|
|
2261
|
+
"sha256": "36e2b59bf5b61ef601426d507c09acd9df09854405c8c4dfda0afb9829709be8"
|
|
2262
2262
|
},
|
|
2263
2263
|
{
|
|
2264
2264
|
"path": "src/providers/auto-compact-budget.ts",
|
|
@@ -2310,7 +2310,7 @@
|
|
|
2310
2310
|
},
|
|
2311
2311
|
{
|
|
2312
2312
|
"path": "src/providers/key-failover.ts",
|
|
2313
|
-
"sha256": "
|
|
2313
|
+
"sha256": "60bb2974bdda5825ad54974617c688139fceaa1c9d9b0d107fc06f8ba30ccc46"
|
|
2314
2314
|
},
|
|
2315
2315
|
{
|
|
2316
2316
|
"path": "src/providers/kiro-models.ts",
|
|
@@ -2338,7 +2338,7 @@
|
|
|
2338
2338
|
},
|
|
2339
2339
|
{
|
|
2340
2340
|
"path": "src/providers/model-rename-startup.ts",
|
|
2341
|
-
"sha256": "
|
|
2341
|
+
"sha256": "6a98ec00772e7b45c056d7c4161a454cceeaa9780de58ca5e89b674c6cc58ca5"
|
|
2342
2342
|
},
|
|
2343
2343
|
{
|
|
2344
2344
|
"path": "src/providers/new-model-policy.ts",
|
|
@@ -2350,7 +2350,7 @@
|
|
|
2350
2350
|
},
|
|
2351
2351
|
{
|
|
2352
2352
|
"path": "src/providers/openai-tier-startup.ts",
|
|
2353
|
-
"sha256": "
|
|
2353
|
+
"sha256": "cb2631ad03a7727d34e7df97137848514b114790bb94276d71729efe9d605815"
|
|
2354
2354
|
},
|
|
2355
2355
|
{
|
|
2356
2356
|
"path": "src/providers/openai-tiers.ts",
|
|
@@ -2686,7 +2686,7 @@
|
|
|
2686
2686
|
},
|
|
2687
2687
|
{
|
|
2688
2688
|
"path": "src/server/index.ts",
|
|
2689
|
-
"sha256": "
|
|
2689
|
+
"sha256": "748a821368960a58228937a15582cb05a087bcc438849d367dc74c05eb5dbf6c"
|
|
2690
2690
|
},
|
|
2691
2691
|
{
|
|
2692
2692
|
"path": "src/server/lifecycle.ts",
|
|
@@ -2742,7 +2742,7 @@
|
|
|
2742
2742
|
},
|
|
2743
2743
|
{
|
|
2744
2744
|
"path": "src/server/management/context.ts",
|
|
2745
|
-
"sha256": "
|
|
2745
|
+
"sha256": "13dfe2bb83ddc157d53b5e5e9340a677d9a80fbf0209f63d8598a8e9da44f269"
|
|
2746
2746
|
},
|
|
2747
2747
|
{
|
|
2748
2748
|
"path": "src/server/management/integration-routes.ts",
|
|
@@ -2758,11 +2758,11 @@
|
|
|
2758
2758
|
},
|
|
2759
2759
|
{
|
|
2760
2760
|
"path": "src/server/management/logs-usage-routes.ts",
|
|
2761
|
-
"sha256": "
|
|
2761
|
+
"sha256": "77bd7f6ffef94bea9b11a5c0bdefb30cce9d6c69a4bea0281db7e71ad1a48cbc"
|
|
2762
2762
|
},
|
|
2763
2763
|
{
|
|
2764
2764
|
"path": "src/server/management/model-routes.ts",
|
|
2765
|
-
"sha256": "
|
|
2765
|
+
"sha256": "5651f71ad65fcc46d74176aa6119393ef074502e1b6c6d29a235c294850cd594"
|
|
2766
2766
|
},
|
|
2767
2767
|
{
|
|
2768
2768
|
"path": "src/server/management/model-rows.ts",
|
|
@@ -2774,7 +2774,7 @@
|
|
|
2774
2774
|
},
|
|
2775
2775
|
{
|
|
2776
2776
|
"path": "src/server/management/oauth-account-routes.ts",
|
|
2777
|
-
"sha256": "
|
|
2777
|
+
"sha256": "29050746283e3fd553e0de399fda85cbc9b0760e25e0dc3ba8e6fdbc4b33c7f6"
|
|
2778
2778
|
},
|
|
2779
2779
|
{
|
|
2780
2780
|
"path": "src/server/management/provider-capability-config.ts",
|
|
@@ -2782,7 +2782,7 @@
|
|
|
2782
2782
|
},
|
|
2783
2783
|
{
|
|
2784
2784
|
"path": "src/server/management/provider-routes.ts",
|
|
2785
|
-
"sha256": "
|
|
2785
|
+
"sha256": "7abf9c86655670bc2de1383577b85e1a4cac5e92447d2a77421820e3126a2d97"
|
|
2786
2786
|
},
|
|
2787
2787
|
{
|
|
2788
2788
|
"path": "src/server/management/replit-provider-routes.ts",
|
|
@@ -3080,6 +3080,10 @@
|
|
|
3080
3080
|
"path": "src/storage/cleanup.ts",
|
|
3081
3081
|
"sha256": "2a90e83983c7da9bdb2ac0134870dd7ba85d4469237fae798c60b34985c8e554"
|
|
3082
3082
|
},
|
|
3083
|
+
{
|
|
3084
|
+
"path": "src/storage/policy-input.ts",
|
|
3085
|
+
"sha256": "94537ac1a988156404ab598c644ed86f2ffac3b286ad73cbf5d84f5ee5b07f35"
|
|
3086
|
+
},
|
|
3083
3087
|
{
|
|
3084
3088
|
"path": "src/storage/policy-job.ts",
|
|
3085
3089
|
"sha256": "4fa9f2297feb1a9404af6173adbf875c19f5e79f4b1aca0056744e83d503ad8a"
|
|
@@ -3094,7 +3098,7 @@
|
|
|
3094
3098
|
},
|
|
3095
3099
|
{
|
|
3096
3100
|
"path": "src/storage/policy.ts",
|
|
3097
|
-
"sha256": "
|
|
3101
|
+
"sha256": "aaefd080cf74c38596134ac4a62ef16128c090b38e9bf13eaf749fbd82862204"
|
|
3098
3102
|
},
|
|
3099
3103
|
{
|
|
3100
3104
|
"path": "src/storage/restore-job.ts",
|
package/src/oauth/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { KiroOAuthMetadata, OAuthController, OAuthCredentials } from "./types";
|
|
2
2
|
import { parseCallbackInput } from "./callback-server";
|
|
3
3
|
import type { OcxConfig, OcxProviderConfig, RefreshPolicy } from "../types";
|
|
4
|
-
import { loadConfig,
|
|
4
|
+
import { initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig, resolveEnvValue } from "../config";
|
|
5
5
|
import { maskEmail } from "../lib/privacy";
|
|
6
6
|
import { KiroTokenRefreshError, environmentKiroRoutingMetadata, loginKiro, refreshKiroToken, settleKiroLoginTransaction } from "./kiro";
|
|
7
7
|
import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredential, saveCredential, setActiveAccount, getCredential, credentialGeneration, createOAuthRefreshIntentLock, mergeAccountCredential, markAccountNeedsReauthIfGeneration, readOAuthRefreshIntent, writeOAuthRefreshIntent, markOAuthRefreshIntentStaleOwner, clearOAuthRefreshIntent, normalizeAuthStoreBuffer, OAuthMutationBusyError } from "./store";
|
|
@@ -1023,42 +1023,107 @@ function migrateLegacyAntigravityStaticCatalog(config: OcxConfig): boolean {
|
|
|
1023
1023
|
return true;
|
|
1024
1024
|
}
|
|
1025
1025
|
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1026
|
+
interface OAuthReconcileProjection {
|
|
1027
|
+
config: OcxConfig;
|
|
1028
|
+
changed: boolean;
|
|
1029
|
+
touchedProviders: string[];
|
|
1030
|
+
touchedAntigravityVersion: boolean;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function projectOAuthProviderReconciliation(config: OcxConfig): OAuthReconcileProjection {
|
|
1034
|
+
const projected = structuredClone(config);
|
|
1035
|
+
const touchedProviders = new Set<string>();
|
|
1036
|
+
const beforeAntigravity = JSON.stringify(projected.providers[GOOGLE_ANTIGRAVITY_PROVIDER]);
|
|
1037
|
+
const beforeAntigravityVersion = projected.googleAntigravityStaticCatalogVersion;
|
|
1038
|
+
let changed = migrateLegacyAntigravityStaticCatalog(projected);
|
|
1039
|
+
if (JSON.stringify(projected.providers[GOOGLE_ANTIGRAVITY_PROVIDER]) !== beforeAntigravity) {
|
|
1040
|
+
touchedProviders.add(GOOGLE_ANTIGRAVITY_PROVIDER);
|
|
1041
|
+
}
|
|
1042
|
+
const touchedAntigravityVersion = projected.googleAntigravityStaticCatalogVersion !== beforeAntigravityVersion;
|
|
1043
|
+
|
|
1044
|
+
for (const [name, prov] of Object.entries(projected.providers)) {
|
|
1045
|
+
const beforeProvider = JSON.stringify(prov);
|
|
1029
1046
|
const def = OAUTH_PROVIDERS[name];
|
|
1030
1047
|
if (name === "command-code" && isLegacyCommandCodeStaticCatalog(prov)) {
|
|
1031
1048
|
// The former experimental preset was the exact three-model seed above. It was not a user
|
|
1032
1049
|
// choice to disable discovery, so promote only that shape to the account live catalog.
|
|
1033
1050
|
prov.liveModels = true;
|
|
1034
|
-
changed = true;
|
|
1035
1051
|
}
|
|
1036
|
-
if (
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1052
|
+
if (def && prov.authMode === "oauth") {
|
|
1053
|
+
const preset = def.providerConfig;
|
|
1054
|
+
for (const field of OAUTH_RECONCILE_FIELDS) {
|
|
1055
|
+
if (JSON.stringify(prov[field]) === JSON.stringify(preset[field])) continue;
|
|
1056
|
+
if (preset[field] !== undefined) {
|
|
1057
|
+
prov[field] = cloneProviderField(preset[field]) as never;
|
|
1058
|
+
} else {
|
|
1059
|
+
delete prov[field];
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
if (prov.liveModels === undefined && preset.liveModels !== undefined) {
|
|
1063
|
+
prov.liveModels = preset.liveModels;
|
|
1064
|
+
}
|
|
1065
|
+
// Heal a defaultModel that no longer exists in the refreshed list (e.g. a deprecated snapshot).
|
|
1066
|
+
// Skip providers without a static preset `models` list: for live-discovery providers
|
|
1067
|
+
// (e.g. command-code OAuth) the account-scoped catalog is not enumerable here, so any
|
|
1068
|
+
// persisted defaultModel is a user selection and must not be overwritten by the seed.
|
|
1069
|
+
if (prov.defaultModel && preset.defaultModel && preset.models && preset.models.length > 0 && !(prov.models ?? []).includes(prov.defaultModel)) {
|
|
1070
|
+
prov.defaultModel = preset.defaultModel;
|
|
1044
1071
|
}
|
|
1045
|
-
changed = true;
|
|
1046
1072
|
}
|
|
1047
|
-
if (prov
|
|
1048
|
-
prov.liveModels = preset.liveModels;
|
|
1073
|
+
if (JSON.stringify(prov) !== beforeProvider) {
|
|
1049
1074
|
changed = true;
|
|
1075
|
+
touchedProviders.add(name);
|
|
1050
1076
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
return {
|
|
1080
|
+
config: projected,
|
|
1081
|
+
changed,
|
|
1082
|
+
touchedProviders: [...touchedProviders],
|
|
1083
|
+
touchedAntigravityVersion,
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function adoptOAuthReconciliation(config: OcxConfig, projection: OAuthReconcileProjection): void {
|
|
1088
|
+
for (const name of projection.touchedProviders) {
|
|
1089
|
+
const provider = projection.config.providers[name];
|
|
1090
|
+
if (provider) config.providers[name] = structuredClone(provider);
|
|
1091
|
+
else delete config.providers[name];
|
|
1092
|
+
}
|
|
1093
|
+
if (projection.touchedAntigravityVersion) {
|
|
1094
|
+
config.googleAntigravityStaticCatalogVersion = projection.config.googleAntigravityStaticCatalogVersion;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function withOAuthReconciliationTouchedKeys(
|
|
1099
|
+
projection: OAuthReconcileProjection,
|
|
1100
|
+
required: OAuthReconcileProjection,
|
|
1101
|
+
): OAuthReconcileProjection {
|
|
1102
|
+
return {
|
|
1103
|
+
...projection,
|
|
1104
|
+
touchedProviders: [...new Set([...projection.touchedProviders, ...required.touchedProviders])],
|
|
1105
|
+
touchedAntigravityVersion: projection.touchedAntigravityVersion || required.touchedAntigravityVersion,
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
export function reconcileOAuthProviders(config: OcxConfig, persist = true): boolean {
|
|
1110
|
+
const projection = projectOAuthProviderReconciliation(config);
|
|
1111
|
+
if (!projection.changed) return false;
|
|
1112
|
+
if (!persist) {
|
|
1113
|
+
adoptOAuthReconciliation(config, projection);
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
if (persist) {
|
|
1117
|
+
const outcome = mutatePersistedConfig(fresh => {
|
|
1118
|
+
const next = projectOAuthProviderReconciliation(fresh);
|
|
1119
|
+
if (next.changed) adoptOAuthReconciliation(fresh, next);
|
|
1120
|
+
return { changed: next.changed, value: next };
|
|
1121
|
+
});
|
|
1122
|
+
if (outcome.status !== "unavailable") {
|
|
1123
|
+
adoptOAuthReconciliation(config, withOAuthReconciliationTouchedKeys(outcome.value, projection));
|
|
1058
1124
|
}
|
|
1059
1125
|
}
|
|
1060
|
-
|
|
1061
|
-
return changed;
|
|
1126
|
+
return true;
|
|
1062
1127
|
}
|
|
1063
1128
|
|
|
1064
1129
|
/** Runtime guards: provider config is intentionally passthrough, so persisted fields may be malformed. */
|
|
@@ -1091,24 +1156,19 @@ function preservableApiKeyPool(value: unknown): NonNullable<OcxProviderConfig["a
|
|
|
1091
1156
|
return pool.length > 0 ? pool : undefined;
|
|
1092
1157
|
}
|
|
1093
1158
|
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
* same content-derived id as the API-key manager when the active key was missing from the
|
|
1108
|
-
* pool). Key mode reflects stored user intent (explicit `"key"` or omitted mode with safe
|
|
1109
|
-
* key material) — never whether the login CLI process can resolve an env reference. Env-backed
|
|
1110
|
-
* availability is decided at proxy routing time in `router.ts`.
|
|
1111
|
-
*/
|
|
1159
|
+
const OAUTH_LOGIN_OWNED_PROVIDER_FIELDS = [
|
|
1160
|
+
"adapter",
|
|
1161
|
+
"baseUrl",
|
|
1162
|
+
"authMode",
|
|
1163
|
+
"headers",
|
|
1164
|
+
"apiKeyTransport",
|
|
1165
|
+
"responsesPath",
|
|
1166
|
+
"tlsProfile",
|
|
1167
|
+
"googleMode",
|
|
1168
|
+
"keyOptional",
|
|
1169
|
+
] as const satisfies readonly (keyof OcxProviderConfig)[];
|
|
1170
|
+
|
|
1171
|
+
/** Add/refresh only an OAuth provider's login-owned config fields (does not persist). */
|
|
1112
1172
|
export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
1113
1173
|
if (provider === "chatgpt") return;
|
|
1114
1174
|
const def = OAUTH_PROVIDERS[provider];
|
|
@@ -1117,35 +1177,16 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1117
1177
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, provider);
|
|
1118
1178
|
if (namespaceCollision) throw new Error(namespaceCollision);
|
|
1119
1179
|
const existing = config.providers[provider];
|
|
1120
|
-
const next: OcxProviderConfig =
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
next
|
|
1125
|
-
}
|
|
1126
|
-
// The Command Code protocol-version pin is an operator compatibility control. A re-login,
|
|
1127
|
-
// add-account, or reauth rebuilds the row from the preset, which has no version; carry the
|
|
1128
|
-
// existing pin so authentication changes do not silently revert the documented control.
|
|
1129
|
-
if (existing?.commandCodeVersion !== undefined) {
|
|
1130
|
-
next.commandCodeVersion = existing.commandCodeVersion;
|
|
1131
|
-
}
|
|
1132
|
-
if (existing?.projectContext !== undefined) {
|
|
1133
|
-
next.projectContext = existing.projectContext;
|
|
1134
|
-
}
|
|
1135
|
-
// User-configured price overlays are operator data, not preset state; a
|
|
1136
|
-
// re-login, add-account, or reauth must not silently drop them from the
|
|
1137
|
-
// Logs/Usage estimates.
|
|
1138
|
-
if (existing?.modelCosts !== undefined) {
|
|
1139
|
-
next.modelCosts = existing.modelCosts;
|
|
1140
|
-
}
|
|
1141
|
-
// The per-provider account-failover opt-out is operator intent about SPENDING, and the login
|
|
1142
|
-
// path is exactly where losing it does damage: adding a second account both rebuilds this row
|
|
1143
|
-
// from the preset and creates the 2-account quorum that turns presence-driven rotation on
|
|
1144
|
-
// (#2568d). Dropping the opt-out here would enable the thing the operator switched off, at the
|
|
1145
|
-
// moment they were doing something unrelated.
|
|
1146
|
-
if (existing?.oauthAccountFailover !== undefined) {
|
|
1147
|
-
next.oauthAccountFailover = existing.oauthAccountFailover;
|
|
1180
|
+
const next: OcxProviderConfig = structuredClone(existing ?? def.providerConfig);
|
|
1181
|
+
for (const field of OAUTH_LOGIN_OWNED_PROVIDER_FIELDS) {
|
|
1182
|
+
const value = def.providerConfig[field];
|
|
1183
|
+
if (value === undefined) delete next[field];
|
|
1184
|
+
else next[field] = structuredClone(value) as never;
|
|
1148
1185
|
}
|
|
1186
|
+
// OAuth-only providers must never retain credentials for a different auth mechanism.
|
|
1187
|
+
delete next.apiKey;
|
|
1188
|
+
delete next.apiKeyPool;
|
|
1189
|
+
delete next.azureCredential;
|
|
1149
1190
|
if (existing && getProviderRegistryEntry(provider)?.allowKeyAuthOverride === true) {
|
|
1150
1191
|
// Shared sanitizeApiKeyValue trim / no-CRLF checks from api-key pool writes.
|
|
1151
1192
|
let storedApiKey = sanitizeApiKeyValue(existing.apiKey);
|
|
@@ -1160,7 +1201,9 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1160
1201
|
// Keep routing and listProviderApiKeys in sync: never leave a hidden active key that
|
|
1161
1202
|
// is absent from the pool (listing would fall back to pool[0] as "active").
|
|
1162
1203
|
if (!pool.some(entry => entry.key === storedApiKey)) {
|
|
1163
|
-
|
|
1204
|
+
const id = apiKeyPoolEntryId(storedApiKey);
|
|
1205
|
+
if (pool.some(entry => entry.id === id)) throw new Error("API-key pool ID collision");
|
|
1206
|
+
pool.push({ id, key: storedApiKey });
|
|
1164
1207
|
}
|
|
1165
1208
|
next.apiKey = storedApiKey;
|
|
1166
1209
|
next.apiKeyPool = pool;
|
|
@@ -1175,7 +1218,8 @@ interface RunLoginDeps {
|
|
|
1175
1218
|
saveCredential?: typeof saveCredential;
|
|
1176
1219
|
saveAccountCredential?: typeof saveAccountCredential;
|
|
1177
1220
|
loadConfig?: typeof loadConfig;
|
|
1178
|
-
|
|
1221
|
+
mutatePersistedConfig?: typeof mutatePersistedConfig;
|
|
1222
|
+
initializePersistedConfigIfMissing?: typeof initializePersistedConfigIfMissing;
|
|
1179
1223
|
settleKiroLoginTransaction?: typeof settleKiroLoginTransaction;
|
|
1180
1224
|
removeAccount?: typeof removeAccount;
|
|
1181
1225
|
setActiveAccount?: typeof setActiveAccount;
|
|
@@ -1210,7 +1254,8 @@ export async function runLogin(
|
|
|
1210
1254
|
const def = OAUTH_PROVIDERS[provider];
|
|
1211
1255
|
if (!def) throw new UnsupportedOAuthProviderError(provider);
|
|
1212
1256
|
const loadLatestConfig = deps.loadConfig ?? loadConfig;
|
|
1213
|
-
const
|
|
1257
|
+
const mutateLatestConfig = deps.mutatePersistedConfig ?? mutatePersistedConfig;
|
|
1258
|
+
const initializeLatestConfig = deps.initializePersistedConfigIfMissing ?? initializePersistedConfigIfMissing;
|
|
1214
1259
|
if (provider !== "chatgpt") {
|
|
1215
1260
|
const preflightConfig = loadLatestConfig();
|
|
1216
1261
|
const namespaceCollision = codexAccountNamespaceProviderCollisionError(
|
|
@@ -1265,16 +1310,32 @@ export async function runLogin(
|
|
|
1265
1310
|
if (provider !== "chatgpt") {
|
|
1266
1311
|
// Re-run against post-credential state so same-provider API-key additions, removals,
|
|
1267
1312
|
// and active-key switches survive. A late namespace claim wins over provider creation.
|
|
1268
|
-
const
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1313
|
+
const publish = () => mutateLatestConfig<{ error: string } | { config: OcxConfig }>(fresh => {
|
|
1314
|
+
const lateCollision = codexAccountNamespaceProviderCollisionError(
|
|
1315
|
+
fresh.codexAccountNamespaces,
|
|
1316
|
+
provider,
|
|
1317
|
+
);
|
|
1318
|
+
if (lateCollision) return { changed: false, value: { error: lateCollision } };
|
|
1319
|
+
upsertOAuthProvider(fresh, provider);
|
|
1320
|
+
return { changed: true, value: { config: structuredClone(fresh) } };
|
|
1321
|
+
});
|
|
1322
|
+
let outcome = publish();
|
|
1323
|
+
let published = false;
|
|
1324
|
+
if (outcome.status === "unavailable" && outcome.reason === "missing") {
|
|
1325
|
+
const initial = loadLatestConfig();
|
|
1326
|
+
const lateCollision = codexAccountNamespaceProviderCollisionError(
|
|
1327
|
+
initial.codexAccountNamespaces,
|
|
1328
|
+
provider,
|
|
1329
|
+
);
|
|
1330
|
+
if (lateCollision) throw new OAuthProviderPublicationError();
|
|
1331
|
+
upsertOAuthProvider(initial, provider);
|
|
1332
|
+
const initialized = initializeLatestConfig(initial);
|
|
1333
|
+
published = initialized === "created";
|
|
1334
|
+
if (initialized === "exists") outcome = publish();
|
|
1335
|
+
}
|
|
1336
|
+
if (!published && (outcome.status === "unavailable" || "error" in outcome.value)) {
|
|
1274
1337
|
throw new OAuthProviderPublicationError();
|
|
1275
1338
|
}
|
|
1276
|
-
upsertOAuthProvider(latestConfig, provider);
|
|
1277
|
-
saveLatestConfig(latestConfig);
|
|
1278
1339
|
}
|
|
1279
1340
|
} catch (error) {
|
|
1280
1341
|
const errors: unknown[] = [error];
|
package/src/oauth/login-cli.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
2
|
import { openUrl } from "../lib/open-url";
|
|
3
|
-
import { loadConfig,
|
|
3
|
+
import { initializePersistedConfigIfMissing, loadConfig, mutatePersistedConfig } from "../config";
|
|
4
4
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
5
5
|
import {
|
|
6
6
|
requestBoundLocalProviderReload,
|
|
@@ -11,6 +11,7 @@ import { KEY_LOGIN_PROVIDERS, isKeyLoginProvider, validateApiKey, type KeyLoginP
|
|
|
11
11
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
12
12
|
import { configuredAdminToken } from "../lib/admin-secrets";
|
|
13
13
|
import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
|
|
14
|
+
import { apiKeyPoolEntryId } from "../providers/api-keys";
|
|
14
15
|
|
|
15
16
|
const LIVE_RELOAD_PROVIDERS = new Set<string>([
|
|
16
17
|
...listOAuthProviders(),
|
|
@@ -121,7 +122,7 @@ async function handleAiStudioLogin(): Promise<void> {
|
|
|
121
122
|
|
|
122
123
|
const config = loadConfig();
|
|
123
124
|
if (!config.providers["google-aistudio"]) {
|
|
124
|
-
config
|
|
125
|
+
await commitKeyLoginProvider(config, "google-aistudio", {
|
|
125
126
|
adapter: "google",
|
|
126
127
|
googleMode: "ai-studio-web",
|
|
127
128
|
baseUrl: "https://alkalimakersuite-pa.clients6.google.com",
|
|
@@ -129,8 +130,7 @@ async function handleAiStudioLogin(): Promise<void> {
|
|
|
129
130
|
liveModels: false,
|
|
130
131
|
defaultModel: "gemini-3.7-flash",
|
|
131
132
|
models: ["gemini-3.7-flash", "gemini-3.1-pro-preview", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-3.5-flash"],
|
|
132
|
-
};
|
|
133
|
-
saveConfig(config);
|
|
133
|
+
});
|
|
134
134
|
console.log("\n ✓ Configured 'google-aistudio' in ~/.opencodex/config.json");
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -200,10 +200,28 @@ export function mergeKeyLoginProviderRow(
|
|
|
200
200
|
provider: OcxProviderConfig,
|
|
201
201
|
existing: OcxProviderConfig | undefined,
|
|
202
202
|
): OcxProviderConfig {
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
203
|
+
if (!existing) return structuredClone(provider);
|
|
204
|
+
const merged = structuredClone(existing);
|
|
205
|
+
merged.adapter = provider.adapter;
|
|
206
|
+
merged.baseUrl = provider.baseUrl;
|
|
207
|
+
if (provider.authMode !== undefined) merged.authMode = provider.authMode;
|
|
208
|
+
else delete merged.authMode;
|
|
209
|
+
delete merged.azureCredential;
|
|
210
|
+
if (provider.apiKeyTransport !== undefined) merged.apiKeyTransport = provider.apiKeyTransport;
|
|
211
|
+
if (provider.apiKey) {
|
|
212
|
+
const pool = merged.apiKeyPool ?? (merged.apiKey
|
|
213
|
+
? [{ id: apiKeyPoolEntryId(merged.apiKey), key: merged.apiKey }]
|
|
214
|
+
: []);
|
|
215
|
+
const existingKey = pool.find(entry => entry.key === provider.apiKey);
|
|
216
|
+
if (!existingKey) {
|
|
217
|
+
const id = apiKeyPoolEntryId(provider.apiKey);
|
|
218
|
+
if (pool.some(entry => entry.id === id)) throw new Error("API-key pool ID collision");
|
|
219
|
+
pool.push({ id, key: provider.apiKey, addedAt: Date.now() });
|
|
220
|
+
}
|
|
221
|
+
if (pool.length > 0) merged.apiKeyPool = pool;
|
|
222
|
+
merged.apiKey = provider.apiKey;
|
|
223
|
+
}
|
|
224
|
+
return merged;
|
|
207
225
|
}
|
|
208
226
|
|
|
209
227
|
/**
|
|
@@ -219,9 +237,33 @@ export async function commitKeyLoginProvider(
|
|
|
219
237
|
provider: OcxProviderConfig,
|
|
220
238
|
onLiveReload?: (result: LocalProviderReloadResult | null) => void,
|
|
221
239
|
): Promise<OcxProviderConfig> {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
240
|
+
let mergedProvider = mergeKeyLoginProviderRow(provider, config.providers[name]);
|
|
241
|
+
const mutate = () => mutatePersistedConfig(fresh => {
|
|
242
|
+
const collision = codexAccountNamespaceProviderCollisionError(fresh.codexAccountNamespaces, name);
|
|
243
|
+
if (collision) throw new Error(collision);
|
|
244
|
+
mergedProvider = mergeKeyLoginProviderRow(provider, fresh.providers[name]);
|
|
245
|
+
fresh.providers[name] = mergedProvider;
|
|
246
|
+
return { changed: true, value: { config: structuredClone(fresh), provider: structuredClone(mergedProvider) } };
|
|
247
|
+
});
|
|
248
|
+
let outcome = mutate();
|
|
249
|
+
if (outcome.status === "unavailable" && outcome.reason === "missing") {
|
|
250
|
+
const initial = structuredClone(config);
|
|
251
|
+
const collision = codexAccountNamespaceProviderCollisionError(initial.codexAccountNamespaces, name);
|
|
252
|
+
if (collision) throw new Error(collision);
|
|
253
|
+
mergedProvider = mergeKeyLoginProviderRow(provider, initial.providers[name]);
|
|
254
|
+
initial.providers[name] = mergedProvider;
|
|
255
|
+
const initialized = initializePersistedConfigIfMissing(initial);
|
|
256
|
+
if (initialized === "invalid") throw new Error("config is invalid");
|
|
257
|
+
outcome = initialized === "created"
|
|
258
|
+
? { status: "committed", value: { config: initial, provider: structuredClone(mergedProvider) } }
|
|
259
|
+
: mutate();
|
|
260
|
+
}
|
|
261
|
+
if (outcome.status === "unavailable") throw new Error(outcome.reason === "conflict"
|
|
262
|
+
? "config changed while saving provider; retry"
|
|
263
|
+
: `config is ${outcome.reason}`);
|
|
264
|
+
for (const key of Object.keys(config)) delete (config as unknown as Record<string, unknown>)[key];
|
|
265
|
+
Object.assign(config, outcome.value.config);
|
|
266
|
+
mergedProvider = outcome.value.provider;
|
|
225
267
|
// Evaluate the reload BEFORE the optional call: `onLiveReload?.(await ...)` short-circuits
|
|
226
268
|
// the whole argument list when no callback is supplied, so the reload would never fire for
|
|
227
269
|
// callers that do not care about the outcome.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mutatePersistedConfig } from "../config";
|
|
2
2
|
import { backupConfigBeforeAlibabaRegionMigration } from "./alibaba-region-backup";
|
|
3
3
|
import { projectAlibabaRegionMigration } from "./alibaba-region-migration";
|
|
4
4
|
import type { OcxConfig } from "../types";
|
|
@@ -6,7 +6,7 @@ import type { OcxConfig } from "../types";
|
|
|
6
6
|
export interface AlibabaRegionStartupDeps {
|
|
7
7
|
project: typeof projectAlibabaRegionMigration;
|
|
8
8
|
backup: () => void;
|
|
9
|
-
save
|
|
9
|
+
save?: (config: OcxConfig) => void;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -22,15 +22,35 @@ export function runAlibabaRegionStartupMigration(
|
|
|
22
22
|
deps: AlibabaRegionStartupDeps = {
|
|
23
23
|
project: projectAlibabaRegionMigration,
|
|
24
24
|
backup: () => { backupConfigBeforeAlibabaRegionMigration(); },
|
|
25
|
-
save: saveConfig,
|
|
26
25
|
},
|
|
27
26
|
): OcxConfig {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
if (deps.save) {
|
|
28
|
+
const projection = deps.project(config);
|
|
29
|
+
if (!projection.changed) {
|
|
30
|
+
for (const warning of projection.warnings) console.warn(`[alibaba-region-migration] ${warning}`);
|
|
31
|
+
return projection.config;
|
|
32
|
+
}
|
|
33
|
+
deps.backup();
|
|
34
|
+
deps.save(projection.config);
|
|
35
|
+
for (const warning of projection.warnings) console.warn(`[alibaba-region-migration] ${warning}`);
|
|
36
|
+
return projection.config;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let previousSnapshot: string | undefined;
|
|
40
|
+
const outcome = mutatePersistedConfig(fresh => {
|
|
41
|
+
const snapshot = JSON.stringify(fresh);
|
|
42
|
+
const next = deps.project(fresh);
|
|
43
|
+
// The callback is repeated after freshness validation. Back up only the
|
|
44
|
+
// confirmed preimage that is about to be committed.
|
|
45
|
+
if (next.changed && snapshot === previousSnapshot) deps.backup();
|
|
46
|
+
previousSnapshot = snapshot;
|
|
47
|
+
if (next.changed) {
|
|
48
|
+
for (const key of Object.keys(fresh)) delete (fresh as unknown as Record<string, unknown>)[key];
|
|
49
|
+
Object.assign(fresh, next.config);
|
|
50
|
+
}
|
|
51
|
+
return { changed: next.changed, value: next };
|
|
52
|
+
});
|
|
53
|
+
if (outcome.status === "unavailable") return config;
|
|
54
|
+
for (const warning of outcome.value.warnings) console.warn(`[alibaba-region-migration] ${warning}`);
|
|
55
|
+
return outcome.value.config;
|
|
36
56
|
}
|