@yansigit/opencodex 2.32.0 → 2.33.1
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 +2 -2
- package/gui/dist/assets/index-CIDo4y4k.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +8 -5
- package/src/adapters/anthropic.ts +20 -6
- package/src/adapters/azure.ts +20 -4
- package/src/adapters/base.ts +3 -1
- package/src/adapters/command-code.ts +131 -17
- package/src/adapters/cursor/envelope-echo.ts +162 -0
- package/src/adapters/cursor/live-transport.ts +4 -2
- package/src/adapters/cursor/native-exec-fs.ts +13 -12
- package/src/adapters/cursor/native-exec-network.ts +3 -5
- package/src/adapters/cursor/native-exec-policy.ts +47 -0
- package/src/adapters/cursor/native-exec-shell.ts +13 -25
- package/src/adapters/cursor/native-exec.ts +18 -10
- package/src/adapters/cursor/protobuf-events.ts +186 -9
- package/src/adapters/cursor/protobuf-request.ts +53 -18
- package/src/adapters/cursor/request-builder.ts +11 -3
- package/src/adapters/cursor/tool-definitions.ts +49 -2
- package/src/adapters/cursor/tool-result-normalize.ts +21 -8
- package/src/adapters/cursor/types.ts +11 -3
- package/src/adapters/cursor.ts +123 -0
- package/src/adapters/google-aistudio-parser.ts +49 -0
- package/src/adapters/google-antigravity-replay.ts +2 -2
- package/src/adapters/google-antigravity-wire.ts +7 -0
- package/src/adapters/google-errors.ts +6 -2
- package/src/adapters/google-http.ts +30 -7
- package/src/adapters/google-truncation.ts +5 -0
- package/src/adapters/google-wire-compiler.ts +38 -6
- package/src/adapters/google.ts +242 -28
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +2 -1
- package/src/bridge.ts +112 -9
- package/src/chat/inbound.ts +15 -0
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +10 -3
- package/src/cli/provider.ts +6 -0
- package/src/cli/status.ts +23 -0
- package/src/codex/auth-api.ts +4 -2
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +12 -12
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/model-metadata.ts +39 -12
- package/src/codex/catalog/parsing.ts +38 -27
- package/src/codex/catalog/provider-fetch.ts +232 -133
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/convergence.ts +5 -0
- package/src/codex/shim.ts +56 -3
- package/src/config/provider-validation.ts +37 -0
- package/src/config.ts +78 -2
- package/src/generated/compatibility-version.json +187 -119
- package/src/generated/model-metadata.ts +3 -0
- package/src/images/loop.ts +37 -6
- package/src/lib/azure-identity.ts +154 -0
- package/src/lib/debug.ts +42 -0
- package/src/lib/errors.ts +14 -0
- package/src/lib/provider-outbound.ts +45 -33
- package/src/lib/provider-tls-profile.ts +309 -0
- package/src/lib/proxy-env.ts +49 -0
- package/src/lib/redact.ts +10 -1
- package/src/oauth/aistudio-native-daemon.ts +62 -0
- package/src/oauth/aistudio-session-sync.ts +95 -0
- package/src/oauth/antigravity-routing.ts +282 -236
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/command-code.ts +5 -16
- package/src/oauth/google-aistudio-auth.ts +98 -0
- package/src/oauth/google-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/key-providers.ts +8 -0
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/login-cli.ts +66 -1
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +6 -0
- package/src/providers/antigravity-quota.ts +3 -1
- package/src/providers/api-keys.ts +2 -1
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/derive.ts +5 -1
- package/src/providers/key-failover.ts +5 -1
- package/src/providers/openai-tiers.ts +5 -0
- package/src/providers/provider-id-rewrite.ts +1 -0
- package/src/providers/quota.ts +148 -50
- package/src/providers/registry.ts +27 -4
- package/src/providers/request-pacing.ts +33 -6
- package/src/providers/xai-transport.ts +21 -0
- package/src/responses/google-provider-options.ts +36 -0
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +11 -0
- package/src/responses/provider-opaque-metadata.ts +3 -3
- package/src/responses/schema.ts +37 -0
- package/src/responses/state.ts +94 -4
- package/src/router.ts +11 -2
- package/src/routing/account-pool/cooldown.ts +8 -0
- package/src/routing/account-pool/index.ts +1 -0
- package/src/server/aistudio-ws-hub.ts +295 -0
- package/src/server/auth-cors.ts +29 -0
- package/src/server/chat-completions.ts +2 -0
- package/src/server/images.ts +19 -35
- package/src/server/index.ts +94 -0
- package/src/server/management/agent-settings-routes.ts +205 -15
- package/src/server/management/combo-routes.ts +6 -0
- package/src/server/management/config-routes.ts +31 -5
- package/src/server/management/logs-usage-routes.ts +11 -5
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +38 -7
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/port-reclaim.ts +19 -1
- package/src/server/request-log-conversation.ts +12 -0
- package/src/server/request-log.ts +23 -1
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +363 -156
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/v2-native-parent-override.ts +59 -0
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-undeclared-tool-guard.ts +90 -8
- package/src/server/ws-bridge.ts +2 -1
- package/src/service.ts +1 -1
- package/src/smoke/fingerprint-cache.ts +133 -0
- package/src/smoke/live-scenarios.ts +33 -0
- package/src/smoke/runner.ts +119 -0
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +18 -1
- package/src/types/request.ts +30 -0
- package/src/types/tools.ts +51 -0
- package/src/types.ts +6 -0
- package/src/usage/command-code-manifest.ts +116 -0
- package/src/usage/cost.ts +2 -2
- package/src/usage/expected-prices.ts +83 -0
- package/src/usage/log.ts +2 -2
- package/src/usage/summary.ts +34 -12
- package/src/web-search/gemini-executor.ts +6 -4
- package/src/web-search/index.ts +16 -8
- package/src/web-search/loop.ts +42 -6
- package/gui/dist/assets/index-BG43zwVe.js +0 -102
- package/gui/dist/assets/index-CiSI-jrP.css +0 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google AI Studio Web-UI (SAPISIDHASH) authorization & header builder.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface GoogleCookieJar {
|
|
6
|
+
sapisid?: string;
|
|
7
|
+
psid?: string;
|
|
8
|
+
ssid?: string;
|
|
9
|
+
hsid?: string;
|
|
10
|
+
sid?: string;
|
|
11
|
+
cookieHeader: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_ORIGIN = "https://aistudio.google.com";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate the Google internal SAPISIDHASH Authorization header value.
|
|
18
|
+
* Formula uses Unix seconds: "SAPISIDHASH <timestamp>_<sha1(timestamp + " " + SAPISID + " " + origin)>".
|
|
19
|
+
*/
|
|
20
|
+
export async function generateSapisidHash(
|
|
21
|
+
sapisid: string,
|
|
22
|
+
origin: string = DEFAULT_ORIGIN,
|
|
23
|
+
timestamp: number = Date.now()
|
|
24
|
+
): Promise<string> {
|
|
25
|
+
// Callers historically passed Date.now() (milliseconds); Google expects Unix seconds.
|
|
26
|
+
const seconds = Math.floor(timestamp > 10_000_000_000 ? timestamp / 1000 : timestamp);
|
|
27
|
+
const raw = `${seconds} ${sapisid} ${origin}`;
|
|
28
|
+
const buf = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(raw));
|
|
29
|
+
const hexHash = Array.from(new Uint8Array(buf))
|
|
30
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
31
|
+
.join("");
|
|
32
|
+
return `SAPISIDHASH ${seconds}_${hexHash}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse a raw cookie string (from browser export, header, or config) into tokens and a normalized string.
|
|
37
|
+
*/
|
|
38
|
+
export function parseGoogleCookieJar(cookieInput: string): GoogleCookieJar {
|
|
39
|
+
const cleanInput = (cookieInput || "").trim();
|
|
40
|
+
const jar: GoogleCookieJar = { cookieHeader: cleanInput };
|
|
41
|
+
if (!cleanInput) return jar;
|
|
42
|
+
if (/[\r\n\u0000]/.test(cleanInput)) return { cookieHeader: "" };
|
|
43
|
+
|
|
44
|
+
const parts = cleanInput.split(";").map((p) => p.trim());
|
|
45
|
+
for (const part of parts) {
|
|
46
|
+
const eqIdx = part.indexOf("=");
|
|
47
|
+
if (eqIdx === -1) continue;
|
|
48
|
+
const name = part.slice(0, eqIdx).trim();
|
|
49
|
+
const value = part.slice(eqIdx + 1).trim();
|
|
50
|
+
|
|
51
|
+
if (name === "SAPISID" || name === "__Secure-3PAPISID") {
|
|
52
|
+
jar.sapisid = value;
|
|
53
|
+
} else if (name === "__Secure-1PSID" || name === "__Secure-3PSID") {
|
|
54
|
+
jar.psid = value;
|
|
55
|
+
} else if (name === "SSID") {
|
|
56
|
+
jar.ssid = value;
|
|
57
|
+
} else if (name === "HSID") {
|
|
58
|
+
jar.hsid = value;
|
|
59
|
+
} else if (name === "SID") {
|
|
60
|
+
jar.sid = value;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return jar;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validate whether a cookie jar contains sufficient authentication credentials.
|
|
69
|
+
*/
|
|
70
|
+
export function validateAiStudioCookies(jar: GoogleCookieJar): { valid: boolean; error?: string } {
|
|
71
|
+
if (!jar.sapisid || !jar.cookieHeader) {
|
|
72
|
+
return {
|
|
73
|
+
valid: false,
|
|
74
|
+
error: "Missing SAPISID cookie required for Google AI Studio authorization.",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { valid: true };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build the HTTP headers required for alkalimakersuite-pa.clients6.google.com calls.
|
|
82
|
+
*/
|
|
83
|
+
export async function buildAiStudioHeaders(
|
|
84
|
+
jar: GoogleCookieJar,
|
|
85
|
+
origin: string = DEFAULT_ORIGIN
|
|
86
|
+
): Promise<Record<string, string>> {
|
|
87
|
+
const sapisid = jar.sapisid || "";
|
|
88
|
+
const authHeader = await generateSapisidHash(sapisid, origin);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
"Authorization": authHeader,
|
|
92
|
+
"Cookie": jar.cookieHeader,
|
|
93
|
+
"X-Goog-AuthUser": "0",
|
|
94
|
+
"Origin": origin,
|
|
95
|
+
"Referer": origin.endsWith("/") ? origin : `${origin}/`,
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -51,6 +51,28 @@ interface GoogleTokenPayload {
|
|
|
51
51
|
id_token?: unknown;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const TERMINAL_OAUTH_ERRORS = new Set([
|
|
55
|
+
"access_denied",
|
|
56
|
+
"expired_token",
|
|
57
|
+
"invalid_grant",
|
|
58
|
+
"refresh_token_reused",
|
|
59
|
+
"refresh_token_revoked",
|
|
60
|
+
"revoked",
|
|
61
|
+
"revoked_token",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
export class AntigravityTokenRequestError extends Error {
|
|
65
|
+
readonly httpStatus: number;
|
|
66
|
+
readonly oauthError?: string;
|
|
67
|
+
|
|
68
|
+
constructor(httpStatus: number, oauthError?: string) {
|
|
69
|
+
super(`Antigravity token request failed: ${httpStatus}`);
|
|
70
|
+
this.name = "AntigravityTokenRequestError";
|
|
71
|
+
this.httpStatus = httpStatus;
|
|
72
|
+
if (oauthError) this.oauthError = oauthError;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
|
|
55
77
|
const part = token.split(".")[1];
|
|
56
78
|
if (!part) return undefined;
|
|
@@ -75,8 +97,17 @@ async function postToken(body: Record<string, string>, signal?: AbortSignal): Pr
|
|
|
75
97
|
signal: requestSignal(signal),
|
|
76
98
|
});
|
|
77
99
|
if (!response.ok) {
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
let oauthError: string | undefined;
|
|
101
|
+
if (response.status === 400 || response.status === 401) {
|
|
102
|
+
try {
|
|
103
|
+
const body = await response.json() as { error?: unknown };
|
|
104
|
+
const candidate = typeof body.error === "string" ? body.error.trim().toLowerCase() : "";
|
|
105
|
+
if (TERMINAL_OAUTH_ERRORS.has(candidate)) oauthError = candidate;
|
|
106
|
+
} catch {
|
|
107
|
+
/* Keep status-only errors for malformed or non-JSON responses. */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
throw new AntigravityTokenRequestError(response.status, oauthError);
|
|
80
111
|
}
|
|
81
112
|
return (await response.json()) as GoogleTokenPayload;
|
|
82
113
|
}
|
|
@@ -221,7 +252,11 @@ export async function loginAntigravity(ctrl: OAuthController, opts?: { forceAcco
|
|
|
221
252
|
return new AntigravityOAuthFlow(ctrl, opts).login();
|
|
222
253
|
}
|
|
223
254
|
|
|
224
|
-
export async function refreshAntigravityToken(
|
|
255
|
+
export async function refreshAntigravityToken(
|
|
256
|
+
refreshToken: string,
|
|
257
|
+
signal?: AbortSignal,
|
|
258
|
+
previousCredential?: OAuthCredentials,
|
|
259
|
+
): Promise<OAuthCredentials> {
|
|
225
260
|
if (!refreshToken) throw new Error("Antigravity credentials are expired and do not include a refresh token");
|
|
226
261
|
const payload = await postToken({
|
|
227
262
|
grant_type: "refresh_token",
|
|
@@ -230,8 +265,10 @@ export async function refreshAntigravityToken(refreshToken: string, signal?: Abo
|
|
|
230
265
|
refresh_token: refreshToken,
|
|
231
266
|
}, signal);
|
|
232
267
|
const creds = credentialsFromPayload(payload, refreshToken);
|
|
233
|
-
//
|
|
234
|
-
|
|
268
|
+
// Project discovery is onboarding work, not ordinary token renewal. Preserve the existing
|
|
269
|
+
// account-scoped project id and avoid an extra authenticated CCA call when it is present.
|
|
270
|
+
const projectId = previousCredential?.projectId
|
|
271
|
+
?? await discoverAntigravityProject(creds.access, signal).catch(() => undefined);
|
|
235
272
|
return projectId ? { ...creds, projectId } : creds;
|
|
236
273
|
}
|
|
237
274
|
|
package/src/oauth/index.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthr
|
|
|
10
10
|
import { loginKimi, refreshKimiToken } from "./kimi";
|
|
11
11
|
import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
|
|
12
12
|
import { loginChatGPT, refreshChatGPTToken } from "./chatgpt";
|
|
13
|
-
import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
|
|
13
|
+
import { AntigravityTokenRequestError, loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
|
|
14
14
|
import { loginCursor, refreshCursorToken } from "./cursor";
|
|
15
15
|
import { loginGithubCopilot, refreshGithubCopilotToken, validateCopilotApiBaseUrl } from "./github-copilot";
|
|
16
16
|
import { loginCommandCode, refreshCommandCodeToken } from "./command-code";
|
|
@@ -222,6 +222,7 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
|
|
|
222
222
|
refresh: refreshAntigravityToken,
|
|
223
223
|
providerConfig: oauthConfig("google-antigravity"),
|
|
224
224
|
defaultModel: oauthDefaultModel("google-antigravity"),
|
|
225
|
+
defaultRefreshPolicy: "lazy-only",
|
|
225
226
|
},
|
|
226
227
|
cursor: {
|
|
227
228
|
login: (ctrl, opts) => loginCursor(ctrl, undefined, { forceLogin: opts?.forceLogin }),
|
|
@@ -466,8 +467,16 @@ export async function getValidAccessTokenSnapshot(provider: string): Promise<OAu
|
|
|
466
467
|
return resolveAccessSnapshotForAccount(provider, set.activeAccountId);
|
|
467
468
|
}
|
|
468
469
|
|
|
470
|
+
/** Resolve a specific stored account without consulting or changing the active account. */
|
|
471
|
+
export async function getValidAccessTokenSnapshotForAccount(
|
|
472
|
+
provider: string,
|
|
473
|
+
accountId: string,
|
|
474
|
+
): Promise<OAuthAccessSnapshot> {
|
|
475
|
+
return resolveAccessSnapshotForAccount(provider, accountId);
|
|
476
|
+
}
|
|
477
|
+
|
|
469
478
|
/** Providers whose upstream-401 replay path may force a snapshot refresh. */
|
|
470
|
-
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro"]);
|
|
479
|
+
const FORCE_REFRESH_PROVIDERS = new Set(["xai", "github-copilot", "kiro", "google-antigravity"]);
|
|
471
480
|
|
|
472
481
|
export async function forceRefreshOAuthAccessSnapshot(
|
|
473
482
|
rejected: OAuthAccessSnapshot,
|
|
@@ -501,6 +510,9 @@ function isTerminalRefreshError(err: unknown): boolean {
|
|
|
501
510
|
|| msg.includes("expired_token");
|
|
502
511
|
}
|
|
503
512
|
function terminal(error:unknown):boolean{
|
|
513
|
+
if (error instanceof AntigravityTokenRequestError) {
|
|
514
|
+
return (error.httpStatus === 400 || error.httpStatus === 401) && error.oauthError !== undefined;
|
|
515
|
+
}
|
|
504
516
|
if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
|
|
505
517
|
if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
|
|
506
518
|
if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
|
|
@@ -630,7 +642,7 @@ export async function refreshAnthropicAccountWithLock(
|
|
|
630
642
|
writeOAuthRefreshIntent(provider, accountId, generation, now(), deps.flight?.flightId);
|
|
631
643
|
if (deps.signal?.aborted) throw deps.signal.reason;
|
|
632
644
|
if (deps.flight) deps.flight.dispatched = true;
|
|
633
|
-
const fresh = merged(await def.refresh(stored.refresh, deps.signal), stored);
|
|
645
|
+
const fresh = merged(await def.refresh(stored.refresh, deps.signal, stored), stored);
|
|
634
646
|
const outcome = await mergeAccountCredential(provider, accountId, fresh, {
|
|
635
647
|
expectedGeneration: generation,
|
|
636
648
|
afterPrePersistRead: deps.afterPrePersistRead,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OcxProviderConfig } from "../types";
|
|
2
2
|
import { deriveKeyLoginMap, enrichProviderFromRegistry, type DerivedKeyLoginProvider } from "../providers/derive";
|
|
3
3
|
import { resolveProviderModelDiscoveryUrl } from "../providers/model-discovery";
|
|
4
|
+
import { parseGoogleCookieJar, validateAiStudioCookies } from "./google-aistudio-auth";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* API-key "login" providers: not OAuth — the flow opens the provider's dashboard so the user can
|
|
@@ -86,6 +87,13 @@ export async function validateApiKey(
|
|
|
86
87
|
return "unknown";
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
if (provider.adapter === "google" && provider.googleMode === "ai-studio-web") {
|
|
91
|
+
const jar = parseGoogleCookieJar(key);
|
|
92
|
+
const val = validateAiStudioCookies(jar);
|
|
93
|
+
if (!val.valid) return false;
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
|
|
89
97
|
if (provider.adapter === "google" && (provider.googleMode ?? "ai-studio") === "ai-studio") {
|
|
90
98
|
// Generative Language API rejects Bearer-wrapped API keys; probe models.list with the
|
|
91
99
|
// documented x-goog-api-key header instead (pageSize=1 — validation only needs a 200).
|
package/src/oauth/kimi.ts
CHANGED
|
@@ -209,7 +209,15 @@ async function pollForToken(deviceCode: string, intervalMs: number, expiresInMs:
|
|
|
209
209
|
|
|
210
210
|
export async function loginKimi(ctrl: OAuthController): Promise<OAuthCredentials> {
|
|
211
211
|
const device = await requestDeviceAuthorization();
|
|
212
|
-
|
|
212
|
+
// `deviceCode` carries the human-typed user code, matching the other device
|
|
213
|
+
// providers. It is also what tells the management login route this is a device
|
|
214
|
+
// flow, so a provider-supplied verification URI is no longer handed to a local
|
|
215
|
+
// browser spawn — the URL stays visible and copyable in every login surface.
|
|
216
|
+
ctrl.onAuth?.({
|
|
217
|
+
url: device.verificationUriComplete,
|
|
218
|
+
instructions: `Enter code: ${device.userCode}`,
|
|
219
|
+
deviceCode: device.userCode,
|
|
220
|
+
});
|
|
213
221
|
return pollForToken(device.deviceCode, device.intervalMs, device.expiresInMs, ctrl.signal);
|
|
214
222
|
}
|
|
215
223
|
|
package/src/oauth/login-cli.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { codexAccountNamespaceProviderCollisionError } from "../codex/account-na
|
|
|
15
15
|
const LIVE_RELOAD_PROVIDERS = new Set<string>([
|
|
16
16
|
...listOAuthProviders(),
|
|
17
17
|
...Object.keys(KEY_LOGIN_PROVIDERS),
|
|
18
|
+
"google-aistudio",
|
|
18
19
|
]);
|
|
19
20
|
|
|
20
21
|
export function runningProxyUpdateHeaders(): Headers {
|
|
@@ -65,16 +66,80 @@ export function warnIfLiveReloadSkipped(result: LocalProviderReloadResult | null
|
|
|
65
66
|
|
|
66
67
|
export async function handleLogin(provider?: string): Promise<void> {
|
|
67
68
|
const name = (provider ?? "").trim().toLowerCase();
|
|
69
|
+
if (name === "google-aistudio" || name === "aistudio" || name === "gemini-aistudio") {
|
|
70
|
+
return handleAiStudioBridgeLogin();
|
|
71
|
+
}
|
|
68
72
|
if (isPublicOAuthProvider(name)) return handleOAuthLogin(name);
|
|
69
73
|
if (isKeyLoginProvider(name)) return handleKeyLogin(name);
|
|
70
74
|
console.error(
|
|
71
75
|
`Usage: ocx login <provider>\n` +
|
|
72
|
-
` OAuth
|
|
76
|
+
` OAuth / Web: ${[...listOAuthProviders(), "google-aistudio"].join(", ")}\n` +
|
|
73
77
|
` API-key login: ${Object.keys(KEY_LOGIN_PROVIDERS).join(", ")}`,
|
|
74
78
|
);
|
|
75
79
|
process.exit(1);
|
|
76
80
|
}
|
|
77
81
|
|
|
82
|
+
async function handleAiStudioBridgeLogin(): Promise<void> {
|
|
83
|
+
const live = await findLiveProxy();
|
|
84
|
+
const port = live?.port ?? 10100;
|
|
85
|
+
const bridgeUrl = "http://127.0.0.1:" + port + "/aistudio/bridge";
|
|
86
|
+
|
|
87
|
+
console.log("\n🌐 Google AI Studio Sign-In & Session Setup:");
|
|
88
|
+
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");
|
|
91
|
+
|
|
92
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
93
|
+
try {
|
|
94
|
+
const choice = await new Promise<string>((res) => {
|
|
95
|
+
rl.question("Paste Session Token (or press Enter for native window): ", (ans) => res(ans.trim()));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (choice.length > 20) {
|
|
99
|
+
const { saveAiStudioSessionFromToken } = await import("./aistudio-session-sync");
|
|
100
|
+
saveAiStudioSessionFromToken(choice);
|
|
101
|
+
console.log("\n✅ Session token imported successfully! Saved to ~/.opencodex/aistudio-session.json");
|
|
102
|
+
} else if (process.platform === "darwin") {
|
|
103
|
+
const { getAiStudioNativeDaemonSourcePath } = await import("./aistudio-native-daemon");
|
|
104
|
+
const swiftSrc = getAiStudioNativeDaemonSourcePath();
|
|
105
|
+
console.log("\n🚀 Opening native Google AI Studio login window...");
|
|
106
|
+
const proc = Bun.spawn(["swift", swiftSrc, "--login"], {
|
|
107
|
+
stdout: "inherit",
|
|
108
|
+
stderr: "inherit",
|
|
109
|
+
});
|
|
110
|
+
const code = await proc.exited;
|
|
111
|
+
if (code === 0) {
|
|
112
|
+
console.log("\n✅ Google AI Studio authenticated successfully! Session saved to ~/.opencodex/aistudio-session.json");
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
console.log("\n🌐 Opening bridge page in your browser: " + bridgeUrl);
|
|
116
|
+
openUrl(bridgeUrl);
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
rl.close();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const config = loadConfig();
|
|
123
|
+
if (!config.providers["google-aistudio"]) {
|
|
124
|
+
config.providers["google-aistudio"] = {
|
|
125
|
+
adapter: "google",
|
|
126
|
+
googleMode: "ai-studio-web",
|
|
127
|
+
baseUrl: "https://alkalimakersuite-pa.clients6.google.com",
|
|
128
|
+
authMode: "local",
|
|
129
|
+
liveModels: false,
|
|
130
|
+
defaultModel: "gemini-3.7-flash",
|
|
131
|
+
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);
|
|
134
|
+
console.log("\n ✓ Configured 'google-aistudio' in ~/.opencodex/config.json");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
openUrl(bridgeUrl);
|
|
138
|
+
const reload = await notifyRunningProxy("google-aistudio");
|
|
139
|
+
console.log("\n✅ Ready! Use models with 'google-aistudio' provider in your coding agents.");
|
|
140
|
+
warnIfLiveReloadSkipped(reload);
|
|
141
|
+
}
|
|
142
|
+
|
|
78
143
|
async function handleOAuthLogin(name: string): Promise<void> {
|
|
79
144
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
80
145
|
try {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Whether a login should open a browser on the machine running the proxy.
|
|
5
|
+
*
|
|
6
|
+
* Both login routes share this so the two surfaces cannot drift: a per-request
|
|
7
|
+
* `openBrowser` beats the persisted setting, and the persisted setting beats
|
|
8
|
+
* the historical default. It lives in `src/oauth/` rather than beside either
|
|
9
|
+
* route because `src/codex/auth-api.ts` and
|
|
10
|
+
* `src/server/management/oauth-account-routes.ts` both already import from
|
|
11
|
+
* here, so no new edge is added to the import graph.
|
|
12
|
+
*
|
|
13
|
+
* The default is the important part. An operator who upgrades and configures
|
|
14
|
+
* nothing must see exactly what they saw before, so only an explicit `false`
|
|
15
|
+
* declines — `undefined`, `true`, and a malformed value all open.
|
|
16
|
+
*
|
|
17
|
+
* A malformed request field is ignored rather than rejected on purpose: this is
|
|
18
|
+
* a display preference, and no login should fail because of one.
|
|
19
|
+
*/
|
|
20
|
+
export function shouldOpenBrowserForLogin(
|
|
21
|
+
requested: unknown,
|
|
22
|
+
config: Pick<OcxConfig, "oauthOpenBrowser">,
|
|
23
|
+
): boolean {
|
|
24
|
+
if (typeof requested === "boolean") return requested;
|
|
25
|
+
return config.oauthOpenBrowser !== false;
|
|
26
|
+
}
|
package/src/oauth/store.ts
CHANGED
|
@@ -593,9 +593,11 @@ export async function upsertCredentialByIdentity(
|
|
|
593
593
|
|
|
594
594
|
/** Remove the ACTIVE account; remaining accounts promote the first one. */
|
|
595
595
|
export async function removeCredential(provider: string): Promise<void> {
|
|
596
|
+
let removedAccountId: string | undefined;
|
|
596
597
|
await mutateStore(store => {
|
|
597
598
|
const set = store[provider];
|
|
598
599
|
if (!set) return;
|
|
600
|
+
removedAccountId = set.activeAccountId;
|
|
599
601
|
set.accounts = set.accounts.filter(a => a.id !== set.activeAccountId);
|
|
600
602
|
if (set.accounts.length === 0) {
|
|
601
603
|
delete store[provider];
|
|
@@ -603,6 +605,10 @@ export async function removeCredential(provider: string): Promise<void> {
|
|
|
603
605
|
}
|
|
604
606
|
set.activeAccountId = set.accounts[0]!.id;
|
|
605
607
|
}, [provider]);
|
|
608
|
+
if (provider === "google-antigravity" && removedAccountId) {
|
|
609
|
+
const { clearAntigravityRoutingStateForAccount } = await import("./antigravity-routing");
|
|
610
|
+
clearAntigravityRoutingStateForAccount(removedAccountId);
|
|
611
|
+
}
|
|
606
612
|
}
|
|
607
613
|
|
|
608
614
|
// ---------------------------------------------------------------------------
|
|
@@ -90,7 +90,9 @@ function candidateModelName(record: Record<string, unknown>): string {
|
|
|
90
90
|
?? record.modelName
|
|
91
91
|
?? record.model
|
|
92
92
|
?? record.name
|
|
93
|
-
?? record.displayName
|
|
93
|
+
?? record.displayName
|
|
94
|
+
?? record.modelFamily
|
|
95
|
+
?? record.model_family;
|
|
94
96
|
return typeof explicit === "string" ? explicit.toLowerCase() : "";
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import { saveConfigPreservingClaudeCode } from "../config";
|
|
11
|
+
import { isAzureIdentityProvider } from "../config/provider-validation";
|
|
11
12
|
import type { OcxConfig, OcxProviderConfig } from "../types";
|
|
12
13
|
|
|
13
14
|
export interface ProviderApiKeyInfo {
|
|
@@ -36,7 +37,7 @@ export function apiKeyPoolEntryId(key: string): string {
|
|
|
36
37
|
|
|
37
38
|
/** True for providers whose upstream auth is a configured API key (not oauth/forward). */
|
|
38
39
|
export function isKeyAuthProvider(provider: OcxProviderConfig): boolean {
|
|
39
|
-
return provider.authMode !== "oauth" && provider.authMode !== "forward";
|
|
40
|
+
return !isAzureIdentityProvider(provider) && provider.authMode !== "oauth" && provider.authMode !== "forward";
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/** Trim and reject blank / CRLF-bearing secrets. Shared by pool writes and OAuth upsert. */
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../codex/catalog/native-models";
|
|
2
|
+
import { redactSecretString } from "../lib/redact";
|
|
3
|
+
|
|
4
|
+
const RESERVED_OBJECT_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
5
|
+
|
|
6
|
+
function positiveSafeInteger(value: unknown): value is number {
|
|
7
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a client-facing soft compaction budget without changing any hard
|
|
12
|
+
* model limit. Configuration and measured input ceilings may only lower the
|
|
13
|
+
* default 90% envelope.
|
|
14
|
+
*/
|
|
15
|
+
export function clampAutoCompactTokenLimit(
|
|
16
|
+
contextWindow: number,
|
|
17
|
+
maxInputTokens?: number,
|
|
18
|
+
configuredLimit?: number,
|
|
19
|
+
): number {
|
|
20
|
+
const candidates = [Math.floor(contextWindow * 0.9), contextWindow];
|
|
21
|
+
if (positiveSafeInteger(maxInputTokens)) candidates.push(maxInputTokens);
|
|
22
|
+
if (positiveSafeInteger(configuredLimit)) candidates.push(configuredLimit);
|
|
23
|
+
return Math.min(...candidates);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AutoCompactBudgetValidationOptions = Readonly<{
|
|
27
|
+
/** PATCH accepts null for whole-map and per-key deletion. */
|
|
28
|
+
allowTombstones?: boolean;
|
|
29
|
+
/** The canonical ChatGPT provider accepts only exact supported native ids. */
|
|
30
|
+
requireNativeIds?: boolean;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
/** Shared config/load/management boundary for per-model soft budgets. */
|
|
34
|
+
export function modelAutoCompactTokenLimitsConfigError(
|
|
35
|
+
value: unknown,
|
|
36
|
+
options: AutoCompactBudgetValidationOptions = {},
|
|
37
|
+
): string | null {
|
|
38
|
+
const field = "modelAutoCompactTokenLimits";
|
|
39
|
+
if (value === undefined || (options.allowTombstones && value === null)) return null;
|
|
40
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
41
|
+
return `${field} must be a plain object${options.allowTombstones ? " or null" : ""}`;
|
|
42
|
+
}
|
|
43
|
+
const prototype = Object.getPrototypeOf(value);
|
|
44
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
45
|
+
return `${field} must be a plain object with own properties`;
|
|
46
|
+
}
|
|
47
|
+
for (const [modelId, entry] of Object.entries(value as Record<string, unknown>)) {
|
|
48
|
+
const safeModelId = JSON.stringify(redactSecretString(modelId));
|
|
49
|
+
if (!modelId.trim()) return `${field} keys must be nonblank model ids`;
|
|
50
|
+
if (RESERVED_OBJECT_KEYS.has(modelId)) {
|
|
51
|
+
return `${field} key ${safeModelId} is reserved`;
|
|
52
|
+
}
|
|
53
|
+
if (options.requireNativeIds
|
|
54
|
+
&& (modelId.includes("/") || !SUPPORTED_NATIVE_OPENAI_SLUGS.has(modelId))) {
|
|
55
|
+
return `${field} key ${safeModelId} must be an exact supported native model id`;
|
|
56
|
+
}
|
|
57
|
+
if (options.allowTombstones && entry === null) continue;
|
|
58
|
+
if (!positiveSafeInteger(entry)) {
|
|
59
|
+
return `${field}[${safeModelId}] must be a positive safe integer${
|
|
60
|
+
options.allowTombstones ? " or null" : ""
|
|
61
|
+
}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
package/src/providers/derive.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CodexAccountMode, OcxProviderConfig } from "../types";
|
|
2
|
+
import { isAzureIdentityProvider } from "../config/provider-validation";
|
|
2
3
|
import { cloneFastWire } from "./fastwire";
|
|
3
4
|
import {
|
|
4
5
|
PROVIDER_REGISTRY,
|
|
@@ -48,7 +49,7 @@ export interface DerivedKeyLoginProvider {
|
|
|
48
49
|
thinkingBudgetModels?: string[];
|
|
49
50
|
escapeBuiltinToolNames?: boolean;
|
|
50
51
|
openaiChatEofTolerance?: boolean;
|
|
51
|
-
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist";
|
|
52
|
+
googleMode?: "ai-studio" | "vertex" | "cloud-code-assist" | "ai-studio-web";
|
|
52
53
|
project?: string;
|
|
53
54
|
location?: string;
|
|
54
55
|
}
|
|
@@ -226,6 +227,7 @@ export function providerConfigSeed(entry: ProviderRegistryEntry): OcxProviderCon
|
|
|
226
227
|
...(entry.modelSuffixBracketStrip !== undefined ? { modelSuffixBracketStrip: entry.modelSuffixBracketStrip } : {}),
|
|
227
228
|
...(entry.staticHeaders ? { headers: { ...entry.staticHeaders } } : {}),
|
|
228
229
|
...(entry.defaultModel ? { defaultModel: entry.defaultModel } : {}),
|
|
230
|
+
...(entry.requestPacing ? { requestPacing: structuredClone(entry.requestPacing) } : {}),
|
|
229
231
|
...(entry.models ? { models: [...entry.models] } : {}),
|
|
230
232
|
...(liveModels !== undefined ? { liveModels } : {}),
|
|
231
233
|
...(entry.contextWindow !== undefined ? { contextWindow: entry.contextWindow } : {}),
|
|
@@ -416,6 +418,7 @@ export function hasLegacyClinePassReasoningEfforts(name: string, prov: OcxProvid
|
|
|
416
418
|
}
|
|
417
419
|
|
|
418
420
|
export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig): void {
|
|
421
|
+
if (isAzureIdentityProvider(prov)) prov.liveModels = false;
|
|
419
422
|
const entry = PROVIDER_REGISTRY.find(row => row.id === name);
|
|
420
423
|
if (!entry || !providerMatchesRegistryTransportWithStaticGuards(name, prov)) {
|
|
421
424
|
// Name lookup failed, but the row may still point at a vendor route we know. #1100 was
|
|
@@ -438,6 +441,7 @@ export function enrichProviderFromRegistry(name: string, prov: OcxProviderConfig
|
|
|
438
441
|
repairStaticModelCatalogProvider(name, prov);
|
|
439
442
|
if (prov.apiKeyTransport === undefined && seed.apiKeyTransport !== undefined) prov.apiKeyTransport = seed.apiKeyTransport;
|
|
440
443
|
if (!prov.defaultModel && seed.defaultModel) prov.defaultModel = seed.defaultModel;
|
|
444
|
+
if (prov.requestPacing === undefined && seed.requestPacing) prov.requestPacing = structuredClone(seed.requestPacing);
|
|
441
445
|
if (prov.responsesPath === undefined && seed.responsesPath !== undefined) prov.responsesPath = seed.responsesPath;
|
|
442
446
|
// Fill mode only when absent: an explicit persisted `direct` must never be overwritten.
|
|
443
447
|
if (prov.codexAccountMode === undefined && seed.codexAccountMode !== undefined) prov.codexAccountMode = seed.codexAccountMode;
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Modelled after src/codex/routing.ts cooldown logic but scoped to plain API-key pools.
|
|
10
10
|
*/
|
|
11
11
|
import { saveConfigPreservingClaudeCode } from "../config";
|
|
12
|
+
import { isAzureIdentityProvider } from "../config/provider-validation";
|
|
12
13
|
import type { OcxConfig, OcxProviderConfig, RateLimitRetryPolicy } from "../types";
|
|
13
14
|
import { resolveProviderTransport, type OcxProviderTransport } from "./xai-transport";
|
|
14
15
|
import { sweepExpiredOnWrite } from "../lib/state-store-sweeper";
|
|
@@ -84,6 +85,7 @@ function isKeyInCooldown(providerName: string, keyId: string, now = Date.now()):
|
|
|
84
85
|
* Returns true only for key-auth providers with 2+ pool entries.
|
|
85
86
|
*/
|
|
86
87
|
export function hasKeyPoolFailover(provider: OcxProviderConfig): boolean {
|
|
88
|
+
if (isAzureIdentityProvider(provider)) return false;
|
|
87
89
|
if (provider.authMode === "oauth" || provider.authMode === "forward") return false;
|
|
88
90
|
return (provider.apiKeyPool?.length ?? 0) >= 2;
|
|
89
91
|
}
|
|
@@ -96,7 +98,7 @@ export function hasKeyPoolFailover(provider: OcxProviderConfig): boolean {
|
|
|
96
98
|
* callers never re-check fields.
|
|
97
99
|
*/
|
|
98
100
|
export function rateLimitRetryPolicyFor(
|
|
99
|
-
provider: Pick<OcxProviderConfig, "retryOn429" | "authMode">,
|
|
101
|
+
provider: Pick<OcxProviderConfig, "retryOn429" | "authMode" | "adapter" | "azureCredential">,
|
|
100
102
|
): Required<RateLimitRetryPolicy> | null {
|
|
101
103
|
const policy = provider.retryOn429;
|
|
102
104
|
if (!policy || policy.enabled === false) return null;
|
|
@@ -105,6 +107,7 @@ export function rateLimitRetryPolicyFor(
|
|
|
105
107
|
// same token, local runtimes have no remote key to preserve, and unknown/custom values are
|
|
106
108
|
// rejected rather than guessed at.
|
|
107
109
|
if (provider.authMode !== undefined && provider.authMode !== "key") return null;
|
|
110
|
+
if (isAzureIdentityProvider(provider)) return null;
|
|
108
111
|
return {
|
|
109
112
|
enabled: policy.enabled ?? DEFAULT_RATE_LIMIT_RETRY.enabled,
|
|
110
113
|
attempts: policy.attempts ?? DEFAULT_RATE_LIMIT_RETRY.attempts,
|
|
@@ -153,6 +156,7 @@ export function rotateKeyOn429(
|
|
|
153
156
|
): OcxProviderConfig | null {
|
|
154
157
|
const provider = config.providers[providerName];
|
|
155
158
|
if (!provider) return null;
|
|
159
|
+
if (isAzureIdentityProvider(provider)) return null;
|
|
156
160
|
if (provider.authMode === "oauth" || provider.authMode === "forward") return null;
|
|
157
161
|
|
|
158
162
|
const pool = provider.apiKeyPool;
|
|
@@ -237,6 +237,7 @@ function hasKnownLegacyOpenAiReference(config: OcxConfig): boolean {
|
|
|
237
237
|
|| matchesList(config.subagentModels)
|
|
238
238
|
|| (config.subagentRoles ?? []).some(role => matches(role.model))
|
|
239
239
|
|| matches(config.injectionModel)
|
|
240
|
+
|| matches(config.v2NativeParentOverride?.model)
|
|
240
241
|
|| matches(config.shadowCallIntercept?.model)
|
|
241
242
|
|| matches(config.webSearchSidecar?.model)
|
|
242
243
|
|| matches(config.visionSidecar?.model)
|
|
@@ -260,6 +261,9 @@ function rewriteLegacyOpenAiReferences(config: OcxConfig, warnings: string[]): v
|
|
|
260
261
|
}
|
|
261
262
|
}
|
|
262
263
|
if (config.injectionModel) config.injectionModel = rewriteLegacyOpenAiSelectedId(config.injectionModel);
|
|
264
|
+
if (config.v2NativeParentOverride?.model) {
|
|
265
|
+
config.v2NativeParentOverride.model = rewriteLegacyOpenAiSelectedId(config.v2NativeParentOverride.model);
|
|
266
|
+
}
|
|
263
267
|
if (config.shadowCallIntercept?.model) {
|
|
264
268
|
config.shadowCallIntercept.model = rewriteLegacyOpenAiSelectedId(config.shadowCallIntercept.model);
|
|
265
269
|
}
|
|
@@ -305,6 +309,7 @@ function isKnownLegacyValuePath(path: readonly string[]): boolean {
|
|
|
305
309
|
if (/^providers\.(openai|openai-multi)\.selectedModels\.\d+$/.test(joined)) return true;
|
|
306
310
|
return new Set([
|
|
307
311
|
"injectionModel",
|
|
312
|
+
"v2NativeParentOverride.model",
|
|
308
313
|
"shadowCallIntercept.model",
|
|
309
314
|
"webSearchSidecar.model",
|
|
310
315
|
"visionSidecar.model",
|
|
@@ -82,6 +82,7 @@ export function rewriteProviderReferences(config: OcxConfig, from: string, to: s
|
|
|
82
82
|
|
|
83
83
|
const scalarOwners: Array<[Record<string, unknown> | undefined, string]> = [
|
|
84
84
|
[config as unknown as Record<string, unknown>, "injectionModel"],
|
|
85
|
+
[config.v2NativeParentOverride as Record<string, unknown> | undefined, "model"],
|
|
85
86
|
[config.shadowCallIntercept as Record<string, unknown> | undefined, "model"],
|
|
86
87
|
[config.webSearchSidecar as Record<string, unknown> | undefined, "model"],
|
|
87
88
|
[config.visionSidecar as Record<string, unknown> | undefined, "model"],
|