@yansigit/opencodex 2.33.1 → 2.35.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 +3 -3
- package/gui/dist/assets/index-BjCaHxdz.js +112 -0
- package/gui/dist/assets/index-DLkXOXLC.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -3
- package/src/adapters/anthropic.ts +79 -2
- package/src/adapters/command-code.ts +40 -3
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/discovery.ts +60 -2
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +9 -9
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-shell.ts +103 -6
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +74 -39
- package/src/adapters/cursor/request-builder.ts +32 -10
- package/src/adapters/cursor/tool-definitions.ts +13 -10
- package/src/adapters/cursor/tool-result-normalize.ts +35 -3
- package/src/adapters/cursor/types.ts +16 -4
- package/src/adapters/cursor.ts +97 -70
- package/src/adapters/google-antigravity-replay.ts +105 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +41 -12
- package/src/adapters/google-http.ts +12 -11
- package/src/adapters/google.ts +156 -65
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +15 -0
- package/src/adapters/kiro-tools.ts +43 -15
- package/src/adapters/kiro.ts +54 -9
- package/src/adapters/openai-chat.ts +286 -242
- package/src/adapters/openai-responses.ts +334 -24
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +2 -2
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/bridge.ts +67 -26
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/outbound.ts +10 -8
- package/src/cli/account-api.ts +27 -7
- package/src/cli/account-extended.ts +10 -3
- package/src/cli/account.ts +29 -5
- package/src/cli/alias.ts +66 -0
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +13 -1
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/init.ts +1 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +13 -7
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/registry.ts +6 -1
- package/src/cli/telemetry-commands.ts +25 -0
- package/src/cli/v2.ts +34 -10
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +80 -6
- package/src/codex/auth-api.ts +48 -8
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/catalog/aggregation.ts +6 -0
- package/src/codex/catalog/model-metadata.ts +13 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +16 -0
- package/src/codex/catalog/provider-fetch.ts +20 -37
- package/src/codex/catalog/sync.ts +127 -2
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +22 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +18 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +14 -1
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +123 -18
- package/src/codex/routing.ts +9 -0
- package/src/codex/subagent-model-fallback.ts +198 -27
- package/src/codex/transition-state.ts +107 -8
- package/src/combos/types.ts +10 -0
- package/src/compatibility/openai-responses.ts +33 -1
- package/src/config/autonomous-remediation.ts +21 -0
- package/src/config/provider-validation.ts +14 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +191 -17
- package/src/generated/compatibility-version.json +229 -153
- package/src/images/loop.ts +5 -4
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +11 -2
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/oauth/aistudio-credentials.ts +65 -0
- package/src/oauth/aistudio-native-daemon.ts +54 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/index.ts +57 -5
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/login-cli.ts +19 -20
- package/src/oauth/types.ts +15 -0
- package/src/providers/codex-capacity.ts +5 -2
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +73 -14
- package/src/providers/registry.ts +160 -68
- package/src/providers/slug-codec.ts +52 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +34 -10
- package/src/responses/parser.ts +4 -0
- package/src/responses/schema.ts +5 -1
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +23 -0
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/effort-policy.ts +16 -0
- package/src/server/index.ts +152 -80
- package/src/server/lifecycle.ts +52 -1
- package/src/server/management/agent-settings-routes.ts +31 -15
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +27 -9
- package/src/server/management/context.ts +9 -0
- package/src/server/management/logs-usage-routes.ts +1 -1
- package/src/server/management/model-routes.ts +266 -0
- package/src/server/management/provider-routes.ts +137 -3
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management-api.ts +2 -0
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +21 -0
- package/src/server/request-log.ts +46 -20
- package/src/server/responses/collaboration.ts +42 -5
- package/src/server/responses/combo-stream-preflight.ts +10 -3
- package/src/server/responses/core.ts +571 -137
- package/src/server/responses/empty-completion-guard.ts +35 -0
- package/src/server/responses/fetch-helpers.ts +14 -6
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +35 -5
- package/src/server/responses-custom-tool-repair.ts +52 -7
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +15 -4
- package/src/telemetry/dispatcher.ts +44 -0
- package/src/telemetry/fingerprint.ts +24 -0
- package/src/telemetry/hook.ts +43 -0
- package/src/telemetry/ledger.ts +54 -0
- package/src/telemetry/types.ts +23 -0
- package/src/types/config.ts +66 -14
- package/src/types/provider.ts +77 -0
- package/src/types/request.ts +16 -10
- package/src/types/tools.ts +20 -25
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +17 -7
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-CIDo4y4k.js +0 -102
- package/gui/dist/assets/index-DrSQdTRd.css +0 -1
- package/src/server/aistudio-ws-hub.ts +0 -295
package/src/oauth/index.ts
CHANGED
|
@@ -58,11 +58,20 @@ export interface OAuthAccessSnapshot {
|
|
|
58
58
|
/** Cloud Code Assist project selected during Antigravity login. */
|
|
59
59
|
projectId?: string;
|
|
60
60
|
/** Safe request-routing subset; refresh-only Kiro client secrets never leave the credential store. */
|
|
61
|
-
kiro?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion">;
|
|
61
|
+
kiro?: Pick<KiroOAuthMetadata, "profileArn" | "apiRegion" | "ssoRegion" | "authType">;
|
|
62
|
+
/**
|
|
63
|
+
* Allowlisted GitHub Copilot API origin belonging to THIS account.
|
|
64
|
+
*
|
|
65
|
+
* Copilot pins its bearer to an account-scoped regional host, and the initial route already
|
|
66
|
+
* pairs the two (`core.ts` resolves transport with `getOAuthCredentialApiBaseUrl`). Account
|
|
67
|
+
* failover must carry the pairing across the rotation; without it, account B's token is sent to
|
|
68
|
+
* account A's origin (#2568d).
|
|
69
|
+
*/
|
|
70
|
+
apiBaseUrl?: string;
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
export interface ObservedOAuthAccessSnapshot extends OAuthAccessSnapshot {
|
|
65
|
-
/**
|
|
74
|
+
/** Retained for callers that predate `apiBaseUrl` moving onto the base snapshot. */
|
|
66
75
|
apiBaseUrl?: string;
|
|
67
76
|
}
|
|
68
77
|
|
|
@@ -351,24 +360,43 @@ export function publicOAuthAuthenticationErrorMessage(error: unknown): string {
|
|
|
351
360
|
}
|
|
352
361
|
|
|
353
362
|
function accessSnapshot(provider: string, accountId: string, cred: OAuthCredentials): OAuthAccessSnapshot {
|
|
363
|
+
// Derived, not read back: a stored `authType` is trusted when present, but a credential imported
|
|
364
|
+
// before the field existed still routes correctly because the client pair implies SSO OIDC.
|
|
365
|
+
const kiroAuthType = cred.kiro?.authType
|
|
366
|
+
?? (cred.kiro?.clientId && cred.kiro?.clientSecret ? "aws_sso_oidc" as const : undefined);
|
|
354
367
|
const storedKiroRouting = {
|
|
355
368
|
...(cred.kiro?.profileArn ? { profileArn: cred.kiro.profileArn } : {}),
|
|
356
369
|
...(cred.kiro?.apiRegion ? { apiRegion: cred.kiro.apiRegion } : {}),
|
|
357
370
|
...(cred.kiro?.ssoRegion ? { ssoRegion: cred.kiro.ssoRegion } : {}),
|
|
358
371
|
};
|
|
372
|
+
// `authType` is a property OF the account, not routing the environment can substitute for, so it
|
|
373
|
+
// is merged after the environment fallback decision rather than counting as stored routing.
|
|
374
|
+
// Folding it into `storedKiroRouting` would make a client-pair-only credential look non-empty
|
|
375
|
+
// and silently disable `environmentKiroRoutingMetadata()` for it.
|
|
376
|
+
const kiroAuthTypeRouting = kiroAuthType ? { authType: kiroAuthType } : {};
|
|
377
|
+
// Validated here, not at the call site: an unvalidated origin from a legacy or crafted
|
|
378
|
+
// credential must never travel with a bearer, and dropping it makes the transport fall back to
|
|
379
|
+
// the canonical host rather than to whatever the previous account was using.
|
|
380
|
+
const copilotApiBaseUrl = provider === "github-copilot"
|
|
381
|
+
? validateCopilotApiBaseUrl(cred.apiBaseUrl)
|
|
382
|
+
: undefined;
|
|
359
383
|
return {
|
|
360
384
|
provider,
|
|
361
385
|
accountId,
|
|
362
386
|
generation: credentialGeneration(cred),
|
|
363
387
|
accessToken: cred.access,
|
|
364
388
|
...(cred.projectId ? { projectId: cred.projectId } : {}),
|
|
389
|
+
...(copilotApiBaseUrl ? { apiBaseUrl: copilotApiBaseUrl } : {}),
|
|
365
390
|
// Stored account metadata remains authoritative. Metadata-less legacy/environment credentials
|
|
366
391
|
// may use explicit environment routing, but never borrow the currently signed-in local CLI account.
|
|
367
392
|
...(provider === "kiro"
|
|
368
393
|
? {
|
|
369
|
-
kiro:
|
|
370
|
-
|
|
371
|
-
|
|
394
|
+
kiro: {
|
|
395
|
+
...(Object.keys(storedKiroRouting).length > 0
|
|
396
|
+
? storedKiroRouting
|
|
397
|
+
: environmentKiroRoutingMetadata() ?? {}),
|
|
398
|
+
...kiroAuthTypeRouting,
|
|
399
|
+
},
|
|
372
400
|
}
|
|
373
401
|
: {}),
|
|
374
402
|
};
|
|
@@ -499,6 +527,22 @@ export async function getValidAccessTokenForAccount(provider: string, accountId:
|
|
|
499
527
|
return (await resolveAccessSnapshotForAccount(provider, accountId)).accessToken;
|
|
500
528
|
}
|
|
501
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Account-scoped resolver returning the FULL snapshot, not just the bearer.
|
|
532
|
+
*
|
|
533
|
+
* A rotator that swaps only the token silently mixes credential generations: Antigravity pairs
|
|
534
|
+
* an account-matched `projectId` with its token (see the pairing comment in
|
|
535
|
+
* server/responses/core.ts), Kiro carries routing metadata, and Copilot's observed snapshot
|
|
536
|
+
* carries an account-specific API origin. Reading those back from "whichever account is active"
|
|
537
|
+
* after a rotation is exactly the mixing this returns in one piece to prevent (#2568).
|
|
538
|
+
*/
|
|
539
|
+
export async function getValidAccessSnapshotForAccount(
|
|
540
|
+
provider: string,
|
|
541
|
+
accountId: string,
|
|
542
|
+
): Promise<OAuthAccessSnapshot> {
|
|
543
|
+
return resolveAccessSnapshotForAccount(provider, accountId);
|
|
544
|
+
}
|
|
545
|
+
|
|
502
546
|
/** Terminal refresh failures (revoked/rotated-away grants) — retrying cannot succeed. */
|
|
503
547
|
function isTerminalRefreshError(err: unknown): boolean {
|
|
504
548
|
const msg = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
@@ -1094,6 +1138,14 @@ export function upsertOAuthProvider(config: OcxConfig, provider: string): void {
|
|
|
1094
1138
|
if (existing?.modelCosts !== undefined) {
|
|
1095
1139
|
next.modelCosts = existing.modelCosts;
|
|
1096
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;
|
|
1148
|
+
}
|
|
1097
1149
|
if (existing && getProviderRegistryEntry(provider)?.allowKeyAuthOverride === true) {
|
|
1098
1150
|
// Shared sanitizeApiKeyValue trim / no-CRLF checks from api-key pool writes.
|
|
1099
1151
|
let storedApiKey = sanitizeApiKeyValue(existing.apiKey);
|
|
@@ -20,7 +20,8 @@ export const KEY_LOGIN_PROVIDERS: Record<string, KeyLoginProvider> = deriveKeyLo
|
|
|
20
20
|
* caller didn't already supply. Lets the vision/reasoning classification actually reach the saved
|
|
21
21
|
* config (the GUI/API only send adapter/baseUrl/apiKey/defaultModel). No-op for unknown names.
|
|
22
22
|
*
|
|
23
|
-
* `modelSupportsReasoningSummaries`
|
|
23
|
+
* `modelSupportsReasoningSummaries` and the verbosity capability are deliberately excluded from
|
|
24
|
+
* what gets persisted. They are
|
|
24
25
|
* registry-only metadata resolved at runtime, and this function feeds a config that is about to
|
|
25
26
|
* be written to disk. Persisting today's registry defaults would freeze them as the user's own
|
|
26
27
|
* overrides: a later registry correction — say we learn a model's backend rejects summary
|
|
@@ -31,9 +32,17 @@ export const KEY_LOGIN_PROVIDERS: Record<string, KeyLoginProvider> = deriveKeyLo
|
|
|
31
32
|
export function enrichProviderFromCatalog(name: string, prov: OcxProviderConfig): void {
|
|
32
33
|
const hadOwnSummaries = Object.hasOwn(prov, "modelSupportsReasoningSummaries");
|
|
33
34
|
const submittedSummaries = prov.modelSupportsReasoningSummaries;
|
|
35
|
+
const hadOwnVerbosity = Object.hasOwn(prov, "modelSupportsVerbosity");
|
|
36
|
+
const submittedVerbosity = prov.modelSupportsVerbosity;
|
|
37
|
+
const hadOwnProviderVerbosity = Object.hasOwn(prov, "supportsVerbosity");
|
|
38
|
+
const submittedProviderVerbosity = prov.supportsVerbosity;
|
|
34
39
|
enrichProviderFromRegistry(name, prov);
|
|
35
40
|
if (hadOwnSummaries) prov.modelSupportsReasoningSummaries = submittedSummaries;
|
|
36
41
|
else delete prov.modelSupportsReasoningSummaries;
|
|
42
|
+
if (hadOwnVerbosity) prov.modelSupportsVerbosity = submittedVerbosity;
|
|
43
|
+
else delete prov.modelSupportsVerbosity;
|
|
44
|
+
if (hadOwnProviderVerbosity) prov.supportsVerbosity = submittedProviderVerbosity;
|
|
45
|
+
else delete prov.supportsVerbosity;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
export function isKeyLoginProvider(name: string): boolean {
|
package/src/oauth/kiro.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
} from "./kiro-credentials";
|
|
29
29
|
import { homedir } from "node:os";
|
|
30
30
|
import { getAccountSet, saveAccountCredential } from "./store";
|
|
31
|
+
import { KIRO_BUILDER_ID_SERVICE_PROFILE_ARN } from "../adapters/kiro-constants";
|
|
31
32
|
|
|
32
33
|
const DEFAULT_REGION = "us-east-1";
|
|
33
34
|
const REFRESH_URL = "https://prod.{region}.auth.desktop.kiro.dev/refreshToken";
|
|
@@ -473,6 +474,50 @@ export function resolveKiroProfileArn(account?: Pick<KiroOAuthMetadata, "profile
|
|
|
473
474
|
return readImportedKiroCredential()?.profileArn;
|
|
474
475
|
}
|
|
475
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Resolve the profileArn actually SENT upstream, which is not always the account's own.
|
|
479
|
+
*
|
|
480
|
+
* An AWS Builder ID account authenticates through SSO OIDC and never receives an account-scoped
|
|
481
|
+
* profile ARN, so gated models reject its requests with a `profileArn`-demanding
|
|
482
|
+
* `ValidationException`. The Kiro CLI handles this by carrying a fixed service profile on Builder
|
|
483
|
+
* ID requests, and this mirrors that.
|
|
484
|
+
*
|
|
485
|
+
* Deliberately separate from `resolveKiroProfileArn`: that resolver answers "what is this
|
|
486
|
+
* account's profile", and callers that ask it — region inference, account matching, continuation
|
|
487
|
+
* scoping — must keep receiving `undefined` here. Only request construction uses this function.
|
|
488
|
+
*
|
|
489
|
+
* The fallback is gated on `authType === "aws_sso_oidc"` rather than on a missing ARN, so a
|
|
490
|
+
* `kiro_desktop` account whose profile import failed keeps producing its actionable error instead
|
|
491
|
+
* of silently borrowing a service profile that does not describe it.
|
|
492
|
+
*/
|
|
493
|
+
export function resolveKiroRequestProfileArn(
|
|
494
|
+
account?: Pick<KiroOAuthMetadata, "profileArn" | "authType">,
|
|
495
|
+
): string | undefined {
|
|
496
|
+
return resolveKiroRequestProfile(account).profileArn;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* The profileArn to send, together with WHY it was chosen.
|
|
501
|
+
*
|
|
502
|
+
* The request builder must decide the wire envelope from the same evaluation that produced the
|
|
503
|
+
* ARN. Re-deriving "is this Builder ID" from the account context alone would miss the accountless
|
|
504
|
+
* path, where the auth type comes from the locally imported credential instead: the fallback would
|
|
505
|
+
* be sent while the request was shaped as an enterprise IDE call, which is not a combination the
|
|
506
|
+
* vendor client ever produces.
|
|
507
|
+
*/
|
|
508
|
+
export function resolveKiroRequestProfile(
|
|
509
|
+
account?: Pick<KiroOAuthMetadata, "profileArn" | "authType">,
|
|
510
|
+
): { profileArn: string | undefined; builderIdFallback: boolean } {
|
|
511
|
+
const own = resolveKiroProfileArn(account);
|
|
512
|
+
if (own) return { profileArn: own, builderIdFallback: false };
|
|
513
|
+
const authType = account !== undefined
|
|
514
|
+
? account.authType
|
|
515
|
+
: readImportedKiroCredential()?.authType;
|
|
516
|
+
return authType === "aws_sso_oidc"
|
|
517
|
+
? { profileArn: KIRO_BUILDER_ID_SERVICE_PROFILE_ARN, builderIdFallback: true }
|
|
518
|
+
: { profileArn: undefined, builderIdFallback: false };
|
|
519
|
+
}
|
|
520
|
+
|
|
476
521
|
async function kiroTokenRefreshError(response: Response): Promise<KiroTokenRefreshError> {
|
|
477
522
|
let oauthError: string | undefined;
|
|
478
523
|
try {
|
package/src/oauth/login-cli.ts
CHANGED
|
@@ -67,7 +67,7 @@ export function warnIfLiveReloadSkipped(result: LocalProviderReloadResult | null
|
|
|
67
67
|
export async function handleLogin(provider?: string): Promise<void> {
|
|
68
68
|
const name = (provider ?? "").trim().toLowerCase();
|
|
69
69
|
if (name === "google-aistudio" || name === "aistudio" || name === "gemini-aistudio") {
|
|
70
|
-
return
|
|
70
|
+
return handleAiStudioLogin();
|
|
71
71
|
}
|
|
72
72
|
if (isPublicOAuthProvider(name)) return handleOAuthLogin(name);
|
|
73
73
|
if (isKeyLoginProvider(name)) return handleKeyLogin(name);
|
|
@@ -79,15 +79,10 @@ export async function handleLogin(provider?: string): Promise<void> {
|
|
|
79
79
|
process.exit(1);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
async function
|
|
83
|
-
const live = await findLiveProxy();
|
|
84
|
-
const port = live?.port ?? 10100;
|
|
85
|
-
const bridgeUrl = "http://127.0.0.1:" + port + "/aistudio/bridge";
|
|
86
|
-
|
|
82
|
+
async function handleAiStudioLogin(): Promise<void> {
|
|
87
83
|
console.log("\n🌐 Google AI Studio Sign-In & Session Setup:");
|
|
88
84
|
console.log(" Option 1: Paste Session Token from the Brave/Chrome extension popup (Passkey-friendly)");
|
|
89
|
-
console.log(" Option 2: Open native macOS sign-in window");
|
|
90
|
-
console.log(" Option 3: Open browser bridge page (" + bridgeUrl + ")\n");
|
|
85
|
+
console.log(" Option 2: Open native macOS sign-in window\n");
|
|
91
86
|
|
|
92
87
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
93
88
|
try {
|
|
@@ -100,20 +95,25 @@ async function handleAiStudioBridgeLogin(): Promise<void> {
|
|
|
100
95
|
saveAiStudioSessionFromToken(choice);
|
|
101
96
|
console.log("\n✅ Session token imported successfully! Saved to ~/.opencodex/aistudio-session.json");
|
|
102
97
|
} else if (process.platform === "darwin") {
|
|
103
|
-
const {
|
|
104
|
-
const swiftSrc = getAiStudioNativeDaemonSourcePath();
|
|
98
|
+
const { runAiStudioNativeLogin } = await import("./aistudio-native-daemon");
|
|
105
99
|
console.log("\n🚀 Opening native Google AI Studio login window...");
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
console.
|
|
100
|
+
const result = await runAiStudioNativeLogin();
|
|
101
|
+
if (result.kind === "cancelled") {
|
|
102
|
+
console.log("\nNative Google AI Studio login cancelled.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (result.kind === "unsupported") {
|
|
106
|
+
console.error("\nGoogle AI Studio native login is only available on macOS.");
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (result.kind === "failed") {
|
|
110
|
+
console.error(`\n${result.error}`);
|
|
111
|
+
return;
|
|
113
112
|
}
|
|
113
|
+
console.log("\n✅ Google AI Studio authenticated successfully! Session saved to ~/.opencodex/aistudio-session.json");
|
|
114
114
|
} else {
|
|
115
|
-
console.
|
|
116
|
-
|
|
115
|
+
console.error("\nGoogle AI Studio native login is only available on macOS. Paste a session token from the extension instead.");
|
|
116
|
+
return;
|
|
117
117
|
}
|
|
118
118
|
} finally {
|
|
119
119
|
rl.close();
|
|
@@ -134,7 +134,6 @@ async function handleAiStudioBridgeLogin(): Promise<void> {
|
|
|
134
134
|
console.log("\n ✓ Configured 'google-aistudio' in ~/.opencodex/config.json");
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
openUrl(bridgeUrl);
|
|
138
137
|
const reload = await notifyRunningProxy("google-aistudio");
|
|
139
138
|
console.log("\n✅ Ready! Use models with 'google-aistudio' provider in your coding agents.");
|
|
140
139
|
warnIfLiveReloadSkipped(reload);
|
package/src/oauth/types.ts
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
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
|
+
/**
|
|
5
|
+
* How the account authenticated. Mirrors `KiroAuthType` in `./kiro-credentials`, restated here so
|
|
6
|
+
* the credential-store types do not depend on the SQLite import module.
|
|
7
|
+
*
|
|
8
|
+
* `aws_sso_oidc` covers AWS Builder ID, which never issues an account-scoped CodeWhisperer
|
|
9
|
+
* profile ARN; the adapter needs that distinction to tell a Builder ID account apart from a
|
|
10
|
+
* `kiro_desktop` account whose profile import merely failed.
|
|
11
|
+
*/
|
|
12
|
+
export type KiroCredentialAuthType = "kiro_desktop" | "aws_sso_oidc";
|
|
13
|
+
|
|
4
14
|
/** Account-scoped Kiro data required for refresh and request routing. */
|
|
5
15
|
export interface KiroOAuthMetadata {
|
|
6
16
|
profileArn?: string;
|
|
@@ -8,6 +18,11 @@ export interface KiroOAuthMetadata {
|
|
|
8
18
|
apiRegion?: string;
|
|
9
19
|
clientId?: string;
|
|
10
20
|
clientSecret?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Non-secret routing signal. Derived from the presence of a device-registration client pair, so
|
|
23
|
+
* it stays accurate even though `clientId`/`clientSecret` never leave the credential store.
|
|
24
|
+
*/
|
|
25
|
+
authType?: KiroCredentialAuthType;
|
|
11
26
|
}
|
|
12
27
|
|
|
13
28
|
export type OAuthCredentials = {
|
|
@@ -14,6 +14,9 @@ export const CODEX_CAPACITY_MAX_QUOTA_AGE_MS = 30 * 60_000;
|
|
|
14
14
|
export type CodexCapacityQuota = {
|
|
15
15
|
fiveHourPercent?: number;
|
|
16
16
|
fiveHourResetAt?: number;
|
|
17
|
+
shortPercent?: number;
|
|
18
|
+
shortResetAt?: number;
|
|
19
|
+
shortWindowSeconds?: number;
|
|
17
20
|
weeklyPercent?: number;
|
|
18
21
|
weeklyResetAt?: number;
|
|
19
22
|
monthlyPercent?: number;
|
|
@@ -106,7 +109,7 @@ function futureResetMs(value: unknown, now: number): number | undefined {
|
|
|
106
109
|
|
|
107
110
|
function hasKnownQuotaWindow(quota: CodexCapacityQuota | null): quota is CodexCapacityQuota {
|
|
108
111
|
if (!quota) return false;
|
|
109
|
-
return normalizedPercent(quota.fiveHourPercent) !== undefined
|
|
112
|
+
return normalizedPercent(quota.fiveHourPercent ?? quota.shortPercent) !== undefined
|
|
110
113
|
|| normalizedPercent(quota.weeklyPercent) !== undefined
|
|
111
114
|
|| normalizedPercent(quota.monthlyPercent) !== undefined
|
|
112
115
|
|| !!quota.customWindows?.some(window => normalizedPercent(window.percent) !== undefined);
|
|
@@ -197,7 +200,7 @@ export function aggregateCodexPoolCapacity(
|
|
|
197
200
|
&& now - quota.updatedAt <= CODEX_CAPACITY_MAX_QUOTA_AGE_MS;
|
|
198
201
|
if (quota && !quotaFresh) staleQuotaAccounts += 1;
|
|
199
202
|
const standard = quota ? [
|
|
200
|
-
["fiveHour", quota.fiveHourPercent, quota.fiveHourResetAt],
|
|
203
|
+
["fiveHour", quota.fiveHourPercent ?? quota.shortPercent, quota.fiveHourResetAt ?? quota.shortResetAt],
|
|
201
204
|
["weekly", quota.weeklyPercent, quota.weeklyResetAt],
|
|
202
205
|
["monthly", quota.monthlyPercent, quota.monthlyResetAt],
|
|
203
206
|
] as const : [];
|
|
@@ -9,12 +9,44 @@ const COMMAND_CODE_MODEL_EFFORTS = {
|
|
|
9
9
|
efforts: ["high", "max"],
|
|
10
10
|
profileUrl: "https://commandcode.ai/models/deepseek-v4-flash",
|
|
11
11
|
},
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
/*
|
|
13
|
+
* Three live routes that reached the catalog without an effort ladder (#2647).
|
|
14
|
+
* Without a row here the model advertises no efforts at all, so a client that
|
|
15
|
+
* sends one gets it stripped or rejected rather than honored.
|
|
16
|
+
*
|
|
17
|
+
* PROVENANCE, stated plainly: these three ladders are the reporter's
|
|
18
|
+
* (darwintree, #2647), recorded as reported and NOT independently verified.
|
|
19
|
+
* All three profileUrls return HTTP 200, but commandcode.ai renders these
|
|
20
|
+
* pages client-side and ships the ladder inside a serialized React payload
|
|
21
|
+
* whose `reasoningEfforts` array is EMPTY in the delivered HTML. There is no
|
|
22
|
+
* fetchable statement of these ladders to check them against.
|
|
23
|
+
*
|
|
24
|
+
* Do not assume the refresh path launders this. It does not:
|
|
25
|
+
* `parsedProfileEfforts` below matches prose of the form
|
|
26
|
+
* "Reasoning efforts ... are supported;", and `grep -c -i 'reasoning efforts'`
|
|
27
|
+
* against the live pages returns 0 — for these three AND for the older rows
|
|
28
|
+
* (deepseek-v4-pro, GLM-5.3, muse-spark-1.2 all measured 0 on 2026-08-27).
|
|
29
|
+
* So `refreshCommandCodeReasoningEfforts` returns undefined and the caller
|
|
30
|
+
* keeps whatever is written here, indefinitely. The self-correction mechanism
|
|
31
|
+
* is currently dead for EVERY row in this table, which is a pre-existing
|
|
32
|
+
* defect worth its own fix (teach the parser to read the embedded payload),
|
|
33
|
+
* not something these three rows introduced.
|
|
34
|
+
*
|
|
35
|
+
* The practical consequence: a wrong ladder here stays wrong until a human
|
|
36
|
+
* changes it. It degrades safely — an effort the upstream rejects surfaces as
|
|
37
|
+
* an error rather than silent corruption — but it does not self-heal.
|
|
38
|
+
*/
|
|
39
|
+
"deepseek/deepseek-v4-flash-vision-exp": {
|
|
40
|
+
efforts: ["high", "max"],
|
|
41
|
+
profileUrl: "https://commandcode.ai/models/deepseek-v4-flash-vision-exp",
|
|
42
|
+
},
|
|
43
|
+
"gpt-5.6-luna": {
|
|
44
|
+
efforts: ["low", "medium", "high", "xhigh", "max"],
|
|
45
|
+
profileUrl: "https://commandcode.ai/models/gpt-5-6-luna",
|
|
46
|
+
},
|
|
47
|
+
"google/gemini-3.7-flash": {
|
|
48
|
+
efforts: ["low", "medium", "high"],
|
|
49
|
+
profileUrl: "https://commandcode.ai/models/gemini-3-7-flash",
|
|
18
50
|
},
|
|
19
51
|
// Keys must match the EXACT upstream /provider/v1/models ids (GLM ships as
|
|
20
52
|
// `zai-org/GLM-5.3`, not `zai-org/glm-5.3`). The table doubles as the router's
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { OcxConfig } from "../types";
|
|
2
|
+
import { deleteConfigTopLevelKey } from "../config/rebase-provenance";
|
|
2
3
|
|
|
3
4
|
export const DEFAULT_PROVIDER_CONTEXT_CAP = 350_000;
|
|
4
5
|
|
|
@@ -50,7 +51,7 @@ export function setProviderContextCap(config: OcxConfig, provider: string, enabl
|
|
|
50
51
|
delete next[provider];
|
|
51
52
|
}
|
|
52
53
|
if (Object.keys(next).length > 0) config.providerContextCaps = next;
|
|
53
|
-
else
|
|
54
|
+
else deleteConfigTopLevelKey(config, "providerContextCaps");
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -71,12 +72,12 @@ export function setGlobalContextCapValue(config: OcxConfig, value: number, apply
|
|
|
71
72
|
/** Enable the cap for every named provider at the current value, or clear all caps. */
|
|
72
73
|
export function setAllProviderContextCaps(config: OcxConfig, providerNames: string[], enabled: boolean): void {
|
|
73
74
|
if (!enabled) {
|
|
74
|
-
|
|
75
|
+
deleteConfigTopLevelKey(config, "providerContextCaps");
|
|
75
76
|
return;
|
|
76
77
|
}
|
|
77
78
|
const value = globalContextCapValue(config);
|
|
78
79
|
const next: Record<string, number> = {};
|
|
79
80
|
for (const name of providerNames) next[name] = value;
|
|
80
81
|
if (Object.keys(next).length > 0) config.providerContextCaps = next;
|
|
81
|
-
else
|
|
82
|
+
else deleteConfigTopLevelKey(config, "providerContextCaps");
|
|
82
83
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
export const MODEL_ALIAS_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
4
|
+
|
|
5
|
+
/** Ordered, most-specific-first built-in aliases. Updated with the provider registry. */
|
|
6
|
+
export const DEFAULT_MODEL_ALIASES: ReadonlyArray<{ match: RegExp; alias: string }> = [
|
|
7
|
+
{ match: /^claude-opus-5/, alias: "opus" },
|
|
8
|
+
{ match: /^claude-sonnet-5/, alias: "sonnet" },
|
|
9
|
+
{ match: /^claude-haiku/, alias: "haiku" },
|
|
10
|
+
{ match: /^gemini-3(?:\.\d+)?-pro/, alias: "g3p" },
|
|
11
|
+
{ match: /^gemini-3(?:\.\d+)?-flash/, alias: "g3f" },
|
|
12
|
+
{ match: /^deepseek-v4/, alias: "ds4" },
|
|
13
|
+
{ match: /^grok-4/, alias: "grok" },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export interface EffectiveModelAlias { alias: string; source: "user" | "builtin" }
|
|
17
|
+
|
|
18
|
+
function builtinRule(id: string): { match: RegExp; alias: string } | undefined {
|
|
19
|
+
const tail = id.slice(id.lastIndexOf("/") + 1);
|
|
20
|
+
return DEFAULT_MODEL_ALIASES.find(rule => rule.match.test(id) || rule.match.test(tail));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaultAliasesEnabled(config: Pick<OcxConfig, "defaultModelAliases">, provider: OcxProviderConfig): boolean {
|
|
24
|
+
return provider.defaultAliases ?? config.defaultModelAliases ?? false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function effectiveModelAliases(
|
|
28
|
+
config: Pick<OcxConfig, "defaultModelAliases">,
|
|
29
|
+
provider: OcxProviderConfig,
|
|
30
|
+
knownIds: Iterable<string>,
|
|
31
|
+
): Map<string, EffectiveModelAlias> {
|
|
32
|
+
const result = new Map<string, EffectiveModelAlias>();
|
|
33
|
+
for (const [id, alias] of Object.entries(provider.modelAliases ?? {})) {
|
|
34
|
+
if (typeof alias === "string" && MODEL_ALIAS_PATTERN.test(alias)) result.set(id, { alias, source: "user" });
|
|
35
|
+
}
|
|
36
|
+
if (!defaultAliasesEnabled(config, provider)) return result;
|
|
37
|
+
const claims = new Map<string, string[]>();
|
|
38
|
+
for (const id of knownIds) {
|
|
39
|
+
if (result.has(id)) continue;
|
|
40
|
+
const rule = builtinRule(id);
|
|
41
|
+
if (!rule) continue;
|
|
42
|
+
const key = rule.alias.toLowerCase();
|
|
43
|
+
claims.set(key, [...(claims.get(key) ?? []), id]);
|
|
44
|
+
}
|
|
45
|
+
for (const [alias, ids] of claims) {
|
|
46
|
+
if (ids.length !== 1) continue;
|
|
47
|
+
const id = ids[0]!;
|
|
48
|
+
// Catalog drift must skip, never shadow, a native id or explicit user alias.
|
|
49
|
+
if ([...knownIds].some(candidate => candidate.toLowerCase() === alias)) continue;
|
|
50
|
+
if ([...result.values()].some(value => value.alias.toLowerCase() === alias)) continue;
|
|
51
|
+
result.set(id, { alias: builtinRule(id)!.alias, source: "builtin" });
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function resolveModelAlias(
|
|
57
|
+
config: Pick<OcxConfig, "defaultModelAliases">,
|
|
58
|
+
provider: OcxProviderConfig,
|
|
59
|
+
knownIds: Iterable<string>,
|
|
60
|
+
requested: string,
|
|
61
|
+
): string | undefined {
|
|
62
|
+
const needle = requested.toLowerCase();
|
|
63
|
+
return [...effectiveModelAliases(config, provider, knownIds)]
|
|
64
|
+
.find(([, value]) => value.alias.toLowerCase() === needle)?.[0];
|
|
65
|
+
}
|
package/src/providers/derive.ts
CHANGED
|
@@ -392,6 +392,32 @@ function serviceTierModelDefaultsFor(
|
|
|
392
392
|
: undefined;
|
|
393
393
|
}
|
|
394
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Materialize the registry's verbosity opt-out into the provider config at seed/enrich time.
|
|
397
|
+
*
|
|
398
|
+
* The catalog hint pass must not read PROVIDER_REGISTRY: a gather flight captures its registry
|
|
399
|
+
* authority up front and forbids any later read, so consulting the registry per model turned
|
|
400
|
+
* every hint pass into a post-lookup read and dropped a custom-destination flight's own
|
|
401
|
+
* discovery result (tests/codex-gather-authority.test.ts).
|
|
402
|
+
*
|
|
403
|
+
* Registry values go in first so an explicit user entry still wins, matching
|
|
404
|
+
* `applyReasoningSummaryDefaults`. `supportsVerbosity` is the provider-wide default, expanded
|
|
405
|
+
* across the seeded model list so an id discovered later still inherits it through the same
|
|
406
|
+
* Record the hint pass already reads.
|
|
407
|
+
*/
|
|
408
|
+
function applyVerbosityDefaults(prov: OcxProviderConfig, entry: ProviderRegistryEntry | undefined): void {
|
|
409
|
+
if (!entry) return;
|
|
410
|
+
const perModel = entry.modelSupportsVerbosity;
|
|
411
|
+
if (!perModel && entry.supportsVerbosity === undefined) return;
|
|
412
|
+
prov.modelSupportsVerbosity = {
|
|
413
|
+
...(perModel ?? {}),
|
|
414
|
+
...(prov.modelSupportsVerbosity ?? {}),
|
|
415
|
+
};
|
|
416
|
+
if (entry.supportsVerbosity !== undefined) {
|
|
417
|
+
prov.supportsVerbosity ??= entry.supportsVerbosity;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
395
421
|
/**
|
|
396
422
|
* Last-resort enrichment for a provider whose NAME matches no registry id.
|
|
397
423
|
*
|
|
@@ -429,6 +455,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
429
455
|
// destinations, so a templated or overridable base URL cannot be claimed by it.
|
|
430
456
|
enrichReasoningSummariesByDestination(prov);
|
|
431
457
|
applyServiceTierModelDefaults(prov, serviceTierModelDefaultsFor(registryEntryForProviderDestination(prov), prov));
|
|
458
|
+
applyVerbosityDefaults(prov, registryEntryForProviderDestination(prov));
|
|
432
459
|
return;
|
|
433
460
|
}
|
|
434
461
|
const explicitDirectReasoning: DirectReasoningEffortOverrides = {
|
|
@@ -493,6 +520,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
493
520
|
if (prov.preserveResponsesReasoningContent === undefined && entry.preserveResponsesReasoningContent !== undefined) prov.preserveResponsesReasoningContent = entry.preserveResponsesReasoningContent;
|
|
494
521
|
applyReasoningSummaryDefaults(prov, entry.modelSupportsReasoningSummaries);
|
|
495
522
|
applyServiceTierModelDefaults(prov, serviceTierModelDefaultsFor(entry, prov));
|
|
523
|
+
applyVerbosityDefaults(prov, entry);
|
|
496
524
|
// Registry-only repair policy (#938): fill only when the runtime provider has
|
|
497
525
|
// no explicit policy, and deep-clone so saved/user values never alias the
|
|
498
526
|
// registry constant.
|
|
@@ -246,6 +246,7 @@ export function tierObservationContext(
|
|
|
246
246
|
policy: ResolvedFastPolicy,
|
|
247
247
|
fastMode: boolean | undefined,
|
|
248
248
|
callerTier: string | undefined,
|
|
249
|
+
responseTierAuthoritative?: boolean,
|
|
249
250
|
): TierObservationContext {
|
|
250
251
|
return {
|
|
251
252
|
capability: policy.capability,
|
|
@@ -253,6 +254,7 @@ export function tierObservationContext(
|
|
|
253
254
|
fastWire: policy.fastWire,
|
|
254
255
|
demandDecision: fastMode === true ? "force-fast" : fastMode === false ? "force-default" : "inherit",
|
|
255
256
|
...(callerTier !== undefined ? { callerTier } : {}),
|
|
257
|
+
...(responseTierAuthoritative !== undefined ? { responseTierAuthoritative } : {}),
|
|
256
258
|
};
|
|
257
259
|
}
|
|
258
260
|
|
|
@@ -344,7 +346,11 @@ export function createAdapterTierMetadata(
|
|
|
344
346
|
|
|
345
347
|
const responseCanConfirmFast = effectiveFastRequested
|
|
346
348
|
&& context.eligibility === "eligible"
|
|
347
|
-
&& wireValue !== null
|
|
349
|
+
&& wireValue !== null
|
|
350
|
+
// A destination whose echo is not authoritative can neither confirm nor deny Fast. The
|
|
351
|
+
// ChatGPT-internal Codex backend echoes "default" on priority-scheduled turns, so believing
|
|
352
|
+
// it reported every Fast request as `response-declined` (#2558).
|
|
353
|
+
&& context.responseTierAuthoritative !== false;
|
|
348
354
|
return {
|
|
349
355
|
outcome,
|
|
350
356
|
observeResponseServiceTier(value: unknown) {
|