@bitkyc08/opencodex 2.79.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/cursor/live-transport.ts +5 -3
- package/src/adapters/cursor/native-exec-fs.ts +12 -9
- package/src/adapters/cursor/native-exec.ts +13 -5
- package/src/adapters/cursor/protobuf-request.ts +13 -7
- package/src/adapters/cursor/request-builder.ts +23 -10
- package/src/adapters/cursor/tool-definitions.ts +9 -6
- package/src/adapters/cursor/tool-guidance.ts +29 -8
- package/src/adapters/cursor/tool-naming.ts +40 -7
- package/src/adapters/cursor/tool-wording.ts +46 -0
- package/src/adapters/cursor.ts +3 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/devin.ts +25 -11
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -4
- 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 +10 -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/autostart-health.ts +3 -3
- 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/codex/journal.ts +36 -5
- package/src/codex/shim-overlay.ts +9 -1
- package/src/combos/jev-decision-contract.ts +25 -2
- package/src/combos/jev.ts +6 -3
- 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 +347 -199
- 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 +16 -16
- 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-scope.ts +4 -1
- 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/combo-requested-effort.ts +14 -0
- 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 +21 -18
- 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/desktop-startup.ts +100 -3
- 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/header-deadline.ts +67 -0
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -10
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-BOUa-ek3.js +0 -54
- package/gui/dist/assets/Tray-DUtzB4zF.js +0 -1
- package/gui/dist/assets/index-DwHNDF4s.js +0 -94
- package/gui/dist/assets/quota-summary-BuL4WIBo.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hasOrdinaryNativeOpenAiRow } from "./control-plane";
|
|
2
|
+
import { isCodexControlPlaneModel } from "../control-plane-models";
|
|
1
3
|
import { execFileSync } from "node:child_process";
|
|
2
4
|
import { createHash } from "node:crypto";
|
|
3
5
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
@@ -385,7 +387,7 @@ export function clampEntryToCodexSupportedEfforts(
|
|
|
385
387
|
entry: RawEntry,
|
|
386
388
|
supported: ReadonlySet<string> | null,
|
|
387
389
|
): void {
|
|
388
|
-
if (!supported) return;
|
|
390
|
+
if (!supported || isCodexControlPlaneModel(entry.slug)) return;
|
|
389
391
|
const levels = Array.isArray(entry.supported_reasoning_levels)
|
|
390
392
|
? entry.supported_reasoning_levels as Array<{ effort?: string }>
|
|
391
393
|
: null;
|
|
@@ -532,6 +534,14 @@ export function clampCatalogModelsToObservedCodexSupport(
|
|
|
532
534
|
else index += 1;
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
// Only Reserve rows are currently omitted above; ordinary natives keep a fallback ladder.
|
|
538
|
+
// Also repair a persisted orphan so a removed Reserve can never leave a reviewer behind.
|
|
539
|
+
if (!hasOrdinaryNativeOpenAiRow(models)) {
|
|
540
|
+
for (let index = models.length - 1; index >= 0; index -= 1) {
|
|
541
|
+
if (isCodexControlPlaneModel(models[index]!.slug)) models.splice(index, 1);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
535
545
|
return {
|
|
536
546
|
removedEfforts: [...removed].sort(),
|
|
537
547
|
affectedModels: affected,
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type OAuthActiveTokenObservation,
|
|
33
33
|
} from "../../oauth";
|
|
34
34
|
import { getAccountSet } from "../../oauth/store";
|
|
35
|
+
import { mayResolveModelsOAuth } from "../../oauth/model-discovery-auth";
|
|
35
36
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
36
37
|
import { modelInList } from "../../types";
|
|
37
38
|
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
@@ -389,15 +390,15 @@ export function captureProviderGather(
|
|
|
389
390
|
? authResolver.resolve(name, provider)
|
|
390
391
|
: undefined;
|
|
391
392
|
// A refreshing capture carries the stored origin so accounts on different hosts keep separate
|
|
392
|
-
// flights.
|
|
393
|
+
// flights. Host-scoped auth rebuilds its request; B retains its configured captured target. Observed capture never
|
|
393
394
|
// reads the live store.
|
|
394
395
|
const oauthApiBaseUrl = observedAuth
|
|
395
396
|
? observedAuth.oauthApiBaseUrl
|
|
396
|
-
: authResolver.kind === "refreshing" && provider.authMode === "oauth"
|
|
397
|
+
: authResolver.kind === "refreshing" && provider.authMode === "oauth" && mayResolveModelsOAuth(name, provider)
|
|
397
398
|
? getOAuthCredentialApiBaseUrl(name)
|
|
398
399
|
: undefined;
|
|
399
400
|
const request = captureModelsRequest(name, provider, oauthApiBaseUrl);
|
|
400
|
-
const refreshingOAuthAccountId = !observedAuth && authResolver.kind === "refreshing" && provider.authMode === "oauth"
|
|
401
|
+
const refreshingOAuthAccountId = !observedAuth && authResolver.kind === "refreshing" && provider.authMode === "oauth" && mayResolveModelsOAuth(name, provider)
|
|
401
402
|
? getAccountSet(name)?.activeAccountId
|
|
402
403
|
: undefined;
|
|
403
404
|
const resolved = resolveProviderModelDiscovery(name, provider);
|
|
@@ -531,6 +532,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
|
|
|
531
532
|
live: prov.liveModels ?? null,
|
|
532
533
|
base: prov.baseUrl ?? "",
|
|
533
534
|
adapter: prov.adapter ?? "",
|
|
535
|
+
instance: prov.anthropicOAuthInstance ?? null,
|
|
534
536
|
models: [...(prov.models ?? [])].sort(),
|
|
535
537
|
retain: [...(prov.retainModels ?? [])].sort(),
|
|
536
538
|
selected: [...(prov.selectedModels ?? [])].sort(),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isCodexControlPlaneModel } from "../control-plane-models";
|
|
1
2
|
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
@@ -700,7 +701,7 @@ export function catalogEntryIsNativeChatGpt(entry: RawEntry): boolean {
|
|
|
700
701
|
&& entry.use_responses_lite === true
|
|
701
702
|
&& hasNativeOpenAiCapabilityMetadata(routedNativeSlug)
|
|
702
703
|
) return true;
|
|
703
|
-
if (UPSTREAM_NATIVE_ENTRIES.has(slug) || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) return true;
|
|
704
|
+
if (isCodexControlPlaneModel(slug) || UPSTREAM_NATIVE_ENTRIES.has(slug) || SUPPORTED_NATIVE_OPENAI_SLUGS.has(slug)) return true;
|
|
704
705
|
return false;
|
|
705
706
|
}
|
|
706
707
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isAnthropicOAuthInstance } from "../../providers/anthropic-instance";
|
|
1
2
|
import { effectiveProviderAlias, effectiveProviderAliasDecision } from "../../providers/default-aliases";
|
|
2
3
|
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
|
|
3
4
|
import { execFileSync } from "node:child_process";
|
|
@@ -28,11 +29,12 @@ import {
|
|
|
28
29
|
} from "../model-cache";
|
|
29
30
|
import {
|
|
30
31
|
buildModelsRequest,
|
|
31
|
-
|
|
32
|
+
getModelsOAuthAccessSnapshot,
|
|
32
33
|
observeActiveOAuthAccessToken,
|
|
33
34
|
resolveModelsAuthToken,
|
|
34
35
|
type OAuthActiveTokenObservation,
|
|
35
36
|
} from "../../oauth";
|
|
37
|
+
import { mayResolveModelsOAuth } from "../../oauth/model-discovery-auth";
|
|
36
38
|
import type { OcxConfig, OcxProviderConfig } from "../../types";
|
|
37
39
|
import { modelInList } from "../../types";
|
|
38
40
|
import { CODEX_REASONING_LEVELS, codexEffortRank, configuredReasoningEfforts, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
@@ -145,7 +147,8 @@ export function observedModelsAuthResolver(
|
|
|
145
147
|
return { apiKey: resolveProviderApiKey(provider.apiKey), observed: true };
|
|
146
148
|
}
|
|
147
149
|
|
|
148
|
-
const observation =
|
|
150
|
+
const observation: OAuthActiveTokenObservation = mayResolveModelsOAuth(name, provider)
|
|
151
|
+
? observeActiveOAuthAccessToken(name, authStoreBuffer) : { kind: "missing" };
|
|
149
152
|
outcomes.push({ provider: name, state: observation.kind });
|
|
150
153
|
if (observation.kind !== "available") return { apiKey: undefined, observed: true };
|
|
151
154
|
return {
|
|
@@ -179,7 +182,7 @@ export async function fetchProviderModelsWithAuth(
|
|
|
179
182
|
// generation, so a request started with the former account cannot later publish its result.
|
|
180
183
|
const cacheGeneration = captureModelCacheGeneration(name);
|
|
181
184
|
const isCurrentCacheGeneration = () => isModelCacheGenerationCurrent(name, cacheGeneration);
|
|
182
|
-
const anthropicSelection = name
|
|
185
|
+
const anthropicSelection = isAnthropicOAuthInstance(name) && prov.authMode === "oauth" && mayResolveModelsOAuth(name, prov)
|
|
183
186
|
? captureOAuthAccountSelection(name) : null;
|
|
184
187
|
if (prov.authMode === "forward") return observed([], "authoritative"); // ChatGPT backend has no /models
|
|
185
188
|
const seedVertexDefault = prov.adapter === "google"
|
|
@@ -257,9 +260,11 @@ export async function fetchProviderModelsWithAuth(
|
|
|
257
260
|
}
|
|
258
261
|
return observed(configured, "authoritative");
|
|
259
262
|
}
|
|
260
|
-
const auth: ModelsAuthResolution =
|
|
263
|
+
const auth: ModelsAuthResolution = prov.authMode === "oauth" && !mayResolveModelsOAuth(name, prov)
|
|
264
|
+
? { apiKey: undefined, observed: resolveAuth.kind === "observed" }
|
|
265
|
+
: captured.observedAuth ?? (resolveAuth.kind === "refreshing"
|
|
261
266
|
? prov.authMode === "oauth"
|
|
262
|
-
? await
|
|
267
|
+
? await getModelsOAuthAccessSnapshot(name, prov)
|
|
263
268
|
.then(snapshot => ({
|
|
264
269
|
apiKey: snapshot.accessToken,
|
|
265
270
|
observed: false,
|
|
@@ -274,7 +279,8 @@ export async function fetchProviderModelsWithAuth(
|
|
|
274
279
|
: resolveAuth.resolve(name, prov));
|
|
275
280
|
const apiKey = auth.apiKey;
|
|
276
281
|
const maySendAnthropicDiscovery = () => {
|
|
277
|
-
if (name
|
|
282
|
+
if (!isAnthropicOAuthInstance(name) || prov.authMode !== "oauth") return true;
|
|
283
|
+
if (!mayResolveModelsOAuth(name, prov)) return false;
|
|
278
284
|
const selected = captureOAuthAccountSelection(name);
|
|
279
285
|
const row = auth.oauthAccountId ? getAccountCredentialWithStatus(name, auth.oauthAccountId) : null;
|
|
280
286
|
return !!anthropicSelection && !!selected && !!row && !row.paused && !row.needsReauth
|
|
@@ -633,7 +639,7 @@ export async function fetchProviderModelsWithAuth(
|
|
|
633
639
|
}
|
|
634
640
|
// The captured request predates any refresh, so a refreshing gather rebuilds it
|
|
635
641
|
// from the auth it resolved: the token and its origin, together.
|
|
636
|
-
const request = resolveAuth.kind === "refreshing"
|
|
642
|
+
const request = resolveAuth.kind === "refreshing" && name !== "anthropic2"
|
|
637
643
|
? captureModelsRequest(name, prov, auth.oauthApiBaseUrl)
|
|
638
644
|
: captured.request;
|
|
639
645
|
const url = request.url;
|
|
@@ -653,7 +659,7 @@ export async function fetchProviderModelsWithAuth(
|
|
|
653
659
|
const failedDiscoveryFallback = (
|
|
654
660
|
failure: ProviderModelDiscoveryFailure,
|
|
655
661
|
): { models: CatalogModel[]; fallback: "stale" | "configured"; shouldLog: boolean } => {
|
|
656
|
-
if (!isCurrentCacheGeneration()) {
|
|
662
|
+
if (!isCurrentCacheGeneration() || !maySendAnthropicDiscovery()) {
|
|
657
663
|
return {
|
|
658
664
|
models: withConfiguredRetention(failedDiscoveryConfigured),
|
|
659
665
|
fallback: "configured",
|
|
@@ -854,7 +860,7 @@ export async function fetchProviderModelsWithAuth(
|
|
|
854
860
|
`[opencodex] Provider model discovery for "${name}" returned an authoritative empty catalog; ${droppedConfiguredIds.length > 0 ? `dropping configured model ids: ${droppedConfiguredIds.join(", ")}` : "no models will be exposed"}.`,
|
|
855
861
|
);
|
|
856
862
|
}
|
|
857
|
-
if (!setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
863
|
+
if (!maySendAnthropicDiscovery() || !setCached(name, forCache, Date.now(), cacheGeneration)) {
|
|
858
864
|
return observed(withConfiguredRetention(configured), "degraded");
|
|
859
865
|
}
|
|
860
866
|
markProviderDiscoveryOk(name, liveModelCount);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Exact Codex control-plane ids routed to canonical OpenAI, outside the public native roster. */
|
|
2
|
+
export const CODEX_INTERNAL_OPENAI_MODELS: ReadonlySet<string> = new Set(["codex-auto-review"]);
|
|
3
|
+
|
|
4
|
+
export function isCodexControlPlaneModel(slug: unknown): boolean {
|
|
5
|
+
return typeof slug === "string" && CODEX_INTERNAL_OPENAI_MODELS.has(slug);
|
|
6
|
+
}
|
package/src/codex/journal.ts
CHANGED
|
@@ -85,6 +85,9 @@ export interface RestoreJournalResult {
|
|
|
85
85
|
ownershipRefusal?: CodexHomeOwnerRefusalReason;
|
|
86
86
|
configRestored: boolean;
|
|
87
87
|
profileRestored: boolean;
|
|
88
|
+
/** True only when restore rewrote or removed a file (not when state was already original). */
|
|
89
|
+
configRewritten: boolean;
|
|
90
|
+
profileRewritten: boolean;
|
|
88
91
|
configChanged: boolean;
|
|
89
92
|
profileChanged: boolean;
|
|
90
93
|
complete: boolean;
|
|
@@ -299,12 +302,13 @@ export function restoreJournalState(): RestoreJournalResult {
|
|
|
299
302
|
try { assertCodexHomeOwner(CODEX_HOME); }
|
|
300
303
|
catch (error) {
|
|
301
304
|
if (!(error instanceof CodexHomeOwnerRefusal)) throw error;
|
|
302
|
-
return { configRestored: false, profileRestored: false,
|
|
303
|
-
complete: false, unverified: true, ownershipRefusal: error.reason };
|
|
305
|
+
return { configRestored: false, profileRestored: false, configRewritten: false, profileRewritten: false,
|
|
306
|
+
configChanged: false, profileChanged: false, complete: false, unverified: true, ownershipRefusal: error.reason };
|
|
304
307
|
}
|
|
305
308
|
const journal = readJournal();
|
|
306
309
|
if (!journal) {
|
|
307
|
-
return { configRestored: false, profileRestored: false,
|
|
310
|
+
return { configRestored: false, profileRestored: false, configRewritten: false, profileRewritten: false,
|
|
311
|
+
configChanged: false, profileChanged: false, complete: false, unverified: false };
|
|
308
312
|
}
|
|
309
313
|
const currentConfig = existsSync(CODEX_CONFIG_PATH) ? readFileSync(CODEX_CONFIG_PATH, "utf-8") : null;
|
|
310
314
|
const currentProfile = existsSync(CODEX_PROFILE_PATH) ? readFileSync(CODEX_PROFILE_PATH, "utf-8") : null;
|
|
@@ -316,6 +320,8 @@ export function restoreJournalState(): RestoreJournalResult {
|
|
|
316
320
|
return {
|
|
317
321
|
configRestored: comparison.configAlreadyOriginal,
|
|
318
322
|
profileRestored: comparison.profileAlreadyOriginal,
|
|
323
|
+
configRewritten: false,
|
|
324
|
+
profileRewritten: false,
|
|
319
325
|
configChanged: !configUnchanged,
|
|
320
326
|
profileChanged: !profileUnchanged,
|
|
321
327
|
complete: false,
|
|
@@ -326,14 +332,18 @@ export function restoreJournalState(): RestoreJournalResult {
|
|
|
326
332
|
assertCodexHomeOwner(CODEX_HOME);
|
|
327
333
|
let configRestored = comparison.configAlreadyOriginal;
|
|
328
334
|
let profileRestored = comparison.profileAlreadyOriginal;
|
|
335
|
+
let configRewritten = false;
|
|
336
|
+
let profileRewritten = false;
|
|
329
337
|
if (configUnchanged && !configRestored) {
|
|
330
338
|
atomicWriteFile(CODEX_CONFIG_PATH, comparison.originalConfig);
|
|
331
339
|
configRestored = true;
|
|
340
|
+
configRewritten = true;
|
|
332
341
|
}
|
|
333
342
|
if (profileUnchanged && !profileRestored) {
|
|
334
343
|
if (comparison.originalProfile !== null) {
|
|
335
344
|
atomicWriteFile(CODEX_PROFILE_PATH, comparison.originalProfile);
|
|
336
345
|
profileRestored = true;
|
|
346
|
+
profileRewritten = true;
|
|
337
347
|
} else if (existsSync(CODEX_PROFILE_PATH)) {
|
|
338
348
|
// "There was no profile before, so remove the one we generated." Claiming success
|
|
339
349
|
// without checking is how a caller ends up deleting the journal, reporting a clean
|
|
@@ -343,6 +353,7 @@ export function restoreJournalState(): RestoreJournalResult {
|
|
|
343
353
|
try {
|
|
344
354
|
unlinkSync(CODEX_PROFILE_PATH);
|
|
345
355
|
profileRestored = true;
|
|
356
|
+
profileRewritten = true;
|
|
346
357
|
} catch (error) {
|
|
347
358
|
profileRestored = (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
348
359
|
}
|
|
@@ -355,6 +366,8 @@ export function restoreJournalState(): RestoreJournalResult {
|
|
|
355
366
|
return {
|
|
356
367
|
configRestored,
|
|
357
368
|
profileRestored,
|
|
369
|
+
configRewritten,
|
|
370
|
+
profileRewritten,
|
|
358
371
|
configChanged: !configUnchanged,
|
|
359
372
|
profileChanged: !profileUnchanged,
|
|
360
373
|
complete,
|
|
@@ -367,6 +380,11 @@ export function restoreJournal(): boolean {
|
|
|
367
380
|
return restoreJournalState().complete;
|
|
368
381
|
}
|
|
369
382
|
|
|
383
|
+
/** A no-rewrite recovery is quiet only when the stale journal is actually gone. */
|
|
384
|
+
function warnIfJournalRetained(): void {
|
|
385
|
+
if (existsSync(JOURNAL_PATH)) console.error("⚠️ Codex journal recovery found nothing to restore, but the journal could not be removed; it will be retried on the next start.");
|
|
386
|
+
}
|
|
387
|
+
|
|
370
388
|
export interface ReconcileJournalOptions {
|
|
371
389
|
activeClientApiKeyId?: string;
|
|
372
390
|
}
|
|
@@ -388,7 +406,13 @@ export function reconcileJournal(options: ReconcileJournalOptions = {}): boolean
|
|
|
388
406
|
console.error("⚠️ Codex journal recovery was not verified; current configuration files and the journal were preserved.");
|
|
389
407
|
return false;
|
|
390
408
|
}
|
|
391
|
-
if (!restored.
|
|
409
|
+
if (!restored.complete) {
|
|
410
|
+
console.error("⚠️ Codex journal recovery was incomplete; the journal was preserved — check the Codex files.");
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
// Warn only when restore actually rewrote state; an already-original
|
|
414
|
+
// snapshot still cleans itself up but has nothing to report.
|
|
415
|
+
if (!restored.configRewritten && !restored.profileRewritten) { warnIfJournalRetained(); return false; }
|
|
392
416
|
console.error(`⚠️ Uncommitted or mismatched client routing (${owner.apiKeyId}) was restored from the Codex journal.`);
|
|
393
417
|
return true;
|
|
394
418
|
}
|
|
@@ -406,7 +430,14 @@ export function reconcileJournal(options: ReconcileJournalOptions = {}): boolean
|
|
|
406
430
|
console.error("⚠️ Codex journal recovery was not verified; current configuration files and the journal were preserved.");
|
|
407
431
|
return false;
|
|
408
432
|
}
|
|
409
|
-
if (!restored.
|
|
433
|
+
if (!restored.complete) {
|
|
434
|
+
console.error("⚠️ Codex journal recovery was incomplete; the journal was preserved — check the Codex files.");
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
// Warn only when restore actually rewrote state. A stale journal whose
|
|
438
|
+
// artifacts are already original still cleans itself up (complete removes
|
|
439
|
+
// it) but has nothing to report — claiming a restore then cries wolf.
|
|
440
|
+
if (!restored.configRewritten && !restored.profileRewritten) { warnIfJournalRetained(); return false; }
|
|
410
441
|
console.error(`⚠️ Previous session (PID ${pid}) did not shut down cleanly. Codex state restored from journal.`);
|
|
411
442
|
return true;
|
|
412
443
|
}
|
|
@@ -48,11 +48,19 @@ function lexical(path: string): Stats | null {
|
|
|
48
48
|
try { return lstatSync(path); }
|
|
49
49
|
catch (error) { if (fileErrorCode(error) === "ENOENT") return null; throw error; }
|
|
50
50
|
}
|
|
51
|
+
/** Name the path and the failed check; a group/world-writable owned file gets the chmod that fixes it. */
|
|
52
|
+
function untrustedArtifact(path: string, stat: Stats): Error {
|
|
53
|
+
const prefix = `Private Codex artifact is not an owned regular file: ${path}`;
|
|
54
|
+
if (stat.isSymbolicLink() || !stat.isFile()) return new Error(`${prefix} is not a regular file`);
|
|
55
|
+
if (stat.uid !== process.getuid?.()) return new Error(`${prefix} is owned by uid ${stat.uid}`);
|
|
56
|
+
const mode = (stat.mode & 0o777).toString(8).padStart(4, "0");
|
|
57
|
+
return new Error(`${prefix} is group- or world-writable (mode ${mode}); run chmod 600 ${shQuote(path)} and retry`);
|
|
58
|
+
}
|
|
51
59
|
function snapshot(path: string): Snapshot | null {
|
|
52
60
|
const before = lexical(path);
|
|
53
61
|
if (!before) return null;
|
|
54
62
|
if (!before.isFile() || before.isSymbolicLink() || before.uid !== process.getuid?.() || (before.mode & 0o022)) {
|
|
55
|
-
throw
|
|
63
|
+
throw untrustedArtifact(path, before);
|
|
56
64
|
}
|
|
57
65
|
const bounded = readBoundedRegularFile(path, PRIVATE_FILE_MAX_BYTES);
|
|
58
66
|
if (!bounded || "warning" in bounded) throw new Error("Private Codex artifact is unreadable or changed");
|
|
@@ -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,
|
|
@@ -47,6 +48,7 @@ const TOOL_NAME_CHARS = 160;
|
|
|
47
48
|
const VISIBLE_TEXT_CHUNK_CHARS = 16_384;
|
|
48
49
|
|
|
49
50
|
const ENVELOPE_TAGS = [
|
|
51
|
+
"system-reminder",
|
|
50
52
|
"codex_internal_context",
|
|
51
53
|
"recommended_plugins",
|
|
52
54
|
"environment_context",
|
|
@@ -302,7 +304,8 @@ function taskWithoutProtectedEnvelopes(text: string): string {
|
|
|
302
304
|
}
|
|
303
305
|
} else {
|
|
304
306
|
if (stack.length === 0) appendTrimmedRange(visible, "\n", 0, 1);
|
|
305
|
-
|
|
307
|
+
// A Codex goal quoted inside a Claude reminder is context, never a fallback task.
|
|
308
|
+
if (!completedGoal && !goal && tag === "codex_internal_context" && !stack.includes("system-reminder")) {
|
|
306
309
|
goal = { sample: emptyTextSample(), pendingWhitespace: emptyTextSample() };
|
|
307
310
|
goalDepth = stack.length;
|
|
308
311
|
}
|
|
@@ -674,7 +677,7 @@ function selfHostedApiKey(name: string, apiKey: string | undefined): string | un
|
|
|
674
677
|
* only while the row still matches the registry transport, and the environment fallbacks exist
|
|
675
678
|
* only for that URL. A retargeted `jev` row therefore keeps today's behavior instead of becoming a
|
|
676
679
|
* custom destination. Any other id must be an enabled `jev-decision` row whose baseUrl is a
|
|
677
|
-
* `/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
|
|
678
681
|
* TypeSafe credential can reach a self-hosted service. `undefined` means no usable decision
|
|
679
682
|
* service (reported through the existing `missing_key` gate); `null` means the request's
|
|
680
683
|
* destination scope refused it before any credential access.
|
|
@@ -707,7 +710,7 @@ function jevDecisionEndpoint(
|
|
|
707
710
|
};
|
|
708
711
|
}
|
|
709
712
|
if (configured?.adapter !== "jev-decision" || typeof configured.baseUrl !== "string") return undefined;
|
|
710
|
-
const url = configured.baseUrl
|
|
713
|
+
const url = jevDecisionEndpointUrl(configured.baseUrl);
|
|
711
714
|
if (!url || !isSystemOneEndpoint(url)) return undefined;
|
|
712
715
|
// `jev-latest` is TypeSafe's model name; a self-hosted host must name its own.
|
|
713
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
|