@bitkyc08/opencodex 2.7.43-preview.20260728 → 2.8.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 +8 -1
- package/bin/ocx.mjs +47 -22
- package/gui/dist/assets/index-BDjpkcRN.js +67 -0
- package/gui/dist/assets/index-BHsKRFh9.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/AGENTS.md +28 -0
- package/src/adapters/anthropic.ts +15 -6
- package/src/adapters/cursor/discovery.ts +4 -1
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/native-exec-shell.ts +18 -6
- package/src/adapters/cursor/protobuf-events.ts +24 -2
- package/src/adapters/cursor/protobuf-request.ts +1 -2
- package/src/adapters/cursor/tool-definitions.ts +68 -29
- package/src/adapters/google-wire-compiler.ts +4 -0
- package/src/adapters/google.ts +128 -2
- package/src/adapters/identity.ts +12 -2
- package/src/adapters/kiro.ts +64 -7
- package/src/adapters/mimo-free.ts +2 -0
- package/src/adapters/openai-responses.ts +246 -59
- package/src/claude/agents-inject.ts +5 -0
- package/src/claude/alias.ts +94 -14
- package/src/claude/inbound.ts +26 -9
- package/src/claude/outbound.ts +6 -3
- package/src/cli/account-auth.ts +1 -1
- package/src/cli/agent-driven.ts +37 -0
- package/src/cli/catalog-prewarm.ts +24 -0
- package/src/cli/claude.ts +35 -10
- package/src/cli/doctor.ts +71 -19
- package/src/cli/help.ts +42 -6
- package/src/cli/index.ts +93 -19
- package/src/cli/interactive-confirm.ts +133 -0
- package/src/cli/opencode.ts +701 -0
- package/src/cli/provider-runtime.ts +3 -0
- package/src/cli/provider.ts +31 -10
- package/src/cli/star-prompt.ts +79 -18
- package/src/cli/status.ts +47 -13
- package/src/cli/v2.ts +10 -1
- package/src/codex/account-id.ts +34 -0
- package/src/codex/account-lifecycle.ts +4 -1
- package/src/codex/account-namespace-match.ts +63 -0
- package/src/codex/account-namespaces.ts +149 -0
- package/src/codex/account-pause.ts +20 -0
- package/src/codex/account-store.ts +2 -0
- package/src/codex/account-usability.ts +6 -1
- package/src/codex/app-server-processes.ts +511 -0
- package/src/codex/auth-api.ts +293 -34
- package/src/codex/auth-collision.ts +2 -1
- package/src/codex/auth-context.ts +60 -17
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/parsing.ts +42 -2
- package/src/codex/catalog/provider-fetch.ts +264 -70
- package/src/codex/catalog/sync.ts +45 -8
- package/src/codex/catalog.ts +2 -2
- package/src/codex/features.ts +524 -5
- package/src/codex/history-provider.ts +145 -1
- package/src/codex/inject.ts +114 -14
- package/src/codex/main-account.ts +2 -8
- package/src/codex/pool-rotation.ts +186 -0
- package/src/codex/quota.ts +92 -2
- package/src/codex/routing.ts +695 -106
- package/src/codex/runtime.ts +10 -1
- package/src/codex/shim.ts +4 -1
- package/src/codex/subagent-defaults.ts +550 -0
- package/src/codex/subagent-model-fallback.ts +2 -0
- package/src/codex/sync.ts +3 -0
- package/src/config.ts +574 -25
- package/src/generated/jawcode-model-metadata.ts +12 -12
- package/src/github/star-state.ts +191 -0
- package/src/images/artifacts.ts +516 -0
- package/src/images/fulfill-video.ts +163 -0
- package/src/images/fulfill.ts +111 -0
- package/src/images/index.ts +4 -0
- package/src/images/loop.ts +789 -0
- package/src/images/plan.ts +133 -0
- package/src/images/synthetic-tool.ts +133 -0
- package/src/images/types.ts +41 -0
- package/src/images/xai-client.ts +141 -0
- package/src/images/xai-video-client.ts +163 -0
- package/src/lib/admin-secrets.ts +25 -0
- package/src/lib/bun-binary-validator.d.mts +3 -0
- package/src/lib/bun-binary-validator.mjs +18 -0
- package/src/lib/bun-runtime.ts +6 -20
- package/src/lib/config-ownership.ts +327 -0
- package/src/lib/crash-guard.ts +2 -0
- package/src/lib/destination-policy.ts +132 -7
- package/src/lib/pinned-http.ts +151 -0
- package/src/lib/process-control.ts +2 -2
- package/src/lib/provider-outbound.ts +167 -0
- package/src/lib/provider-url.ts +14 -0
- package/src/lib/proxy-env.ts +18 -0
- package/src/lib/shadow-call.ts +30 -0
- package/src/lib/test-home-guard.ts +90 -0
- package/src/lib/win-exec.ts +12 -2
- package/src/lib/windows-elevation.ts +81 -3
- package/src/lib/windows-secret-acl.ts +189 -12
- package/src/lib/winsw.ts +2 -0
- package/src/oauth/anthropic-routing.ts +570 -0
- package/src/oauth/health.ts +6 -0
- package/src/oauth/index.ts +310 -75
- package/src/oauth/key-providers.ts +38 -8
- package/src/oauth/kimi.ts +2 -0
- package/src/oauth/kiro-credentials.ts +373 -12
- package/src/oauth/kiro.ts +424 -43
- package/src/oauth/login-cli.ts +33 -6
- package/src/oauth/store.ts +56 -4
- package/src/oauth/types.ts +11 -0
- package/src/providers/alibaba-region-migration.ts +16 -3
- package/src/providers/antigravity-models.ts +3 -0
- package/src/providers/api-keys.ts +13 -6
- package/src/providers/derive.ts +8 -2
- package/src/providers/key-failover.ts +24 -4
- package/src/providers/model-discovery.ts +356 -0
- package/src/providers/quota.ts +233 -29
- package/src/providers/registry.ts +125 -3
- package/src/responses/parser.ts +11 -0
- package/src/responses/state.ts +22 -8
- package/src/responses/tool-groups.ts +19 -0
- package/src/router.ts +19 -7
- package/src/server/auth-cors.ts +114 -24
- package/src/server/claude-messages.ts +8 -1
- package/src/server/gui-static.ts +30 -6
- package/src/server/images.ts +303 -9
- package/src/server/index.ts +77 -9
- package/src/server/lifecycle.ts +25 -1
- package/src/server/live.ts +75 -25
- package/src/server/management/agent-settings-routes.ts +106 -8
- package/src/server/management/combo-routes.ts +7 -0
- package/src/server/management/config-routes.ts +22 -7
- package/src/server/management/context.ts +11 -1
- package/src/server/management/logs-usage-routes.ts +167 -3
- package/src/server/management/model-routes.ts +46 -13
- package/src/server/management/oauth-account-routes.ts +163 -17
- package/src/server/management/provider-routes.ts +73 -10
- package/src/server/management/shared.ts +2 -2
- package/src/server/management/sidebar-routes.ts +39 -0
- package/src/server/management/system-restart.ts +172 -0
- package/src/server/management/system-routes.ts +33 -10
- package/src/server/management-api.ts +5 -3
- package/src/server/management-auth.ts +216 -0
- package/src/server/proxy-liveness.ts +14 -3
- package/src/server/responses/compact.ts +21 -13
- package/src/server/responses/core.ts +614 -172
- package/src/server/responses/upstream-error.ts +48 -0
- package/src/server/responses-image-gen-repair.ts +118 -0
- package/src/server/responses-item-id-repair.ts +10 -85
- package/src/server/sse-payload-rewrite.ts +116 -0
- package/src/server/startup-action-control.ts +30 -14
- package/src/server/system-env.ts +28 -10
- package/src/service.ts +284 -19
- package/src/storage/cleanup-job.ts +57 -0
- package/src/storage/cleanup.ts +1504 -28
- package/src/storage/policy-job.ts +387 -0
- package/src/storage/policy-scheduler.ts +40 -0
- package/src/storage/policy-worker.ts +53 -0
- package/src/storage/policy.ts +522 -0
- package/src/storage/restore-job.ts +253 -0
- package/src/storage/restore-worker.ts +52 -0
- package/src/storage/storage-mutation-coordinator.ts +109 -0
- package/src/storage/worker-lifecycle.ts +81 -0
- package/src/tray/windows.ts +34 -4
- package/src/types.ts +107 -1
- package/src/update/badge.ts +72 -0
- package/src/update/index.ts +36 -18
- package/src/update/job.ts +111 -16
- package/src/update/npm-invocation.d.mts +23 -0
- package/src/update/npm-invocation.mjs +94 -0
- package/src/usage/debug.ts +2 -0
- package/src/usage/expected-prices.ts +6 -5
- package/src/usage/log.ts +12 -0
- package/src/web-search/loop.ts +57 -16
- package/gui/dist/assets/index-CjKFJHSC.js +0 -65
- package/gui/dist/assets/index-DfVGuN88.css +0 -1
package/src/oauth/store.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Exceptions:
|
|
11
11
|
* - `chatgpt` stays single-slot (always replaced): codex-auth-api uses it as a scratch slot
|
|
12
12
|
* for Codex pool logins, which have their own ledger (codex-accounts.json).
|
|
13
|
-
* - Credentials without identity (no accountId/email
|
|
13
|
+
* - Credentials without identity (no accountId/email) replace the active slot
|
|
14
14
|
* instead of appending: their refresh tokens rotate, so a derived id would duplicate the
|
|
15
15
|
* same human on every re-login. Kimi extracts JWT `user_id`/`sub` as accountId; Cursor
|
|
16
16
|
* extracts JWT `sub` — both append distinct accounts under multiauth.
|
|
@@ -19,6 +19,8 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
19
19
|
import { chmodSync, closeSync, copyFileSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
20
20
|
import { join } from "node:path";
|
|
21
21
|
import { getConfigDir, atomicWriteFile, backupInvalidConfig, hardenConfigDir, hardenExistingSecret } from "../config";
|
|
22
|
+
import { assertNotRealHomeUnderTest } from "../lib/test-home-guard";
|
|
23
|
+
import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
22
24
|
import { validateCopilotApiBaseUrl } from "./github-copilot";
|
|
23
25
|
import type { OAuthCredentialSource, OAuthCredentials, ProviderAccount, ProviderAccountSet } from "./types";
|
|
24
26
|
|
|
@@ -130,6 +132,7 @@ export function peekAuthStore(): AuthStore {
|
|
|
130
132
|
|
|
131
133
|
function persist(store: AuthStore): void {
|
|
132
134
|
const dir = getConfigDir();
|
|
135
|
+
assertNotRealHomeUnderTest(dir);
|
|
133
136
|
if (!existsSync(dir)) {
|
|
134
137
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
135
138
|
} else {
|
|
@@ -150,7 +153,7 @@ function sameFd(a: LockSnapshot,b: ReturnType<typeof fstatSync>): boolean { retu
|
|
|
150
153
|
export function createOAuthFileLock(options: OAuthFileLockOptions): { acquire(): Promise<OAuthFileLockGuard> } {
|
|
151
154
|
const wait=options.waitTimeoutMs??5000, stale=options.staleAfterMs??120000, min=options.pollMinMs??25,max=options.pollMaxMs??100,sleep=options.sleep??(ms=>Bun.sleep(ms)),now=options.now??Date.now,random=options.random??Math.random,write=options.writeMetadata??((fd,b)=>writeFileSync(fd,b,"utf8"));
|
|
152
155
|
if(wait<0||stale<=0||min<0||max<min) throw new OAuthFileLockError("Invalid OAuth file-lock timing options");
|
|
153
|
-
return { async acquire() { hardenConfigDir(); if(!existsSync(getConfigDir())) mkdirSync(getConfigDir(),{recursive:true,mode:0o700}); const ownerId=randomUUID(),started=now(); for(;;){ let fd:number|undefined; try { fd=openSync(options.path,"wx",0o600); const bytes=`${JSON.stringify({version:1,ownerId,pid:process.pid,createdAt:now()})}\n`; write(fd,bytes); const fs=fstatSync(fd); closeSync(fd); fd=undefined; const owned=snapshot(options.path); if(owned.bytes!==bytes||!sameFd(owned,fs)) throw new OAuthFileLockError("OAuth lock changed during creation"); let released=false; return {ownerId,release(){if(released)return;released=true;try{const a=snapshot(options.path);if(!sameSnapshot(owned,a))return;options.beforeReleaseUnlink?.();const b=snapshot(options.path);if(sameSnapshot(owned,b))unlinkSync(options.path);}catch(e){if(errorCode(e)!=="ENOENT")console.warn(`[oauth] lock release failed: ${e instanceof Error?e.message:String(e)}`);}}}; } catch(e) { if(fd!==undefined){let fs;try{fs=fstatSync(fd);}catch{}try{closeSync(fd);}catch{}if(fs)try{const a=snapshot(options.path);if(sameFd(a,fs)){options.beforeFailedCreateUnlink?.();const b=snapshot(options.path);if(sameSnapshot(a,b)&&sameFd(b,fs))unlinkSync(options.path);}}catch{}} if(errorCode(e)!=="EEXIST")throw e instanceof OAuthFileLockError?e:new OAuthFileLockError("Could not create OAuth file lock",{cause:e}); }
|
|
156
|
+
return { async acquire() { hardenConfigDir(); recordOwnedConfigPath(getConfigDir(),options.path); if(!existsSync(getConfigDir())) mkdirSync(getConfigDir(),{recursive:true,mode:0o700}); const ownerId=randomUUID(),started=now(); for(;;){ let fd:number|undefined; try { fd=openSync(options.path,"wx",0o600); const bytes=`${JSON.stringify({version:1,ownerId,pid:process.pid,createdAt:now()})}\n`; write(fd,bytes); const fs=fstatSync(fd); closeSync(fd); fd=undefined; const owned=snapshot(options.path); if(owned.bytes!==bytes||!sameFd(owned,fs)) throw new OAuthFileLockError("OAuth lock changed during creation"); let released=false; return {ownerId,release(){if(released)return;released=true;try{const a=snapshot(options.path);if(!sameSnapshot(owned,a))return;options.beforeReleaseUnlink?.();const b=snapshot(options.path);if(sameSnapshot(owned,b))unlinkSync(options.path);}catch(e){if(errorCode(e)!=="ENOENT")console.warn(`[oauth] lock release failed: ${e instanceof Error?e.message:String(e)}`);}}}; } catch(e) { if(fd!==undefined){let fs;try{fs=fstatSync(fd);}catch{}try{closeSync(fd);}catch{}if(fs)try{const a=snapshot(options.path);if(sameFd(a,fs)){options.beforeFailedCreateUnlink?.();const b=snapshot(options.path);if(sameSnapshot(a,b)&&sameFd(b,fs))unlinkSync(options.path);}}catch{}} if(errorCode(e)!=="EEXIST")throw e instanceof OAuthFileLockError?e:new OAuthFileLockError("Could not create OAuth file lock",{cause:e}); }
|
|
154
157
|
try{const a=snapshot(options.path);let created=a.mtimeMs;try{const p=JSON.parse(a.bytes);if(typeof p.createdAt==="number")created=Math.max(created,p.createdAt);}catch{}if(now()-created>stale){options.beforeStaleUnlink?.();const b=snapshot(options.path);if(sameSnapshot(a,b))unlinkSync(options.path);continue;}}catch(e){if(errorCode(e)==="ENOENT")continue;throw new OAuthFileLockError("Could not inspect OAuth file lock",{cause:e});} const elapsed=now()-started;if(elapsed>=wait)throw new OAuthFileLockError(`Timed out after ${wait}ms waiting for OAuth file lock`);await sleep(Math.min(wait-elapsed,min+Math.floor(random()*(max-min+1)))); } } };
|
|
155
158
|
}
|
|
156
159
|
/** Wait long enough for slow IdP refreshes (e.g. Cursor 15s × 3 attempts) before timing out. */
|
|
@@ -206,6 +209,28 @@ function normalizeCredential(cred: unknown): OAuthCredentials | null {
|
|
|
206
209
|
const validated = validateCopilotApiBaseUrl(candidate.apiBaseUrl);
|
|
207
210
|
if (validated) normalized.apiBaseUrl = validated;
|
|
208
211
|
}
|
|
212
|
+
if (candidate.kiro && typeof candidate.kiro === "object") {
|
|
213
|
+
const kiro = candidate.kiro;
|
|
214
|
+
const clean = (value: unknown, max: number): string | undefined => {
|
|
215
|
+
if (typeof value !== "string") return undefined;
|
|
216
|
+
const trimmed = value.trim();
|
|
217
|
+
return trimmed && trimmed.length <= max && !/[\x00-\x1f\x7f]/.test(trimmed) ? trimmed : undefined;
|
|
218
|
+
};
|
|
219
|
+
const profileArn = clean(kiro.profileArn, 1024);
|
|
220
|
+
const ssoRegion = clean(kiro.ssoRegion, 64);
|
|
221
|
+
const apiRegion = clean(kiro.apiRegion, 64);
|
|
222
|
+
const clientId = clean(kiro.clientId, 4096);
|
|
223
|
+
const clientSecret = clean(kiro.clientSecret, 4096);
|
|
224
|
+
if (profileArn || ssoRegion || apiRegion || clientId || clientSecret) {
|
|
225
|
+
normalized.kiro = {
|
|
226
|
+
...(profileArn ? { profileArn } : {}),
|
|
227
|
+
...(ssoRegion ? { ssoRegion } : {}),
|
|
228
|
+
...(apiRegion ? { apiRegion } : {}),
|
|
229
|
+
...(clientId ? { clientId } : {}),
|
|
230
|
+
...(clientSecret ? { clientSecret } : {}),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
209
234
|
return normalized;
|
|
210
235
|
}
|
|
211
236
|
|
|
@@ -293,7 +318,11 @@ export function getCredential(provider: string): OAuthCredentials | null {
|
|
|
293
318
|
* (rotating refresh tokens would fabricate duplicates) and single-slot providers replace the
|
|
294
319
|
* active slot / whole set instead.
|
|
295
320
|
*/
|
|
296
|
-
export async function saveCredential(
|
|
321
|
+
export async function saveCredential(
|
|
322
|
+
provider: string,
|
|
323
|
+
cred: OAuthCredentials,
|
|
324
|
+
opts: { preserveIdentityless?: boolean } = {},
|
|
325
|
+
): Promise<void> {
|
|
297
326
|
const safe = normalizeCredential(cred);
|
|
298
327
|
if (!safe) return;
|
|
299
328
|
await mutateStore(store => {
|
|
@@ -317,7 +346,7 @@ export async function saveCredential(provider: string, cred: OAuthCredentials):
|
|
|
317
346
|
// active identity-less row in place prevents a stale duplicate that stays selectable
|
|
318
347
|
// and would re-refresh into a second row with the same identity.
|
|
319
348
|
const active = set.accounts.find(a => a.id === set.activeAccountId);
|
|
320
|
-
if (active && active.credential.accountId === undefined && active.credential.email === undefined) {
|
|
349
|
+
if (!opts.preserveIdentityless && active && active.credential.accountId === undefined && active.credential.email === undefined) {
|
|
321
350
|
active.credential = safe;
|
|
322
351
|
delete active.needsReauth;
|
|
323
352
|
return;
|
|
@@ -418,6 +447,29 @@ export async function removeAccount(provider: string, accountId: string): Promis
|
|
|
418
447
|
});
|
|
419
448
|
}
|
|
420
449
|
|
|
450
|
+
/** Replace or clear a provider account set (used for transactional Kiro add-account rollback). */
|
|
451
|
+
export async function replaceProviderAccountSet(
|
|
452
|
+
provider: string,
|
|
453
|
+
set: ProviderAccountSet | null,
|
|
454
|
+
): Promise<void> {
|
|
455
|
+
await mutateStore(store => {
|
|
456
|
+
if (!set || set.accounts.length === 0) {
|
|
457
|
+
delete store[provider];
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
store[provider] = {
|
|
461
|
+
activeAccountId: set.activeAccountId,
|
|
462
|
+
accounts: set.accounts.map(account => ({
|
|
463
|
+
id: account.id,
|
|
464
|
+
credential: { ...account.credential, ...(account.credential.kiro ? { kiro: { ...account.credential.kiro } } : {}) },
|
|
465
|
+
...(account.alias ? { alias: account.alias } : {}),
|
|
466
|
+
...(account.needsReauth ? { needsReauth: true } : {}),
|
|
467
|
+
...(account.addedAt !== undefined ? { addedAt: account.addedAt } : {}),
|
|
468
|
+
})),
|
|
469
|
+
};
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
421
473
|
export async function markAccountNeedsReauth(provider: string, accountId: string, needsReauth: boolean): Promise<void> {
|
|
422
474
|
await mutateStore(store => {
|
|
423
475
|
const account = store[provider]?.accounts.find(a => a.id === accountId);
|
package/src/oauth/types.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
/** Minimal OAuth types, ported from jawcode packages/ai/src/utils/oauth/types.ts. */
|
|
2
2
|
export type OAuthCredentialSource = "oauth" | "local-cli" | "credential-file" | "environment" | "manual";
|
|
3
3
|
|
|
4
|
+
/** Account-scoped Kiro data required for refresh and request routing. */
|
|
5
|
+
export interface KiroOAuthMetadata {
|
|
6
|
+
profileArn?: string;
|
|
7
|
+
ssoRegion?: string;
|
|
8
|
+
apiRegion?: string;
|
|
9
|
+
clientId?: string;
|
|
10
|
+
clientSecret?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
4
13
|
export type OAuthCredentials = {
|
|
5
14
|
refresh: string;
|
|
6
15
|
access: string;
|
|
@@ -15,6 +24,8 @@ export type OAuthCredentials = {
|
|
|
15
24
|
* Never reuse for Antigravity projectId; validated on write and again at request time.
|
|
16
25
|
*/
|
|
17
26
|
apiBaseUrl?: string;
|
|
27
|
+
/** Never returned by management APIs; persisted only inside the protected auth-store boundary. */
|
|
28
|
+
kiro?: KiroOAuthMetadata;
|
|
18
29
|
};
|
|
19
30
|
|
|
20
31
|
/** One logged-in account inside a provider's account set (multiauth). */
|
|
@@ -2,6 +2,7 @@ import { ALIBABA_INTL_BASE_URL_CHOICES } from "./base-url-choices";
|
|
|
2
2
|
import { providerConfigSeed } from "./derive";
|
|
3
3
|
import { rewriteProviderReferences } from "./provider-id-rewrite";
|
|
4
4
|
import { PROVIDER_REGISTRY } from "./registry";
|
|
5
|
+
import { codexAccountNamespaceProviderCollisionError } from "../codex/account-namespace-match";
|
|
5
6
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
6
7
|
|
|
7
8
|
const BEIJING_ID = "alibaba-token-plan";
|
|
@@ -91,9 +92,10 @@ function buildIntlRow(source: OcxProviderConfig): OcxProviderConfig {
|
|
|
91
92
|
* those contracts across. That override fix shipped once, was backed out twice,
|
|
92
93
|
* and was refused again when PR #459 closed.
|
|
93
94
|
*
|
|
94
|
-
* Refuses to act when the destination
|
|
95
|
-
*
|
|
96
|
-
* user decision, not a
|
|
95
|
+
* Refuses to act when the destination is reserved by an account namespace or
|
|
96
|
+
* already holds a value — a provider row or a key the reference rewrite would
|
|
97
|
+
* land on — because choosing which setting wins is a user decision, not a
|
|
98
|
+
* migration's.
|
|
97
99
|
*/
|
|
98
100
|
export function projectAlibabaRegionMigration(config: OcxConfig): AlibabaRegionMigrationProjection {
|
|
99
101
|
const beijing = config.providers[BEIJING_ID];
|
|
@@ -101,6 +103,17 @@ export function projectAlibabaRegionMigration(config: OcxConfig): AlibabaRegionM
|
|
|
101
103
|
if (!beijing || !savedBaseUrl || !isInternationalEndpoint(savedBaseUrl)) {
|
|
102
104
|
return { config, changed: false, warnings: [] };
|
|
103
105
|
}
|
|
106
|
+
if (codexAccountNamespaceProviderCollisionError(config.codexAccountNamespaces, INTL_ID)) {
|
|
107
|
+
return {
|
|
108
|
+
config,
|
|
109
|
+
changed: false,
|
|
110
|
+
warnings: [
|
|
111
|
+
`provider "${BEIJING_ID}" needs to move to "${INTL_ID}", but that destination is reserved `
|
|
112
|
+
+ `by a configured Codex account namespace. Nothing was changed. Rename the account `
|
|
113
|
+
+ `selector or move the provider manually, then restart.`,
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
104
117
|
if (config.providers[INTL_ID]) {
|
|
105
118
|
return {
|
|
106
119
|
config,
|
|
@@ -13,6 +13,7 @@ const ANTIGRAVITY_WIRE_MODELS = [
|
|
|
13
13
|
"gemini-3.6-flash-high",
|
|
14
14
|
"gemini-3.1-pro-low",
|
|
15
15
|
"gemini-pro-agent",
|
|
16
|
+
"gemini-3.1-flash-image",
|
|
16
17
|
"claude-sonnet-4-6",
|
|
17
18
|
"claude-opus-4-6-thinking",
|
|
18
19
|
"gpt-oss-120b-medium",
|
|
@@ -85,6 +86,7 @@ export const ANTIGRAVITY_MODEL_ALIASES: Record<string, string> = {
|
|
|
85
86
|
export const ANTIGRAVITY_MODELS = [
|
|
86
87
|
"gemini-3.6-flash",
|
|
87
88
|
"gemini-3.1-pro",
|
|
89
|
+
"gemini-3.1-flash-image",
|
|
88
90
|
"claude-sonnet-4-6",
|
|
89
91
|
"claude-opus-4-6-thinking",
|
|
90
92
|
"gpt-oss-120b-medium",
|
|
@@ -97,6 +99,7 @@ const ANTIGRAVITY_WIRE_MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
|
|
97
99
|
"gemini-3.6-flash-high": 1_048_576,
|
|
98
100
|
"gemini-3.1-pro-low": 1_048_576,
|
|
99
101
|
"gemini-pro-agent": 1_048_576,
|
|
102
|
+
"gemini-3.1-flash-image": 1_048_576,
|
|
100
103
|
"claude-sonnet-4-6": 200_000,
|
|
101
104
|
"claude-opus-4-6-thinking": 1_000_000,
|
|
102
105
|
"gpt-oss-120b-medium": 131_072,
|
|
@@ -30,7 +30,7 @@ export function maskApiKey(value: string): string {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Content-derived id: re-adding the same key upserts instead of duplicating. */
|
|
33
|
-
function
|
|
33
|
+
export function apiKeyPoolEntryId(key: string): string {
|
|
34
34
|
return createHash("sha256").update(key).digest("hex").slice(0, 8);
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -39,11 +39,18 @@ export function isKeyAuthProvider(provider: OcxProviderConfig): boolean {
|
|
|
39
39
|
return provider.authMode !== "oauth" && provider.authMode !== "forward";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** Trim and reject blank / CRLF-bearing secrets. Shared by pool writes and OAuth upsert. */
|
|
43
|
+
export function sanitizeApiKeyValue(value: unknown): string | undefined {
|
|
44
|
+
if (typeof value !== "string") return undefined;
|
|
45
|
+
const trimmed = value.trim();
|
|
46
|
+
return trimmed && !/[\r\n]/.test(trimmed) ? trimmed : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
42
49
|
/** Seed the pool from a legacy bare `apiKey`, and keep `apiKey` mirrored to the active entry. */
|
|
43
50
|
function ensurePool(provider: OcxProviderConfig): NonNullable<OcxProviderConfig["apiKeyPool"]> {
|
|
44
51
|
if (!provider.apiKeyPool) provider.apiKeyPool = [];
|
|
45
52
|
if (provider.apiKeyPool.length === 0 && provider.apiKey) {
|
|
46
|
-
provider.apiKeyPool.push({ id:
|
|
53
|
+
provider.apiKeyPool.push({ id: apiKeyPoolEntryId(provider.apiKey), key: provider.apiKey });
|
|
47
54
|
}
|
|
48
55
|
return provider.apiKeyPool;
|
|
49
56
|
}
|
|
@@ -75,11 +82,11 @@ export function listProviderApiKeys(config: OcxConfig, name: string): { activeId
|
|
|
75
82
|
export function addProviderApiKey(config: OcxConfig, name: string, key: string, label?: string): { id: string } | { error: string } {
|
|
76
83
|
const provider = config.providers[name];
|
|
77
84
|
if (!provider || !isKeyAuthProvider(provider)) return { error: "provider does not use API-key auth" };
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (
|
|
85
|
+
if (typeof key !== "string" || !key.trim()) return { error: "key is required" };
|
|
86
|
+
const trimmed = sanitizeApiKeyValue(key);
|
|
87
|
+
if (!trimmed) return { error: "key must not include line breaks" };
|
|
81
88
|
const pool = ensurePool(provider);
|
|
82
|
-
const id =
|
|
89
|
+
const id = apiKeyPoolEntryId(trimmed);
|
|
83
90
|
const existing = pool.find(e => e.id === id);
|
|
84
91
|
if (existing) {
|
|
85
92
|
if (label?.trim()) existing.label = label.trim();
|
package/src/providers/derive.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
|
-
import { PROVIDER_REGISTRY, type ProviderRegistryEntry } from "./registry";
|
|
2
|
+
import { PROVIDER_REGISTRY, providerMatchesRegistryTransport, type ProviderRegistryEntry } from "./registry";
|
|
3
3
|
|
|
4
4
|
export interface DerivedKeyLoginProvider {
|
|
5
5
|
label: string;
|
|
6
6
|
baseUrl: string;
|
|
7
7
|
adapter: string;
|
|
8
|
+
apiKeyTransport?: OcxProviderConfig["apiKeyTransport"];
|
|
8
9
|
dashboardUrl: string;
|
|
9
10
|
models?: string[];
|
|
10
11
|
liveModels?: boolean;
|
|
@@ -103,6 +104,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
103
104
|
return {
|
|
104
105
|
adapter: entry.adapter,
|
|
105
106
|
baseUrl: entry.baseUrl,
|
|
107
|
+
...(entry.apiKeyTransport !== undefined ? { apiKeyTransport: entry.apiKeyTransport } : {}),
|
|
106
108
|
authMode: entry.authKind === "local" ? undefined : entry.authKind,
|
|
107
109
|
...(entry.codexAccountMode ? { codexAccountMode: entry.codexAccountMode } : {}),
|
|
108
110
|
...(entry.keyOptional !== undefined ? { keyOptional: entry.keyOptional } : {}),
|
|
@@ -129,6 +131,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
129
131
|
...(entry.noTopPModels ? { noTopPModels: [...entry.noTopPModels] } : {}),
|
|
130
132
|
...(entry.noPenaltyModels ? { noPenaltyModels: [...entry.noPenaltyModels] } : {}),
|
|
131
133
|
...(entry.parallelToolCalls !== undefined ? { parallelToolCalls: entry.parallelToolCalls } : {}),
|
|
134
|
+
...(entry.promptCacheKey !== undefined ? { promptCacheKey: entry.promptCacheKey } : {}),
|
|
132
135
|
...(entry.autoToolChoiceOnlyModels ? { autoToolChoiceOnlyModels: [...entry.autoToolChoiceOnlyModels] } : {}),
|
|
133
136
|
...(entry.preserveReasoningContentModels ? { preserveReasoningContentModels: [...entry.preserveReasoningContentModels] } : {}),
|
|
134
137
|
...(entry.reasoningSplitModels ? { reasoningSplitModels: [...entry.reasoningSplitModels] } : {}),
|
|
@@ -150,6 +153,7 @@ export function deriveKeyLoginMap(): Record<string, DerivedKeyLoginProvider> {
|
|
|
150
153
|
label: entry.label,
|
|
151
154
|
baseUrl: entry.baseUrl,
|
|
152
155
|
adapter: entry.adapter,
|
|
156
|
+
...(entry.apiKeyTransport !== undefined ? { apiKeyTransport: entry.apiKeyTransport } : {}),
|
|
153
157
|
dashboardUrl: entry.dashboardUrl,
|
|
154
158
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
155
159
|
...(entry.liveModels !== undefined ? { liveModels: entry.liveModels } : {}),
|
|
@@ -219,8 +223,9 @@ export function deriveProviderPresets(): DerivedProviderPreset[] {
|
|
|
219
223
|
|
|
220
224
|
export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig): void {
|
|
221
225
|
const entry = PROVIDER_REGISTRY.find(row => row.id === name);
|
|
222
|
-
if (!entry) return;
|
|
226
|
+
if (!entry || !providerMatchesRegistryTransport(name, prov)) return;
|
|
223
227
|
const seed = providerConfigSeed(entry);
|
|
228
|
+
if (prov.apiKeyTransport === undefined && seed.apiKeyTransport !== undefined) prov.apiKeyTransport = seed.apiKeyTransport;
|
|
224
229
|
if (!prov.defaultModel && seed.defaultModel) prov.defaultModel = seed.defaultModel;
|
|
225
230
|
// Fill mode only when absent: an explicit persisted `direct` must never be overwritten.
|
|
226
231
|
if (prov.codexAccountMode === undefined && seed.codexAccountMode !== undefined) prov.codexAccountMode = seed.codexAccountMode;
|
|
@@ -242,6 +247,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
242
247
|
if (!prov.noTopPModels && seed.noTopPModels) prov.noTopPModels = [...seed.noTopPModels];
|
|
243
248
|
if (!prov.noPenaltyModels && seed.noPenaltyModels) prov.noPenaltyModels = [...seed.noPenaltyModels];
|
|
244
249
|
if (prov.parallelToolCalls === undefined && seed.parallelToolCalls !== undefined) prov.parallelToolCalls = seed.parallelToolCalls;
|
|
250
|
+
if (prov.promptCacheKey === undefined && seed.promptCacheKey !== undefined) prov.promptCacheKey = seed.promptCacheKey;
|
|
245
251
|
if (!prov.autoToolChoiceOnlyModels && seed.autoToolChoiceOnlyModels) prov.autoToolChoiceOnlyModels = [...seed.autoToolChoiceOnlyModels];
|
|
246
252
|
if (!prov.preserveReasoningContentModels && seed.preserveReasoningContentModels) prov.preserveReasoningContentModels = [...seed.preserveReasoningContentModels];
|
|
247
253
|
if (!prov.reasoningSplitModels && seed.reasoningSplitModels) prov.reasoningSplitModels = [...seed.reasoningSplitModels];
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { saveConfigPreservingClaudeCode } from "../config";
|
|
12
12
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
13
|
-
import { resolveProviderTransport } from "./xai-transport";
|
|
13
|
+
import { resolveProviderTransport, type OcxProviderTransport } from "./xai-transport";
|
|
14
14
|
|
|
15
15
|
// ---- cooldown state (in-memory, same as codex/routing.ts) ----
|
|
16
16
|
|
|
@@ -69,6 +69,11 @@ export function hasKeyPoolFailover(provider: OcxProviderConfig): boolean {
|
|
|
69
69
|
*
|
|
70
70
|
* @returns A new OcxProviderConfig with the swapped key (and mutated config on disk),
|
|
71
71
|
* or `null` when no alternative key is available (all in cooldown or pool < 2).
|
|
72
|
+
*
|
|
73
|
+
* The returned object is a snapshot of the PERSISTED config — it carries none of the
|
|
74
|
+
* registry backfills `routedProviderConfig` merges in at request time. Request paths must
|
|
75
|
+
* not assign it to an active route wholesale; use `rotateProviderTransportOn429`, which
|
|
76
|
+
* takes only the swapped key and keeps the routed provider intact.
|
|
72
77
|
*/
|
|
73
78
|
export function rotateKeyOn429(
|
|
74
79
|
config: OcxConfig,
|
|
@@ -133,12 +138,23 @@ interface RotateProviderTransportOptions {
|
|
|
133
138
|
promptCacheKey?: string;
|
|
134
139
|
}
|
|
135
140
|
|
|
136
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Rotate a failed key and re-apply provider-specific transport metadata to the replacement.
|
|
143
|
+
*
|
|
144
|
+
* `routedProvider` is the request's active provider (the `routedProviderConfig` output the
|
|
145
|
+
* route was built with). The result inherits it and swaps ONLY the API key: the persisted
|
|
146
|
+
* config that `rotateKeyOn429` snapshots predates registry backfill, so building the retry
|
|
147
|
+
* provider from that snapshot would silently drop every field the registry merged in at
|
|
148
|
+
* routing time (scalar flags like `promptCacheKey`/`parallelToolCalls`, merged model
|
|
149
|
+
* metadata such as `noTemperatureModels`, a pinned baseUrl). Mirrors the OAuth-401 replay
|
|
150
|
+
* path in src/server/responses/core.ts, which spreads `route.provider` for the same reason.
|
|
151
|
+
*/
|
|
137
152
|
export function rotateProviderTransportOn429(
|
|
138
153
|
config: OcxConfig,
|
|
139
154
|
providerName: string,
|
|
155
|
+
routedProvider: OcxProviderTransport,
|
|
140
156
|
options: RotateProviderTransportOptions = {},
|
|
141
|
-
):
|
|
157
|
+
): OcxProviderTransport | null {
|
|
142
158
|
const rotated = rotateKeyOn429(
|
|
143
159
|
config,
|
|
144
160
|
providerName,
|
|
@@ -147,7 +163,11 @@ export function rotateProviderTransportOn429(
|
|
|
147
163
|
options.attemptedKey,
|
|
148
164
|
);
|
|
149
165
|
return rotated
|
|
150
|
-
? resolveProviderTransport(
|
|
166
|
+
? resolveProviderTransport(
|
|
167
|
+
providerName,
|
|
168
|
+
{ ...routedProvider, apiKey: rotated.apiKey },
|
|
169
|
+
options.promptCacheKey,
|
|
170
|
+
)
|
|
151
171
|
: null;
|
|
152
172
|
}
|
|
153
173
|
|