@bitkyc08/opencodex 2.80.0 → 2.81.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/App-7yhoys8a.js +54 -0
- package/gui/dist/assets/Tray-EwAOsP0S.js +1 -0
- package/gui/dist/assets/index-RsoaAxXY.js +94 -0
- package/gui/dist/assets/quota-summary-D2lN8tDm.js +1 -0
- package/gui/dist/index.html +1 -1
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +7 -2
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +1 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-listener.ts +71 -4
- package/src/cli/account-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-integrations.ts +16 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/dispatch.ts +10 -1
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +11 -5
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +32 -3
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/clients/config-export/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +1 -0
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +15 -7
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +11 -1
- package/src/codex/catalog/gather-capture.ts +5 -3
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +15 -9
- package/src/codex/control-plane-models.ts +6 -0
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +3 -2
- package/src/combos/types.ts +1 -1
- package/src/config/diagnostics.ts +27 -16
- package/src/config/load-degrade.ts +34 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +7 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +320 -184
- package/src/generated/model-metadata.ts +5 -4
- package/src/images/loop.ts +2 -1
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +8 -2
- package/src/lib/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +236 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- package/src/lib/upstream-retry.ts +17 -5
- package/src/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +51 -17
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +90 -173
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +67 -9
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/derive.ts +2 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +22 -32
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/spill-store.ts +5 -1
- package/src/responses/state/metrics.ts +87 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-queue.ts +1 -0
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +50 -66
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/chat-completions.ts +6 -1
- package/src/server/chat-native.ts +45 -25
- package/src/server/claude-messages.ts +16 -2
- package/src/server/gui-static.ts +38 -4
- package/src/server/index/spend-ledger-lifecycle.ts +10 -4
- package/src/server/index.ts +4 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +30 -6
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- package/src/server/management/config-routes.ts +27 -7
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +86 -41
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +84 -25
- package/src/server/request-log.ts +5 -0
- package/src/server/responses/adapter-continuation.ts +29 -17
- package/src/server/responses/adapter-dispatch.ts +47 -17
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- package/src/server/responses/compact.ts +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-combo-native.ts +4 -1
- package/src/server/responses/core-combo.ts +16 -11
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +3 -1
- package/src/server/responses/core-replay.ts +5 -0
- package/src/server/responses/passthrough-delivery.ts +7 -3
- package/src/server/responses/passthrough-dispatch.ts +21 -8
- package/src/server/responses/request-prepare.ts +37 -5
- package/src/server/responses/request-send-budget.ts +74 -37
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/run-turn-execution.ts +11 -3
- package/src/server/responses/sidecar-execution.ts +96 -14
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/workflow-refusal.ts +30 -0
- package/src/service/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- package/src/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- package/src/types/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +12 -28
- package/src/types/provider.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types.ts +4 -0
- package/src/usage/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +27 -5
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
|
@@ -10,11 +10,34 @@ export const JEV_DECISION_TIMEOUT_MAX_MS = 120_000;
|
|
|
10
10
|
/** Decision deadline when a combo sets no `decisionTimeoutMs`. */
|
|
11
11
|
export const JEV_DECISION_TIMEOUT_DEFAULT_MS = 4_000;
|
|
12
12
|
|
|
13
|
-
/**
|
|
13
|
+
/** HTTPS decision services may use any path; local cleartext keeps the `/systemone` contract. */
|
|
14
14
|
export function isSystemOneEndpoint(baseUrl: string): boolean {
|
|
15
15
|
try {
|
|
16
|
-
|
|
16
|
+
const raw = baseUrl.trim();
|
|
17
|
+
// URL drops empty delimiters ("?", "#", "@") and strips tab/CR/LF, so check the raw text first.
|
|
18
|
+
if (/[\u0000-\u001f\u007f]/.test(raw)) return false;
|
|
19
|
+
const authority = raw.replace(/^[a-z][a-z\d+.-]*:[/\\]*/i, "").split(/[/\\]/, 1)[0] ?? "";
|
|
20
|
+
if (/[?#]/.test(raw) || authority.includes("@")) return false;
|
|
21
|
+
const url = new URL(raw);
|
|
22
|
+
if (url.username || url.password || url.search || url.hash) return false;
|
|
23
|
+
// URL canonicalizes address literals; keep this import-free for the dashboard bundle.
|
|
24
|
+
const host = url.hostname;
|
|
25
|
+
const local = host === "localhost" || host === "[::1]"
|
|
26
|
+
|| /^(?:(?:127|10)\.\d+|172\.(?:1[6-9]|2\d|3[01])|192\.168)\.\d+\.\d+$/.test(host)
|
|
27
|
+
|| /^\[f[cd][\da-f]{2}:/.test(host) || /^\[::ffff:7f[\da-f]{2}:/.test(host);
|
|
28
|
+
return url.protocol === "https:"
|
|
29
|
+
|| url.protocol === "http:" && local && url.pathname.replace(/\/+$/, "").endsWith("/systemone");
|
|
17
30
|
} catch {
|
|
18
31
|
return false;
|
|
19
32
|
}
|
|
20
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The URL a decision row is sent to. A `/systemone` path keeps its historical trailing-slash
|
|
37
|
+
* normalization; any other HTTPS path is the operator's exact endpoint.
|
|
38
|
+
*/
|
|
39
|
+
export function jevDecisionEndpointUrl(baseUrl: string): string {
|
|
40
|
+
const raw = baseUrl.trim();
|
|
41
|
+
const stripped = raw.replace(/\/+$/, "");
|
|
42
|
+
return stripped.endsWith("/systemone") ? stripped : raw;
|
|
43
|
+
}
|
package/src/combos/jev.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
} from "../providers/api-key-resolve";
|
|
11
11
|
import { providerMatchesRegistryTransport } from "../providers/registry";
|
|
12
12
|
import type { OcxComboDefaultEffort, OcxConfig, OcxProviderConfig } from "../types";
|
|
13
|
+
import { jevDecisionEndpointUrl } from "./jev-decision-contract";
|
|
13
14
|
import {
|
|
14
15
|
isSystemOneEndpoint,
|
|
15
16
|
JEV_DECISION_TIMEOUT_DEFAULT_MS,
|
|
@@ -676,7 +677,7 @@ function selfHostedApiKey(name: string, apiKey: string | undefined): string | un
|
|
|
676
677
|
* only while the row still matches the registry transport, and the environment fallbacks exist
|
|
677
678
|
* only for that URL. A retargeted `jev` row therefore keeps today's behavior instead of becoming a
|
|
678
679
|
* custom destination. Any other id must be an enabled `jev-decision` row whose baseUrl is a
|
|
679
|
-
* `/systemone` endpoint and
|
|
680
|
+
* full HTTPS decision endpoint (or a local HTTP `/systemone` endpoint) and names its own model; only its own key may accompany it, so no
|
|
680
681
|
* TypeSafe credential can reach a self-hosted service. `undefined` means no usable decision
|
|
681
682
|
* service (reported through the existing `missing_key` gate); `null` means the request's
|
|
682
683
|
* destination scope refused it before any credential access.
|
|
@@ -709,7 +710,7 @@ function jevDecisionEndpoint(
|
|
|
709
710
|
};
|
|
710
711
|
}
|
|
711
712
|
if (configured?.adapter !== "jev-decision" || typeof configured.baseUrl !== "string") return undefined;
|
|
712
|
-
const url = configured.baseUrl
|
|
713
|
+
const url = jevDecisionEndpointUrl(configured.baseUrl);
|
|
713
714
|
if (!url || !isSystemOneEndpoint(url)) return undefined;
|
|
714
715
|
// `jev-latest` is TypeSafe's model name; a self-hosted host must name its own.
|
|
715
716
|
const model = configured.defaultModel?.trim() || configured.models?.[0]?.trim();
|
package/src/combos/types.ts
CHANGED
|
@@ -302,7 +302,7 @@ export function comboConfigIssues(
|
|
|
302
302
|
} else if (!isSystemOneEndpoint(String(providers[decisionProvider]?.baseUrl ?? ""))) {
|
|
303
303
|
issues.push({
|
|
304
304
|
path: ["decisionProvider"],
|
|
305
|
-
message: `decisionProvider "${decisionProvider}" baseUrl must be
|
|
305
|
+
message: `decisionProvider "${decisionProvider}" baseUrl must be a full HTTPS decision endpoint or an HTTP /systemone endpoint`,
|
|
306
306
|
});
|
|
307
307
|
} else if (options.requireUsableDecisionService && providers[decisionProvider]?.disabled === true) {
|
|
308
308
|
issues.push({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readExistingSpendSalt, spendPoolAliasesError, validatePoolAliasOwners } from "../lib/spend-pool-alias-validation";
|
|
1
2
|
import { isSubagentModelEntry, rawSubagentModelForce } from "./subagent-models";
|
|
2
3
|
import { protocolConfigSchema } from "./schema/config-schema";
|
|
3
4
|
import { createHash } from "node:crypto";
|
|
@@ -7,13 +8,13 @@ import * as z from "zod/v4";
|
|
|
7
8
|
import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
|
|
8
9
|
import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
|
|
9
10
|
import type { OcxConfig } from "../types";
|
|
10
|
-
import {
|
|
11
|
+
import { anthropicAccountPoolConfigError, anthropicOAuthInstanceConfigError, anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
|
|
11
12
|
import { configReasoningPinsConfigError } from "./provider-validation";
|
|
12
13
|
import { loopbackCompanionAllowed } from "../codex/loopback-target";
|
|
13
14
|
import { UPSTREAM_HOST_CIRCUIT_MAX_THRESHOLD } from "../codex/upstream-host-health";
|
|
14
15
|
import { MAX_APP_OWNED_MEMORY_BUDGET_MB, MIN_APP_OWNED_MEMORY_BUDGET_MB } from "../lib/app-owned-memory";
|
|
15
16
|
import { isMissingPathError } from "./atomic-write";
|
|
16
|
-
import { getConfigPath } from "./paths";
|
|
17
|
+
import { getConfigDir, getConfigPath } from "./paths";
|
|
17
18
|
import { getDefaultConfig } from "./proxy-env";
|
|
18
19
|
import { salvageConfigCandidate } from "./salvage";
|
|
19
20
|
import {
|
|
@@ -81,6 +82,23 @@ export type ConfigDiagnostics = {
|
|
|
81
82
|
warnings?: string[];
|
|
82
83
|
};
|
|
83
84
|
|
|
85
|
+
function rawPoolAliasesError(value: unknown): string | undefined {
|
|
86
|
+
const record = rawConfigRecord(value);
|
|
87
|
+
if (!record) return undefined;
|
|
88
|
+
const aliases = record.spendPoolAliases;
|
|
89
|
+
const shapeError = spendPoolAliasesError(aliases);
|
|
90
|
+
if (shapeError) return shapeError;
|
|
91
|
+
const mapping = rawConfigRecord(aliases);
|
|
92
|
+
if (!mapping || Object.keys(mapping).length === 0) return undefined;
|
|
93
|
+
const providers = rawConfigRecord(record.providers ?? {});
|
|
94
|
+
if (!providers) return "providers must be an object to validate spendPoolAliases";
|
|
95
|
+
const targetError = spendPoolAliasesError(aliases, Object.keys(providers));
|
|
96
|
+
if (targetError) return targetError;
|
|
97
|
+
const salt = readExistingSpendSalt(getConfigDir());
|
|
98
|
+
if (!salt) return "spendPoolAliases requires a safely readable existing spend salt";
|
|
99
|
+
return validatePoolAliasOwners(aliases, salt, Object.keys(providers));
|
|
100
|
+
}
|
|
101
|
+
|
|
84
102
|
export type ConfigFileSnapshot = {
|
|
85
103
|
diagnostics: ConfigDiagnostics;
|
|
86
104
|
/** Exact file contents, including a possible BOM, used as the optimistic revision. */
|
|
@@ -145,6 +163,9 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
|
|
|
145
163
|
if (codexPoolWarning) warnings.push(codexPoolWarning);
|
|
146
164
|
const spendWarning = malformedSpendWarning(rawParsed);
|
|
147
165
|
if (spendWarning) warnings.push(spendWarning);
|
|
166
|
+
if (rawPoolAliasesError(rawParsed)) {
|
|
167
|
+
warnings.push("spendPoolAliases invalid: configured pool admission is refused until the mapping is corrected");
|
|
168
|
+
}
|
|
148
169
|
const plaintextWarning = malformedPlaintextV2AgentMessagesWarning(rawParsed);
|
|
149
170
|
if (plaintextWarning) warnings.push(plaintextWarning);
|
|
150
171
|
if (syncDisabledReason) {
|
|
@@ -153,7 +174,7 @@ function validFileConfigDiagnostics(config: OcxConfig, rawParsed: unknown): Conf
|
|
|
153
174
|
return {
|
|
154
175
|
config: normalized,
|
|
155
176
|
source: "file",
|
|
156
|
-
error: null,
|
|
177
|
+
error: anthropicOAuthInstanceConfigError(rawParsed) ?? anthropicAccountPoolConfigError(rawParsed) ?? anthropicSidecarConfigError(rawParsed) ?? null,
|
|
157
178
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
158
179
|
};
|
|
159
180
|
}
|
|
@@ -655,19 +676,8 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
|
|
|
655
676
|
if (protocols !== undefined && !protocolConfigSchema.safeParse(protocols).success) {
|
|
656
677
|
return { ok: false, error: "schema_invalid: protocols: expected valid protocol policy and boolean rollout flags" };
|
|
657
678
|
}
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
if (rawAnthropicPool !== undefined && !anthropicPool) {
|
|
661
|
-
return { ok: false, error: "schema_invalid: anthropicAccountPool: must be an object" };
|
|
662
|
-
}
|
|
663
|
-
if (anthropicPool && Object.hasOwn(anthropicPool, "nativeMessages") && typeof anthropicPool.nativeMessages !== "boolean") {
|
|
664
|
-
return { ok: false, error: "schema_invalid: anthropicAccountPool.nativeMessages: must be a boolean" };
|
|
665
|
-
}
|
|
666
|
-
const routeValue = anthropicPool?.routes;
|
|
667
|
-
if (routeValue !== undefined) {
|
|
668
|
-
const parsed = parseAnthropicModelRoutes(routeValue);
|
|
669
|
-
if (!parsed.ok) return { ok: false, error: `schema_invalid: anthropicAccountPool.routes: ${parsed.error}` };
|
|
670
|
-
}
|
|
679
|
+
const anthropicError = anthropicOAuthInstanceConfigError(value) ?? anthropicAccountPoolConfigError(value) ?? anthropicSidecarConfigError(value);
|
|
680
|
+
if (anthropicError) return { ok: false, error: anthropicError };
|
|
671
681
|
const boundaryError = blockedModelRedirectsError(value)
|
|
672
682
|
?? compactionRecoveryConfigError(value) ?? configReasoningPinsConfigError(value)
|
|
673
683
|
?? blankHostnameError(value)
|
|
@@ -679,6 +689,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
|
|
|
679
689
|
?? agentTaskRecoveryError(value)
|
|
680
690
|
?? quotaResetNotifyError(value)
|
|
681
691
|
?? catalogAutoRefreshError(value)
|
|
692
|
+
?? (rawPoolAliasesError(value) ? "schema_invalid: spendPoolAliases: invalid pool identity mapping" : null)
|
|
682
693
|
?? spendError(value)
|
|
683
694
|
?? codexPoolError(value)
|
|
684
695
|
?? googleAntigravityStaticCatalogVersionError(value)
|
|
@@ -3,6 +3,7 @@ import { chmodSync, existsSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
|
|
5
5
|
import { blockedModelRedirectsError } from "./schema/blocked-model-redirects";
|
|
6
|
+
import { anthropicSidecarConfigError } from "./schema/anthropic-account-pool";
|
|
6
7
|
import {
|
|
7
8
|
modelPinnedEffortsConfigError,
|
|
8
9
|
pinnedReasoningEffortConfigError,
|
|
@@ -160,6 +161,39 @@ export function warnDegradedMemoryModels(rawParsed: unknown, validated: OcxConfi
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Load-time degradation for helper `anthropicInstance` (loadConfig only). A malformed id, or one
|
|
166
|
+
* that conflicts with its block's backend or model, drops just that key with a warning, so the
|
|
167
|
+
* helper inherits the request's pool and every provider/key survives. Validated writes still
|
|
168
|
+
* reject the value and file diagnostics still report it from the raw document.
|
|
169
|
+
*
|
|
170
|
+
* Each location is checked in isolation, global blocks first, so a Claude override is judged
|
|
171
|
+
* against the already-degraded global block it inherits from. An override that owns no
|
|
172
|
+
* `anthropicInstance` of its own is left untouched: the conflicting key lives in the global
|
|
173
|
+
* block, which is valid on its own, and the inherited value is still a well-formed instance.
|
|
174
|
+
*/
|
|
175
|
+
export function sanitizeAnthropicSidecarInstanceForLoad(parsed: unknown): void {
|
|
176
|
+
const asRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
177
|
+
value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined;
|
|
178
|
+
const root = asRecord(parsed);
|
|
179
|
+
if (!root) return;
|
|
180
|
+
for (const scope of ["global", "claude"] as const) {
|
|
181
|
+
const owner = scope === "global" ? root : asRecord(root.claudeCode);
|
|
182
|
+
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
183
|
+
const sidecar = asRecord(owner?.[field]);
|
|
184
|
+
if (!sidecar || !Object.hasOwn(sidecar, "anthropicInstance")) continue;
|
|
185
|
+
const probe = scope === "global"
|
|
186
|
+
? { [field]: sidecar }
|
|
187
|
+
: { [field]: root[field], claudeCode: { [field]: sidecar } };
|
|
188
|
+
const error = anthropicSidecarConfigError(probe);
|
|
189
|
+
if (!error) continue;
|
|
190
|
+
delete sidecar.anthropicInstance;
|
|
191
|
+
// The message names a static path and reason only; the hand-edited value is never echoed.
|
|
192
|
+
console.warn(`⚠️ config.json ${error} — ignoring that anthropicInstance; the helper inherits the request's Anthropic pool`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
163
197
|
/**
|
|
164
198
|
* Load-time degradation for `retryOn429` (loadConfig only): one hand-edited invalid optional
|
|
165
199
|
* field (e.g. `attempts: 0` or a string) must not trip the whole provider schema and hide every
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { parseAnthropicModelRoutes } from "../../oauth/anthropic-model-routes";
|
|
3
|
+
import { isAnthropicInstanceId } from "../../providers/anthropic-instance-id";
|
|
4
|
+
import { redactSecretString } from "../../lib/redact";
|
|
5
|
+
|
|
6
|
+
/** Historical A load contract, shared verbatim with B: recover only the native preference. */
|
|
7
|
+
export const anthropicAccountPoolSchema = z.object({
|
|
8
|
+
nativeMessages: z.boolean().optional().catch(false),
|
|
9
|
+
}).passthrough().optional().catch(undefined);
|
|
10
|
+
|
|
11
|
+
function record(value: unknown): Record<string, unknown> | undefined {
|
|
12
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
13
|
+
? value as Record<string, unknown> : undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Write/diagnostic contract preserves A's historical nativeMessages and route validation. */
|
|
17
|
+
function poolError(value: unknown, path: string): string | undefined {
|
|
18
|
+
if (value === undefined) return undefined;
|
|
19
|
+
const pool = record(value);
|
|
20
|
+
if (!pool) return `schema_invalid: ${path}: must be an object`;
|
|
21
|
+
if (Object.hasOwn(pool, "nativeMessages") && typeof pool.nativeMessages !== "boolean") {
|
|
22
|
+
return `schema_invalid: ${path}.nativeMessages: must be a boolean`;
|
|
23
|
+
}
|
|
24
|
+
if (pool.routes !== undefined) {
|
|
25
|
+
const parsed = parseAnthropicModelRoutes(pool.routes);
|
|
26
|
+
if (!parsed.ok) return `schema_invalid: ${path}.routes: ${parsed.error}`;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Inspect raw locations before the tolerant load schema can discard a malformed field. */
|
|
32
|
+
export function anthropicAccountPoolConfigError(value: unknown): string | undefined {
|
|
33
|
+
const config = record(value);
|
|
34
|
+
const primaryError = poolError(config?.anthropicAccountPool, "anthropicAccountPool");
|
|
35
|
+
if (primaryError) return primaryError;
|
|
36
|
+
for (const [name, raw] of Object.entries(record(config?.providers) ?? {})) {
|
|
37
|
+
const provider = record(raw);
|
|
38
|
+
if (!provider || !Object.hasOwn(provider, "anthropicAccountPool")) continue;
|
|
39
|
+
const path = `providers.${redactSecretString(name)}.anthropicAccountPool`;
|
|
40
|
+
if (name !== "anthropic2") {
|
|
41
|
+
return `schema_invalid: ${path}: misplaced field; provider-local anthropicAccountPool is valid only on anthropic2 (anthropic uses the top-level field)`;
|
|
42
|
+
}
|
|
43
|
+
const error = poolError(provider.anthropicAccountPool, path);
|
|
44
|
+
if (error) return error;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Raw provenance validation runs before tolerant parsing and never adopts or edits a row. */
|
|
50
|
+
export function anthropicOAuthInstanceConfigError(value: unknown): string | undefined {
|
|
51
|
+
const seen = new WeakSet<object>();
|
|
52
|
+
const inspect = (raw: unknown, location: string[]): string | undefined => {
|
|
53
|
+
if (!raw || typeof raw !== "object" || seen.has(raw)) return undefined;
|
|
54
|
+
seen.add(raw);
|
|
55
|
+
for (const [field, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(raw))) {
|
|
56
|
+
const path = [...location, field];
|
|
57
|
+
if (field === "anthropicOAuthInstance") {
|
|
58
|
+
const label = redactSecretString(path.join("."));
|
|
59
|
+
if (!("value" in descriptor)) return `schema_invalid: ${label}: must be an own data property`;
|
|
60
|
+
if (location.length !== 2 || location[0] !== "providers" || location[1] !== "anthropic2") {
|
|
61
|
+
return `schema_invalid: ${label}: misplaced field; valid only on providers.anthropic2`;
|
|
62
|
+
}
|
|
63
|
+
if (descriptor.value !== "anthropic2") return `schema_invalid: ${label}: must be anthropic2`;
|
|
64
|
+
const row = Object.getOwnPropertyDescriptors(raw);
|
|
65
|
+
if (row.adapter?.value !== "anthropic" || row.authMode?.value !== "oauth") {
|
|
66
|
+
return `schema_invalid: ${label}: requires Anthropic adapter and OAuth authMode`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (!("value" in descriptor)) continue;
|
|
70
|
+
const error = inspect(descriptor.value, path);
|
|
71
|
+
if (error) return error;
|
|
72
|
+
}
|
|
73
|
+
seen.delete(raw);
|
|
74
|
+
return undefined;
|
|
75
|
+
};
|
|
76
|
+
return inspect(value, []);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Validate explicit helper identity without materializing an absent instance preference. */
|
|
80
|
+
export function anthropicSidecarConfigError(value: unknown): string | undefined {
|
|
81
|
+
const config = record(value);
|
|
82
|
+
const claude = record(config?.claudeCode);
|
|
83
|
+
for (const [prefix, owner] of [["", config], ["claudeCode.", claude]] as const) {
|
|
84
|
+
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
85
|
+
const rawSidecar = record(owner?.[field]);
|
|
86
|
+
if (!rawSidecar) continue;
|
|
87
|
+
// Claude overrides inherit individual global fields, just like buildClaudeReplayConfig.
|
|
88
|
+
const sidecar = prefix ? { ...record(config?.[field]), ...rawSidecar } : rawSidecar;
|
|
89
|
+
if (!Object.hasOwn(sidecar, "anthropicInstance")) continue;
|
|
90
|
+
const path = `${prefix}${field}.anthropicInstance`;
|
|
91
|
+
if (!isAnthropicInstanceId(sidecar.anthropicInstance)) {
|
|
92
|
+
return `schema_invalid: ${path}: must be anthropic or anthropic2`;
|
|
93
|
+
}
|
|
94
|
+
// Web search defaults to OpenAI; vision's absent backend keeps its credential-based auto mode.
|
|
95
|
+
const backend = sidecar.backend === undefined
|
|
96
|
+
? (field === "webSearchSidecar" ? "openai" : undefined) : sidecar.backend;
|
|
97
|
+
// An instance inherited from the global block is inert once the override's effective backend
|
|
98
|
+
// leaves Anthropic (the helper resolver only reads it for the Anthropic family), so it is not
|
|
99
|
+
// a conflict. An instance the override sets itself is still validated in full.
|
|
100
|
+
if (prefix && !Object.hasOwn(rawSidecar, "anthropicInstance") && backend !== undefined && backend !== "anthropic") continue;
|
|
101
|
+
const modelProvider = typeof sidecar.model === "string" && sidecar.model.includes("/")
|
|
102
|
+
? sidecar.model.slice(0, sidecar.model.indexOf("/")) : undefined;
|
|
103
|
+
if (modelProvider && isAnthropicInstanceId(modelProvider) && modelProvider !== sidecar.anthropicInstance) {
|
|
104
|
+
return `schema_invalid: ${path}: conflicts with the model's Anthropic instance`;
|
|
105
|
+
}
|
|
106
|
+
if (backend !== undefined && backend !== "anthropic") {
|
|
107
|
+
return `schema_invalid: ${path}: requires an anthropic backend`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Validate the effective settings before either management route mutates live state. */
|
|
115
|
+
export function anthropicSidecarPatchError(
|
|
116
|
+
config: unknown,
|
|
117
|
+
patches: Record<string, unknown>,
|
|
118
|
+
claude = false,
|
|
119
|
+
): string | undefined {
|
|
120
|
+
const source = record(config) ?? {};
|
|
121
|
+
const next = { ...source };
|
|
122
|
+
const owner = claude ? { ...record(source.claudeCode) } : next;
|
|
123
|
+
for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
|
|
124
|
+
if (!Object.hasOwn(patches, field)) continue;
|
|
125
|
+
const patch = patches[field];
|
|
126
|
+
if (patch === null) { delete owner[field]; continue; }
|
|
127
|
+
const section = record(patch);
|
|
128
|
+
if (!section) continue; // Shape validation belongs to the route.
|
|
129
|
+
if (section.anthropicInstance !== undefined && section.anthropicInstance !== null
|
|
130
|
+
&& !isAnthropicInstanceId(section.anthropicInstance)) {
|
|
131
|
+
return `${field}.anthropicInstance must be anthropic, anthropic2, or null`;
|
|
132
|
+
}
|
|
133
|
+
const settings = { ...record(owner[field]) };
|
|
134
|
+
for (const key of ["backend", "model", "anthropicInstance"] as const) {
|
|
135
|
+
if (section[key] === null || (key === "model" && section[key] === "")) delete settings[key];
|
|
136
|
+
else if (section[key] !== undefined) settings[key] = section[key];
|
|
137
|
+
}
|
|
138
|
+
if (claude && (Object.keys(section).length === 0 || Object.keys(settings).length === 0)) delete owner[field];
|
|
139
|
+
else owner[field] = settings;
|
|
140
|
+
}
|
|
141
|
+
if (claude) next.claudeCode = owner;
|
|
142
|
+
return anthropicSidecarConfigError(next);
|
|
143
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MAIN_ACCOUNT_HARD_LOCK_MIN_PERCENT } from "../../codex/quota-types";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
|
+
import { anthropicAccountPoolSchema } from "./anthropic-account-pool";
|
|
3
4
|
import { compactionRecoverySchema } from "./compaction-recovery";
|
|
4
5
|
import { blockedModelRedirectsSchema } from "./blocked-model-redirects";
|
|
5
6
|
import {
|
|
@@ -327,7 +328,7 @@ export const configSchema = z.object({
|
|
|
327
328
|
blockedModelRedirects: blockedModelRedirectsSchema.optional().catch(undefined),
|
|
328
329
|
// Degrade malformed hand edits locally; candidate writes reject them before parsing.
|
|
329
330
|
// An invalid native preference retains the legacy route instead of enabling native by default.
|
|
330
|
-
anthropicAccountPool:
|
|
331
|
+
anthropicAccountPool: anthropicAccountPoolSchema,
|
|
331
332
|
// Same degrade-don't-reject rationale as the fields above: a hand-edited
|
|
332
333
|
// non-string must not trip the backup-and-defaults repair path. Unset then
|
|
333
334
|
// takes the canonical sideband path (src/server/live.ts normalizeSidebandRoot).
|
|
@@ -350,6 +351,11 @@ export const configSchema = z.object({
|
|
|
350
351
|
.map(row => apiKeyEntrySchema.parse(row) as OcxApiKeyEntry);
|
|
351
352
|
}),
|
|
352
353
|
}).passthrough().superRefine((config, ctx) => {
|
|
354
|
+
// Helper `anthropicInstance` is deliberately not refined here. A document-level issue would send
|
|
355
|
+
// loadConfig through salvage (which refuses pathless issues) to the backup-and-defaults path, so
|
|
356
|
+
// one hand-edited helper preference would hide every provider. Writes reject it explicitly in
|
|
357
|
+
// validateConfigCandidate, diagnostics report it from the raw file, and loadConfig drops only the
|
|
358
|
+
// offending key (sanitizeAnthropicSidecarInstanceForLoad).
|
|
353
359
|
const claudeCode = (config as { claudeCode?: unknown }).claudeCode;
|
|
354
360
|
if (claudeCode !== undefined && (!claudeCode || typeof claudeCode !== "object" || Array.isArray(claudeCode))) {
|
|
355
361
|
ctx.addIssue({ code: "custom", path: ["claudeCode"], message: "claudeCode must be an object" });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as z from "zod/v4";
|
|
2
|
+
import { anthropicAccountPoolSchema } from "./anthropic-account-pool";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { isValidProviderName } from "../provider-name";
|
|
4
5
|
import {
|
|
@@ -285,6 +286,9 @@ const providerNoProxySchema = z.unknown().superRefine((value, ctx) => {
|
|
|
285
286
|
* fields pass through (preserved for runtime extensions).
|
|
286
287
|
*/
|
|
287
288
|
export const providerConfigSchema = z.object({
|
|
289
|
+
anthropicAccountPool: anthropicAccountPoolSchema,
|
|
290
|
+
// Raw diagnostics/writes reject malformed provenance; tolerant loads preserve the custom row.
|
|
291
|
+
anthropicOAuthInstance: z.literal("anthropic2").optional().catch(undefined),
|
|
288
292
|
modelCapabilities: modelCapabilitiesSchema.optional(),
|
|
289
293
|
modelContextTiers: z.unknown().superRefine((value, ctx) => {
|
|
290
294
|
const error = contextTierRecordConfigError(value);
|
package/src/config.ts
CHANGED
|
@@ -172,6 +172,7 @@ import {
|
|
|
172
172
|
sanitizeModelDisplayNamesForLoad,
|
|
173
173
|
sanitizeAutoReviewForLoad,
|
|
174
174
|
sanitizeRetryOn429ForLoad,
|
|
175
|
+
sanitizeAnthropicSidecarInstanceForLoad,
|
|
175
176
|
sanitizeModelCostsForLoad,
|
|
176
177
|
sanitizeCapabilityDeclarationsForLoad,
|
|
177
178
|
warnInheritedFastWireConflicts,
|
|
@@ -226,6 +227,7 @@ export function loadConfig(): OcxConfig {
|
|
|
226
227
|
sanitizeModelDisplayNamesForLoad(parsed);
|
|
227
228
|
sanitizeAutoReviewForLoad(parsed);
|
|
228
229
|
sanitizeRetryOn429ForLoad(parsed);
|
|
230
|
+
sanitizeAnthropicSidecarInstanceForLoad(parsed);
|
|
229
231
|
sanitizeModelCostsForLoad(parsed);
|
|
230
232
|
sanitizeCapabilityDeclarationsForLoad(parsed);
|
|
231
233
|
const result = configSchema.safeParse(parsed);
|