@yansigit/opencodex 2.32.0 → 2.33.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 +2 -2
- package/gui/dist/assets/index-DKLr4LTE.js +102 -0
- package/gui/dist/assets/index-DrSQdTRd.css +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +6 -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 +40 -7
- package/src/adapters/cursor/live-transport.ts +1 -1
- package/src/adapters/cursor/protobuf-events.ts +158 -7
- package/src/adapters/cursor/protobuf-request.ts +33 -15
- package/src/adapters/cursor/request-builder.ts +4 -3
- package/src/adapters/cursor/tool-definitions.ts +27 -1
- package/src/adapters/cursor/types.ts +4 -3
- package/src/adapters/cursor.ts +9 -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 +148 -26
- package/src/adapters/kiro-tools.ts +20 -9
- package/src/adapters/openai-chat.ts +9 -0
- package/src/adapters/openai-responses.ts +1 -1
- package/src/bridge.ts +112 -9
- package/src/claude/context-windows.ts +16 -9
- package/src/cli/doctor.ts +2 -2
- package/src/cli/index.ts +9 -2
- 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 +198 -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 +120 -96
- 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/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-antigravity.ts +42 -5
- package/src/oauth/index.ts +15 -3
- package/src/oauth/kimi.ts +9 -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 +4 -0
- 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 +59 -13
- package/src/providers/registry.ts +3 -1
- 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 +8 -2
- package/src/server/auth-cors.ts +28 -0
- package/src/server/images.ts +19 -35
- 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/model-rows.ts +4 -0
- package/src/server/management/oauth-account-routes.ts +25 -4
- package/src/server/management/provider-routes.ts +113 -15
- package/src/server/management/routing-profile-routes.ts +3 -0
- package/src/server/request-log.ts +21 -0
- package/src/server/responses/agent-task-recovery.ts +1 -1
- package/src/server/responses/compact.ts +30 -1
- package/src/server/responses/core.ts +359 -153
- package/src/server/responses/empty-completion-guard.ts +35 -6
- package/src/server/responses/fetch-helpers.ts +18 -5
- 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/service.ts +1 -1
- package/src/types/config.ts +16 -1
- package/src/types/provider.ts +16 -0
- package/src/types/request.ts +28 -0
- package/src/types/tools.ts +27 -0
- package/src/types.ts +6 -0
- package/src/web-search/gemini-executor.ts +6 -4
- 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
package/src/codex/shim.ts
CHANGED
|
@@ -603,6 +603,47 @@ function backupPathFor(path: string): string {
|
|
|
603
603
|
return ext ? `${path.slice(0, -ext.length)}.opencodex-real${ext}` : `${path}.opencodex-real`;
|
|
604
604
|
}
|
|
605
605
|
|
|
606
|
+
/**
|
|
607
|
+
* True when a Codex binary lives inside a version manager's install tree.
|
|
608
|
+
*
|
|
609
|
+
* These trees are rewritten in place on upgrade, which destroys both the shim
|
|
610
|
+
* and the sibling .opencodex-real backup it restores from (#2412). The tempting
|
|
611
|
+
* repair — adopt the newly installed binary as a fresh original — is wrong
|
|
612
|
+
* twice: it records a provenance that never happened, and the next upgrade wipes
|
|
613
|
+
* it again, so the repair silently un-repairs on the version manager's schedule.
|
|
614
|
+
*
|
|
615
|
+
* Scope is the three managers named in the report. nvm/fnm/npm-prefix are
|
|
616
|
+
* deliberately excluded: a false positive here refuses a restore that would
|
|
617
|
+
* otherwise be correct.
|
|
618
|
+
*/
|
|
619
|
+
export function isVersionManagerOwnedCodexPath(path: string): boolean {
|
|
620
|
+
const normalized = path.replace(/\\/g, "/").toLowerCase();
|
|
621
|
+
return normalized.includes("/mise/installs/")
|
|
622
|
+
|| normalized.includes("/mise/shims/")
|
|
623
|
+
|| normalized.includes("/.asdf/installs/")
|
|
624
|
+
|| normalized.includes("/.asdf/shims/")
|
|
625
|
+
|| normalized.includes("/.volta/");
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Why auto-restore refused, in the operator's own terms. Auto-restore used to
|
|
630
|
+
* return a bare `{ status: "ineligible" }`, and the CLI warns only when a
|
|
631
|
+
* message is present, so `ocx start`, `ocx ensure`, and `ocx service repair` all
|
|
632
|
+
* reported success while routing quietly stayed native (#2412, the cause behind
|
|
633
|
+
* the misleading green status in #2411).
|
|
634
|
+
*/
|
|
635
|
+
function destroyedShimMessage(file: ShimFileState): string {
|
|
636
|
+
const wrapper = existsSync(file.wrapperPath)
|
|
637
|
+
? isShim(file.wrapperPath) ? "present but unusable" : "present but not an opencodex shim"
|
|
638
|
+
: "missing";
|
|
639
|
+
const backup = existsSync(file.backupPath) ? "present" : "missing";
|
|
640
|
+
const base = `Codex autostart shim not restored: wrapper ${wrapper} at ${file.wrapperPath}; original backup ${backup} at ${file.backupPath}.`;
|
|
641
|
+
if (!isVersionManagerOwnedCodexPath(file.wrapperPath)) {
|
|
642
|
+
return `${base} Re-run 'ocx codex-shim install' once the Codex binary is stable.`;
|
|
643
|
+
}
|
|
644
|
+
return `${base} This Codex binary is owned by a version manager (mise/asdf/volta), so opencodex will not wrap it as a new original — the next upgrade would overwrite the shim and its backup again. Route through Codex instead with 'ocx start', and use 'ocx service install' for autostart.`;
|
|
645
|
+
}
|
|
646
|
+
|
|
606
647
|
function shQuote(value: string): string {
|
|
607
648
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
608
649
|
}
|
|
@@ -2039,14 +2080,20 @@ export function autoRestoreCodexShim(options: {
|
|
|
2039
2080
|
if (seen.has(file.wrapperPath)) return { status: "ineligible" };
|
|
2040
2081
|
seen.add(file.wrapperPath);
|
|
2041
2082
|
if (file.preserveOnly) {
|
|
2042
|
-
if (!existsSync(file.backupPath) || existsSync(file.originalPath))
|
|
2083
|
+
if (!existsSync(file.backupPath) || existsSync(file.originalPath)) {
|
|
2084
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2085
|
+
}
|
|
2043
2086
|
continue;
|
|
2044
2087
|
}
|
|
2045
|
-
if (!existsSync(file.wrapperPath) || !hasUsableBackingPath(file))
|
|
2088
|
+
if (!existsSync(file.wrapperPath) || !hasUsableBackingPath(file)) {
|
|
2089
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2090
|
+
}
|
|
2046
2091
|
const probe = stableShimPathProbe(file.wrapperPath);
|
|
2047
2092
|
if (!probe) return { status: "deferred" };
|
|
2048
2093
|
if (probe.prefix.includes(SHIM_MARKER)) {
|
|
2049
|
-
if (!isHealthyShimProbe(probe, state.platform))
|
|
2094
|
+
if (!isHealthyShimProbe(probe, state.platform)) {
|
|
2095
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2096
|
+
}
|
|
2050
2097
|
if (state.platform !== "win32" && !isCurrentUnixShimProbe(probe)) {
|
|
2051
2098
|
obsoleteShimProbes.set(file.wrapperPath, probe);
|
|
2052
2099
|
continue;
|
|
@@ -2054,6 +2101,12 @@ export function autoRestoreCodexShim(options: {
|
|
|
2054
2101
|
healthyCount += 1;
|
|
2055
2102
|
continue;
|
|
2056
2103
|
}
|
|
2104
|
+
// A surviving backup would otherwise let the replacement path below wrap the
|
|
2105
|
+
// version manager's NEW binary as a fresh original — the same adoption the
|
|
2106
|
+
// missing-backup case refuses, arriving through the back door.
|
|
2107
|
+
if (isVersionManagerOwnedCodexPath(file.wrapperPath)) {
|
|
2108
|
+
return { status: "ineligible", message: destroyedShimMessage(file) };
|
|
2109
|
+
}
|
|
2057
2110
|
replacementProbes.set(file.wrapperPath, probe);
|
|
2058
2111
|
}
|
|
2059
2112
|
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
const HEADER_NAME_PATTERN = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
|
|
12
12
|
const SENSITIVE_PROVIDER_HEADERS = new Set([
|
|
13
13
|
"authorization",
|
|
14
|
+
"api-key",
|
|
14
15
|
"cookie",
|
|
15
16
|
"set-cookie",
|
|
16
17
|
"proxy-authorization",
|
|
@@ -19,6 +20,42 @@ const SENSITIVE_PROVIDER_HEADERS = new Set([
|
|
|
19
20
|
"x-amz-security-token",
|
|
20
21
|
]);
|
|
21
22
|
const REASONING_SUMMARY_DELIVERY_SET = new Set<string>(REASONING_SUMMARY_DELIVERY_VALUES);
|
|
23
|
+
const AZURE_ADAPTERS = new Set(["azure", "azure-openai"]);
|
|
24
|
+
|
|
25
|
+
export function isAzureIdentityProvider(provider: Pick<OcxProviderConfig, "adapter" | "azureCredential">): boolean {
|
|
26
|
+
return AZURE_ADAPTERS.has(provider.adapter) && provider.azureCredential?.type === "default-azure-credential";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Shared semantic boundary for Azure's exact keyless identity mode. */
|
|
30
|
+
export function azureCredentialConfigError(provider: {
|
|
31
|
+
adapter?: unknown;
|
|
32
|
+
azureCredential?: unknown;
|
|
33
|
+
apiKey?: unknown;
|
|
34
|
+
apiKeyPool?: unknown;
|
|
35
|
+
authMode?: unknown;
|
|
36
|
+
}): string | null {
|
|
37
|
+
const credential = provider.azureCredential;
|
|
38
|
+
if (credential === undefined) return null;
|
|
39
|
+
if (!AZURE_ADAPTERS.has(provider.adapter as string)) return "azureCredential is supported only by azure adapters";
|
|
40
|
+
if (!credential || typeof credential !== "object" || Array.isArray(credential)) {
|
|
41
|
+
return "azureCredential must be an object";
|
|
42
|
+
}
|
|
43
|
+
const record = credential as Record<string, unknown>;
|
|
44
|
+
for (const key of Object.keys(record)) {
|
|
45
|
+
if (key !== "type" && key !== "managedIdentityClientId") return "azureCredential has an unrecognized field";
|
|
46
|
+
}
|
|
47
|
+
if (record.type !== "default-azure-credential") return 'azureCredential.type must be "default-azure-credential"';
|
|
48
|
+
if (Object.hasOwn(record, "managedIdentityClientId")
|
|
49
|
+
&& (typeof record.managedIdentityClientId !== "string" || !record.managedIdentityClientId.trim())) {
|
|
50
|
+
return "azureCredential.managedIdentityClientId must be a non-empty string";
|
|
51
|
+
}
|
|
52
|
+
if (Object.hasOwn(provider, "apiKey")) return "azureCredential conflicts with apiKey";
|
|
53
|
+
if (Object.hasOwn(provider, "apiKeyPool")) return "azureCredential conflicts with apiKeyPool";
|
|
54
|
+
if (provider.authMode !== undefined && provider.authMode !== "key") {
|
|
55
|
+
return "azureCredential requires authMode key or omitted";
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
22
59
|
|
|
23
60
|
/** Validate a provider destination without coupling DTO callers to config persistence. */
|
|
24
61
|
export function providerBaseUrlConfigError(baseUrl: string): string | null {
|
package/src/config.ts
CHANGED
|
@@ -6,6 +6,7 @@ import * as z from "zod/v4";
|
|
|
6
6
|
import { isValidProviderName, hasOwnProvider } from "./config/provider-name";
|
|
7
7
|
import {
|
|
8
8
|
apiKeyTransportConfigError,
|
|
9
|
+
azureCredentialConfigError,
|
|
9
10
|
booleanRecordConfigError,
|
|
10
11
|
modelAdapterRecordConfigError,
|
|
11
12
|
nonBlankStringArrayConfigError,
|
|
@@ -59,6 +60,7 @@ import { recordOwnedConfigPath } from "./lib/config-ownership";
|
|
|
59
60
|
import { assertNotRealHomeUnderTest } from "./lib/test-home-guard";
|
|
60
61
|
import { providerDestinationConfigError } from "./lib/destination-policy";
|
|
61
62
|
import { redactSecretString } from "./lib/redact";
|
|
63
|
+
import { antigravityOAuthDestinationConfigError, providerTlsProfileConfigError } from "./lib/provider-tls-profile";
|
|
62
64
|
import { openRouterRoutingConfigError } from "./providers/openrouter-routing";
|
|
63
65
|
import {
|
|
64
66
|
MODEL_ADAPTER_OVERRIDE_ALLOWED,
|
|
@@ -73,6 +75,7 @@ import {
|
|
|
73
75
|
type ProviderCostOverlay,
|
|
74
76
|
} from "./types";
|
|
75
77
|
import { OPENAI_CODEX_PROVIDER_ID } from "./providers/openai-tiers";
|
|
78
|
+
import { modelAutoCompactTokenLimitsConfigError } from "./providers/auto-compact-budget";
|
|
76
79
|
import { fastWireDeclarationError, hasFastWireCapabilityConflict } from "./providers/fastwire";
|
|
77
80
|
import {
|
|
78
81
|
getProviderRegistryEntry,
|
|
@@ -439,18 +442,21 @@ const requestPacingRuleSchema = z.object({
|
|
|
439
442
|
// Keep the RPM-derived timer within the same one-hour bound as minIntervalMs.
|
|
440
443
|
requestsPerMinute: z.number().min(1 / 60).max(60_000).optional(),
|
|
441
444
|
minIntervalMs: z.number().int().min(1).max(3_600_000).optional(),
|
|
442
|
-
|
|
443
|
-
|
|
445
|
+
jitterMs: z.number().int().min(0).max(60_000).optional(),
|
|
446
|
+
}).strict().refine(value => value.requestsPerMinute !== undefined || value.minIntervalMs !== undefined || value.jitterMs !== undefined, {
|
|
447
|
+
message: "request pacing rules need requestsPerMinute, minIntervalMs, or jitterMs",
|
|
444
448
|
});
|
|
445
449
|
|
|
446
450
|
const requestPacingSchema = z.object({
|
|
447
451
|
enabled: z.boolean(),
|
|
448
452
|
requestsPerMinute: z.number().min(1 / 60).max(60_000).optional(),
|
|
449
453
|
minIntervalMs: z.number().int().min(1).max(3_600_000).optional(),
|
|
454
|
+
jitterMs: z.number().int().min(0).max(60_000).optional(),
|
|
450
455
|
models: z.record(z.string().trim().min(1), requestPacingRuleSchema).optional(),
|
|
451
456
|
}).strict().refine(value => value.enabled === false
|
|
452
457
|
|| value.requestsPerMinute !== undefined
|
|
453
458
|
|| value.minIntervalMs !== undefined
|
|
459
|
+
|| value.jitterMs !== undefined
|
|
454
460
|
|| (value.models !== undefined && Object.keys(value.models).length > 0), {
|
|
455
461
|
message: "enabled request pacing needs a provider rule or model override",
|
|
456
462
|
});
|
|
@@ -479,7 +485,14 @@ const fastWireSchema = z.object({
|
|
|
479
485
|
const providerConfigSchema = z.object({
|
|
480
486
|
adapter: z.string().min(1),
|
|
481
487
|
baseUrl: z.string().min(1),
|
|
488
|
+
azureCredential: z.object({
|
|
489
|
+
type: z.literal("default-azure-credential"),
|
|
490
|
+
managedIdentityClientId: z.string().trim().min(1).optional(),
|
|
491
|
+
}).strict().transform(value => value.managedIdentityClientId === undefined
|
|
492
|
+
? value
|
|
493
|
+
: { ...value, managedIdentityClientId: value.managedIdentityClientId.trim() }).optional(),
|
|
482
494
|
requestPacing: requestPacingSchema.optional().catch(undefined),
|
|
495
|
+
tlsProfile: z.literal("antigravity-browser").optional(),
|
|
483
496
|
mcpMaxTools: z.number().int().positive().optional(),
|
|
484
497
|
mcpMaxSchemaBytes: z.number().int().positive().optional(),
|
|
485
498
|
mcpMaxResultBytes: z.number().int().positive().optional(),
|
|
@@ -524,6 +537,8 @@ const providerConfigSchema = z.object({
|
|
|
524
537
|
export { isValidProviderName, hasOwnProvider } from "./config/provider-name";
|
|
525
538
|
export {
|
|
526
539
|
apiKeyTransportConfigError,
|
|
540
|
+
azureCredentialConfigError,
|
|
541
|
+
isAzureIdentityProvider,
|
|
527
542
|
booleanRecordConfigError,
|
|
528
543
|
modelAdapterRecordConfigError,
|
|
529
544
|
nonBlankStringArrayConfigError,
|
|
@@ -836,6 +851,11 @@ const agentTaskRecoverySchema = z.object({
|
|
|
836
851
|
cacheEntries: z.number().int().min(1).max(512).optional(),
|
|
837
852
|
}).strict();
|
|
838
853
|
|
|
854
|
+
const v2NativeParentOverrideSchema = z.object({
|
|
855
|
+
enabled: z.boolean().optional(),
|
|
856
|
+
model: z.string().trim().min(1).optional(),
|
|
857
|
+
}).strict();
|
|
858
|
+
|
|
839
859
|
const configSchema = z.object({
|
|
840
860
|
port: z.number().int().min(0).max(65535).default(10100),
|
|
841
861
|
managementUsageMaxReadBytes: z.number().int().positive().default(64 * 1024 * 1024),
|
|
@@ -869,6 +889,9 @@ const configSchema = z.object({
|
|
|
869
889
|
defaultProvider: z.string().min(1).default("openai"),
|
|
870
890
|
// A retry can be billable, so absence and malformed hand edits both stay off.
|
|
871
891
|
emptyCompletionRetry: z.boolean().optional().catch(false),
|
|
892
|
+
// A malformed hand edit must not silently stop opening the browser: fall back
|
|
893
|
+
// to undefined, which resolves to the historical auto-open behavior.
|
|
894
|
+
oauthOpenBrowser: z.boolean().optional().catch(undefined),
|
|
872
895
|
openaiProviderTierVersion: z.union([z.literal(1), z.literal(2)]).optional(),
|
|
873
896
|
// Invalid hand edits must not discard an otherwise usable config.
|
|
874
897
|
googleAntigravityStaticCatalogVersion: z.union([z.literal(1), z.literal(2)]).optional().catch(undefined),
|
|
@@ -876,6 +899,8 @@ const configSchema = z.object({
|
|
|
876
899
|
providerContextCaps: z.record(z.string(), z.number().int().positive()).optional(),
|
|
877
900
|
contextCapValue: z.number().int().positive().optional(),
|
|
878
901
|
multiAgentGuidanceEnabled: z.boolean().optional(),
|
|
902
|
+
// Invalid hand edits disable only this experimental opt-in subtree.
|
|
903
|
+
v2NativeParentOverride: v2NativeParentOverrideSchema.optional().catch(undefined),
|
|
879
904
|
// Invalid optional recovery config must not discard unrelated provider/account state.
|
|
880
905
|
agentTaskRecovery: agentTaskRecoverySchema.optional().catch(undefined),
|
|
881
906
|
// These selections pre-date schema validation and used to pass through as
|
|
@@ -1044,6 +1069,14 @@ const configSchema = z.object({
|
|
|
1044
1069
|
message: responsesPathError,
|
|
1045
1070
|
});
|
|
1046
1071
|
}
|
|
1072
|
+
const tlsProfileError = providerTlsProfileConfigError(name, provider);
|
|
1073
|
+
if (tlsProfileError) {
|
|
1074
|
+
ctx.addIssue({
|
|
1075
|
+
code: "custom",
|
|
1076
|
+
path: ["providers", redactSecretString(name), "tlsProfile"],
|
|
1077
|
+
message: tlsProfileError,
|
|
1078
|
+
});
|
|
1079
|
+
}
|
|
1047
1080
|
const headersError = providerHeadersConfigError((provider as { headers?: unknown }).headers);
|
|
1048
1081
|
if (headersError) {
|
|
1049
1082
|
ctx.addIssue({
|
|
@@ -1070,6 +1103,14 @@ const configSchema = z.object({
|
|
|
1070
1103
|
message: apiKeyTransportError,
|
|
1071
1104
|
});
|
|
1072
1105
|
}
|
|
1106
|
+
const azureCredentialError = azureCredentialConfigError(provider);
|
|
1107
|
+
if (azureCredentialError) {
|
|
1108
|
+
ctx.addIssue({
|
|
1109
|
+
code: "custom",
|
|
1110
|
+
path: ["providers", redactSecretString(name), "azureCredential"],
|
|
1111
|
+
message: azureCredentialError,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1073
1114
|
const modelAdaptersError = modelAdapterRecordConfigError(
|
|
1074
1115
|
(provider as { modelAdapters?: unknown }).modelAdapters,
|
|
1075
1116
|
"modelAdapters",
|
|
@@ -1107,6 +1148,17 @@ const configSchema = z.object({
|
|
|
1107
1148
|
message: maxInputError,
|
|
1108
1149
|
});
|
|
1109
1150
|
}
|
|
1151
|
+
const autoCompactError = modelAutoCompactTokenLimitsConfigError(
|
|
1152
|
+
(provider as { modelAutoCompactTokenLimits?: unknown }).modelAutoCompactTokenLimits,
|
|
1153
|
+
{ requireNativeIds: name === OPENAI_CODEX_PROVIDER_ID },
|
|
1154
|
+
);
|
|
1155
|
+
if (autoCompactError) {
|
|
1156
|
+
ctx.addIssue({
|
|
1157
|
+
code: "custom",
|
|
1158
|
+
path: ["providers", redactSecretString(name), "modelAutoCompactTokenLimits"],
|
|
1159
|
+
message: autoCompactError,
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1110
1162
|
const reasoningSummariesError = booleanRecordConfigError(
|
|
1111
1163
|
(provider as { modelSupportsReasoningSummaries?: unknown }).modelSupportsReasoningSummaries,
|
|
1112
1164
|
"modelSupportsReasoningSummaries",
|
|
@@ -2043,6 +2095,16 @@ function agentTaskRecoveryError(value: unknown): string | null {
|
|
|
2043
2095
|
return `schema_invalid: agentTaskRecovery${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`;
|
|
2044
2096
|
}
|
|
2045
2097
|
|
|
2098
|
+
function v2NativeParentOverrideError(value: unknown): string | null {
|
|
2099
|
+
const raw = rawConfigRecord(value);
|
|
2100
|
+
if (!raw || !Object.hasOwn(raw, "v2NativeParentOverride") || raw.v2NativeParentOverride === undefined) return null;
|
|
2101
|
+
const result = v2NativeParentOverrideSchema.safeParse(raw.v2NativeParentOverride);
|
|
2102
|
+
if (result.success) return null;
|
|
2103
|
+
const issue = result.error.issues[0];
|
|
2104
|
+
const field = issue?.path.join(".");
|
|
2105
|
+
return `schema_invalid: v2NativeParentOverride${field ? `.${field}` : ""}: ${issue?.message ?? "invalid configuration"}`;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2046
2108
|
/**
|
|
2047
2109
|
* Same reasoning as {@link blankHostnameError}, and more urgent: the read path degrades a
|
|
2048
2110
|
* malformed selection-order map to undefined, which on a write would drop every entry the
|
|
@@ -2102,6 +2164,14 @@ function emptyCompletionRetryError(value: unknown): string | null {
|
|
|
2102
2164
|
return "schema_invalid: emptyCompletionRetry: must be a boolean or omitted";
|
|
2103
2165
|
}
|
|
2104
2166
|
|
|
2167
|
+
function oauthOpenBrowserError(value: unknown): string | null {
|
|
2168
|
+
const raw = rawConfigRecord(value);
|
|
2169
|
+
if (!raw || !Object.hasOwn(raw, "oauthOpenBrowser")) return null;
|
|
2170
|
+
const enabled = raw.oauthOpenBrowser;
|
|
2171
|
+
if (enabled === undefined || typeof enabled === "boolean") return null;
|
|
2172
|
+
return "schema_invalid: oauthOpenBrowser: must be a boolean or omitted";
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2105
2175
|
/** Validate an in-memory config candidate without touching disk. Used by headless CLI import/set. */
|
|
2106
2176
|
/**
|
|
2107
2177
|
* Reject a loopback-listener port that collides with the proxy port (#1102).
|
|
@@ -2148,15 +2218,21 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
|
|
|
2148
2218
|
?? appOwnedMemoryBudgetError(value)
|
|
2149
2219
|
?? upstreamHostCircuitThresholdError(value)
|
|
2150
2220
|
?? agentTaskRecoveryError(value)
|
|
2221
|
+
?? v2NativeParentOverrideError(value)
|
|
2151
2222
|
?? googleAntigravityStaticCatalogVersionError(value)
|
|
2152
2223
|
?? codexAccountPrioritiesError(value)
|
|
2153
2224
|
?? codexAccountPickerEnabledError(value)
|
|
2154
2225
|
?? emptyCompletionRetryError(value)
|
|
2226
|
+
?? oauthOpenBrowserError(value)
|
|
2155
2227
|
?? loopbackListenerPortError(value);
|
|
2156
2228
|
if (boundaryError) return { ok: false, error: boundaryError };
|
|
2157
2229
|
const result = configSchema.safeParse(value);
|
|
2158
2230
|
if (result.success) {
|
|
2159
2231
|
const config = normalizeApiKeyIds(result.data as OcxConfig);
|
|
2232
|
+
for (const [name, provider] of Object.entries(config.providers)) {
|
|
2233
|
+
const antigravityError = antigravityOAuthDestinationConfigError(name, provider);
|
|
2234
|
+
if (antigravityError) return { ok: false, error: `providers.${name}.baseUrl: ${antigravityError}` };
|
|
2235
|
+
}
|
|
2160
2236
|
if (value && typeof value === "object" && !Array.isArray(value) && Object.hasOwn(value, "subagentRoles")) {
|
|
2161
2237
|
const parsedRoles = parseSubagentRoles((value as { subagentRoles?: unknown }).subagentRoles);
|
|
2162
2238
|
if (!parsedRoles.ok) return { ok: false, error: `schema_invalid: ${parsedRoles.error}` };
|