@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005
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/bin/ocx.mjs +11 -10
- package/gui/dist/assets/App-CppMz-6w.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
- package/gui/dist/assets/index-DQt9srNi.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +62 -19
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +33 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +37 -9
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +29 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1190
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +35 -9
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +109 -0
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +55 -82
- package/src/cli/doctor.ts +22 -4
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +101 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +69 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +9 -1
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +116 -64
- package/src/cli/registry.ts +90 -24
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +42 -9
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +57 -80
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/tray-proxy.ts +3 -2
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +53 -67
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +60 -11
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +57 -16
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/main-account.ts +144 -11
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +36 -1
- package/src/codex/quota.ts +67 -16
- package/src/codex/refresh.ts +20 -9
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +145 -0
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +54 -107
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +101 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/jev.ts +13 -3
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +23 -1
- package/src/config/load-degrade.ts +7 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +43 -14
- package/src/config/schema/leaf-validators.ts +8 -3
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +645 -237
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +50 -5
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +106 -19
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +107 -35
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +20 -15
- package/src/oauth/anthropic.ts +34 -3
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +42 -35
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +29 -15
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +13 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +12 -134
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +69 -11
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +29 -27
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +152 -28
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +67 -30
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +64 -7
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +6 -0
- package/src/types/provider.ts +28 -10
- package/src/update/index.ts +2 -1
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effective export metadata for custom `/api/models` rows.
|
|
3
|
+
*
|
|
4
|
+
* A custom row's own fields are the operator's stored OVERRIDE. `/api/models` returns them
|
|
5
|
+
* verbatim because the edit dialog must show what the user set — including an explicit empty
|
|
6
|
+
* "no reasoning" ladder — not what the provider happens to advertise today. But a client
|
|
7
|
+
* export has to serialize what the proxy will actually route: the canonical resolved catalog
|
|
8
|
+
* metadata for the same `provider/modelId`. `listManagementModelRows` resolves that
|
|
9
|
+
* projection here and attaches it additively, so the editor's raw fields and the exporter's
|
|
10
|
+
* effective fields can never overwrite each other.
|
|
11
|
+
*
|
|
12
|
+
* Sources are the ones the routed-row projection already uses — the gathered catalog row for
|
|
13
|
+
* the same slug (the gather materializes custom rows with provider/native inheritance), the
|
|
14
|
+
* registry-enriched provider config, and the ladder resolution in `model-rows.ts`. No
|
|
15
|
+
* independent provider fact tables, and no per-model fetch: everything comes from the roster
|
|
16
|
+
* and config already in hand.
|
|
17
|
+
*/
|
|
18
|
+
import { enrichProviderFromRegistry } from "../../providers/derive";
|
|
19
|
+
import { modelRecordValue } from "../../reasoning-effort";
|
|
20
|
+
import { configuredReasoningSummarySupport } from "../../codex/catalog/model-hints";
|
|
21
|
+
import type { CatalogModel } from "../../codex/catalog";
|
|
22
|
+
import type { OcxConfig } from "../../types";
|
|
23
|
+
import {
|
|
24
|
+
knownReasoningSupport,
|
|
25
|
+
knownToolsSupport,
|
|
26
|
+
type EffectiveModelExportMetadata,
|
|
27
|
+
} from "../../clients/config-export/contracts";
|
|
28
|
+
|
|
29
|
+
/** The stored-override fields a custom row carries, exactly as `config.customModels` writes them. */
|
|
30
|
+
export interface CustomRowOverrideSource {
|
|
31
|
+
provider: string;
|
|
32
|
+
id: string;
|
|
33
|
+
contextWindow?: number;
|
|
34
|
+
inputModalities?: string[];
|
|
35
|
+
/** Explicit ladder; `[]` is a declaration ("no rungs"), not an absence. */
|
|
36
|
+
reasoningEfforts?: string[];
|
|
37
|
+
defaultReasoningEffort?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the effective export metadata for one custom row.
|
|
42
|
+
*
|
|
43
|
+
* Explicit custom overrides win; resolved gathered facts precede provider-config fallbacks.
|
|
44
|
+
* `effectiveReasoningEfforts` is
|
|
45
|
+
* supplied by the caller because its resolution (declared ladder first, then the enriched
|
|
46
|
+
* provider maps, then the pinned native table, then the catalog hints) lives with the other
|
|
47
|
+
* ladder readers in `model-rows.ts`; here it is an input, resolved once per row.
|
|
48
|
+
*/
|
|
49
|
+
export function customRowExportMetadata(
|
|
50
|
+
config: OcxConfig,
|
|
51
|
+
source: CustomRowOverrideSource,
|
|
52
|
+
catalogRow: CatalogModel | undefined,
|
|
53
|
+
effectiveReasoningEfforts: string[] | undefined,
|
|
54
|
+
): EffectiveModelExportMetadata {
|
|
55
|
+
const provider = config.providers[source.provider];
|
|
56
|
+
// The same registry hydration the other per-model readers at this boundary perform, so a
|
|
57
|
+
// custom row whose provider declares facts only in the registry still inherits them. The
|
|
58
|
+
// per-model records are cloned first because the enrichment fills and folds them in place.
|
|
59
|
+
const enriched = provider === undefined ? undefined : {
|
|
60
|
+
...provider,
|
|
61
|
+
...(provider.modelContextWindows ? { modelContextWindows: { ...provider.modelContextWindows } } : {}),
|
|
62
|
+
...(provider.modelInputModalities ? { modelInputModalities: { ...provider.modelInputModalities } } : {}),
|
|
63
|
+
...(provider.modelCapabilities ? { modelCapabilities: { ...provider.modelCapabilities } } : {}),
|
|
64
|
+
...(provider.modelReasoningEfforts ? { modelReasoningEfforts: { ...provider.modelReasoningEfforts } } : {}),
|
|
65
|
+
...(provider.modelDefaultReasoningEfforts
|
|
66
|
+
? { modelDefaultReasoningEfforts: { ...provider.modelDefaultReasoningEfforts } }
|
|
67
|
+
: {}),
|
|
68
|
+
};
|
|
69
|
+
if (enriched) enrichProviderFromRegistry(source.provider, enriched);
|
|
70
|
+
|
|
71
|
+
// Gathered resolved context first after the explicit override: the gather already ran the
|
|
72
|
+
// canonical precedence (operator maps, registry, native clamps) and may have applied a
|
|
73
|
+
// tighter cap than a config map read here would suggest. Re-reading the config maps ahead
|
|
74
|
+
// of it would duplicate that policy and could widen past an observed tighter window.
|
|
75
|
+
const contextWindow = source.contextWindow
|
|
76
|
+
?? catalogRow?.contextWindow
|
|
77
|
+
?? modelRecordValue(enriched?.modelContextWindows, source.id)
|
|
78
|
+
?? enriched?.contextWindow;
|
|
79
|
+
// Same declaration chain `declaredModelInputModalities` reads, as the fallback when neither
|
|
80
|
+
// the override nor the gathered row says anything.
|
|
81
|
+
const declaredModalities = enriched === undefined ? undefined : (
|
|
82
|
+
enriched.modelCapabilities?.[source.id]?.inputModalities
|
|
83
|
+
?? modelRecordValue(enriched.modelInputModalities, source.id)
|
|
84
|
+
);
|
|
85
|
+
const inputModalities = source.inputModalities ?? catalogRow?.inputModalities ?? declaredModalities;
|
|
86
|
+
// The gathered row carries what the materializer already resolved; for a rosterless row the
|
|
87
|
+
// same reader the materializer uses answers from the provider config, so both paths agree.
|
|
88
|
+
const supportsReasoningSummaries = catalogRow?.supportsReasoningSummaries
|
|
89
|
+
?? configuredReasoningSummarySupport(provider, source.id);
|
|
90
|
+
const defaultReasoningEffort = declaredDefaultWithin(
|
|
91
|
+
effectiveReasoningEfforts,
|
|
92
|
+
defaultEffort(source, catalogRow, provider),
|
|
93
|
+
);
|
|
94
|
+
return {
|
|
95
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
96
|
+
...(catalogRow?.maxInputTokens !== undefined ? { maxInputTokens: catalogRow.maxInputTokens } : {}),
|
|
97
|
+
...(catalogRow?.maxOutputTokens !== undefined ? { maxTokens: catalogRow.maxOutputTokens } : {}),
|
|
98
|
+
...(inputModalities !== undefined ? { inputModalities: [...inputModalities] } : {}),
|
|
99
|
+
...(effectiveReasoningEfforts !== undefined ? { reasoningEfforts: [...effectiveReasoningEfforts] } : {}),
|
|
100
|
+
...(defaultReasoningEffort !== undefined ? { defaultReasoningEffort } : {}),
|
|
101
|
+
...(knownToolsSupport({
|
|
102
|
+
capabilities: catalogRow?.capabilities,
|
|
103
|
+
parallelToolCalls: catalogRow?.parallelToolCalls,
|
|
104
|
+
}) === true ? { supportsTools: true } : {}),
|
|
105
|
+
...(knownReasoningSupport({ reasoningEfforts: effectiveReasoningEfforts, supportsReasoningSummaries }) === true
|
|
106
|
+
? { supportsReasoning: true }
|
|
107
|
+
: {}),
|
|
108
|
+
...(supportsReasoningSummaries !== undefined ? { supportsReasoningSummaries } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The declared default, suppressed unless it is a member of the effective ladder. An empty
|
|
114
|
+
* ladder has no rung to default to, and a default the ladder does not contain describes an
|
|
115
|
+
* effort the proxy would never send — the same rule the settings route applies when it
|
|
116
|
+
* rejects a default "not in the declared reasoningEfforts ladder", applied here so an export
|
|
117
|
+
* never ships a default the row cannot act on.
|
|
118
|
+
*/
|
|
119
|
+
function declaredDefaultWithin(
|
|
120
|
+
effectiveReasoningEfforts: string[] | undefined,
|
|
121
|
+
declared: string | undefined,
|
|
122
|
+
): string | undefined {
|
|
123
|
+
if (declared === undefined) return undefined;
|
|
124
|
+
if (effectiveReasoningEfforts === undefined) return declared;
|
|
125
|
+
return effectiveReasoningEfforts.includes(declared) ? declared : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The declared default, and nothing else. The ladder-preference fallback
|
|
130
|
+
* (`effectiveModelDefaultReasoningEffort`'s medium → low → first) fabricates a default no
|
|
131
|
+
* source declared; it is right for the picker's own preference order but wrong for an export
|
|
132
|
+
* that claims to carry provider truth, so this projection stops at:
|
|
133
|
+
*
|
|
134
|
+
* 1. the explicit custom default (an override always wins),
|
|
135
|
+
* 2. the gathered catalog row's declared default (the gather only lets it ride when it is a
|
|
136
|
+
* member of the effective ladder),
|
|
137
|
+
* 3. the operator's per-model record — the same raw map the routed-row reader consults.
|
|
138
|
+
*
|
|
139
|
+
* `declaredDefaultWithin` then drops the answer when it is not a member of the effective
|
|
140
|
+
* ladder, empty ladder included.
|
|
141
|
+
*/
|
|
142
|
+
function defaultEffort(
|
|
143
|
+
source: CustomRowOverrideSource,
|
|
144
|
+
catalogRow: CatalogModel | undefined,
|
|
145
|
+
provider: { modelDefaultReasoningEfforts?: Record<string, string> } | undefined,
|
|
146
|
+
): string | undefined {
|
|
147
|
+
if (source.defaultReasoningEffort) return source.defaultReasoningEffort;
|
|
148
|
+
if (catalogRow?.defaultReasoningEffort) return catalogRow.defaultReasoningEffort;
|
|
149
|
+
return modelRecordValue(provider?.modelDefaultReasoningEfforts, source.id) ?? undefined;
|
|
150
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Bodies are unchanged from their previous home; only `export` was added.
|
|
10
10
|
*/
|
|
11
11
|
import type { CatalogModel } from "../../codex/catalog";
|
|
12
|
+
import { antigravityFamilyDisabled } from "../../providers/antigravity-effort-families";
|
|
12
13
|
import { observeModelCacheRevision } from "../../codex/model-cache";
|
|
13
14
|
import {
|
|
14
15
|
captureExportConfigAdmission,
|
|
@@ -30,7 +31,13 @@ import {
|
|
|
30
31
|
catalogModelEfforts,
|
|
31
32
|
} from "../../codex/catalog";
|
|
32
33
|
import { SUPPORTED_NATIVE_OPENAI_SLUGS } from "../../codex/catalog/native-models";
|
|
33
|
-
import
|
|
34
|
+
import {
|
|
35
|
+
knownReasoningSupport,
|
|
36
|
+
knownToolsSupport,
|
|
37
|
+
type EffectiveModelExportMetadata,
|
|
38
|
+
type ExportModel,
|
|
39
|
+
} from "../../clients/config-export/contracts";
|
|
40
|
+
import { customRowExportMetadata } from "./model-row-export-metadata";
|
|
34
41
|
import { providerContextCap } from "../../providers/context-cap";
|
|
35
42
|
import { isVisionReasoningEffort, modelRecordValue, sanitizeCodexReasoningEfforts } from "../../reasoning-effort";
|
|
36
43
|
import { routedSlug, slugEquals } from "../../providers/slug-codec";
|
|
@@ -78,6 +85,28 @@ export type ManagementModelRow = Partial<CatalogModel> & {
|
|
|
78
85
|
* config — so this compares values against what the model would inherit anyway.
|
|
79
86
|
*/
|
|
80
87
|
reasoningOverridden?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Tool support a canonical source positively asserts (native identity, a catalog row's
|
|
90
|
+
* `"tools"` capability, or the parallel-call opt-in — the same evidence
|
|
91
|
+
* `src/routing/capability.ts` counts). A row without positive evidence stays absent:
|
|
92
|
+
* unknown is not false.
|
|
93
|
+
*/
|
|
94
|
+
supportsTools?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* True when a canonical source positively asserts reasoning (a non-empty effective ladder
|
|
97
|
+
* or delivered reasoning summaries). Absent means unknown, never "cannot reason": an empty
|
|
98
|
+
* ladder only says the effort is not adjustable.
|
|
99
|
+
*/
|
|
100
|
+
supportsReasoning?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Effective export projection for a row, resolved from canonical sources only — for a
|
|
103
|
+
* custom row the full effective view (the raw override fields above stay exactly what the
|
|
104
|
+
* editor wrote), for routed and native rows the authoritative declared default where the
|
|
105
|
+
* row's own `defaultReasoningEffort` is the picker's preference order instead. Additive on
|
|
106
|
+
* `/api/models`, and its PRESENCE is the authority signal: an absent field inside it means
|
|
107
|
+
* "no declared value", never "fall back to the row".
|
|
108
|
+
*/
|
|
109
|
+
exportMetadata?: EffectiveModelExportMetadata;
|
|
81
110
|
};
|
|
82
111
|
|
|
83
112
|
/** Resolve the exact text and source shown for one routed discovered model. */
|
|
@@ -322,6 +351,13 @@ export async function listManagementModelRows(
|
|
|
322
351
|
...(row.autoCompactTokenLimit !== undefined
|
|
323
352
|
? { autoCompactTokenLimit: row.autoCompactTokenLimit }
|
|
324
353
|
: {}),
|
|
354
|
+
// The pinned default above IS the declared default for a native row — no heuristic
|
|
355
|
+
// touches it — so the authoritative projection carries it verbatim and exists even
|
|
356
|
+
// when empty, which is what tells an export consumer "absence means no default"
|
|
357
|
+
// rather than "fall back to the row".
|
|
358
|
+
exportMetadata: {
|
|
359
|
+
...(defaultReasoningEffort !== undefined ? { defaultReasoningEffort } : {}),
|
|
360
|
+
},
|
|
325
361
|
};
|
|
326
362
|
});
|
|
327
363
|
const customModels: ManagementModelRow[] = (config.customModels ?? []).map(cm => {
|
|
@@ -374,6 +410,34 @@ export async function listManagementModelRows(
|
|
|
374
410
|
// the guard and the value) re-read the same file thousands of times on a large roster.
|
|
375
411
|
const routedSlugs = publicModels.filter(m => m.provider !== "combo").map(m => routedSlug(m.provider, m.id));
|
|
376
412
|
const catalogLadders = catalogModelEfforts([...new Set(routedSlugs)]);
|
|
413
|
+
// The gathered roster, keyed once by Codex-facing slug. A custom row looks itself up here —
|
|
414
|
+
// the gather materializes custom rows with the provider/native inheritance the catalog
|
|
415
|
+
// publishes, so this is the canonical resolved metadata the export must preserve, and no
|
|
416
|
+
// per-model fetch ever happens to obtain it.
|
|
417
|
+
const rosterByNamespaced = new Map(publicModels.map(model => [catalogModelSlug(model), model] as const));
|
|
418
|
+
const customRowsWithExportMetadata = visibleCustomModels.map(model => {
|
|
419
|
+
const catalogRow = rosterByNamespaced.get(model.namespaced);
|
|
420
|
+
// The gathered materialization's ladder is authoritative when it has one: it already
|
|
421
|
+
// folded the explicit override in and then applied the canonical bounds (the native
|
|
422
|
+
// clamp for a capability-backed id, learned refusals), so re-declaring the raw override
|
|
423
|
+
// here would undo a bound. Two raw values still win: the explicit empty ladder — "no
|
|
424
|
+
// rungs" is an operator statement no normalization may fill in, even against a stale
|
|
425
|
+
// gathered row — and any override with no gathered ladder behind it.
|
|
426
|
+
const declaredEfforts = Array.isArray(model.reasoningEfforts) && model.reasoningEfforts.length === 0
|
|
427
|
+
? model.reasoningEfforts
|
|
428
|
+
: catalogRow?.reasoningEfforts !== undefined
|
|
429
|
+
? catalogRow.reasoningEfforts
|
|
430
|
+
: model.reasoningEfforts;
|
|
431
|
+
return {
|
|
432
|
+
...model,
|
|
433
|
+
exportMetadata: customRowExportMetadata(
|
|
434
|
+
config,
|
|
435
|
+
model,
|
|
436
|
+
catalogRow,
|
|
437
|
+
effectiveModelReasoningEfforts(config, model.provider, model.id, declaredEfforts, catalogLadders),
|
|
438
|
+
),
|
|
439
|
+
};
|
|
440
|
+
});
|
|
377
441
|
const dedupedRouted = publicModels.map((m): ManagementModelRow | null => {
|
|
378
442
|
// Codex-facing slug (one "/", slug-codec); disabledModels compares tolerate both forms.
|
|
379
443
|
const namespaced = catalogModelSlug(m);
|
|
@@ -395,7 +459,7 @@ export async function listManagementModelRows(
|
|
|
395
459
|
...m,
|
|
396
460
|
...displayName,
|
|
397
461
|
namespaced,
|
|
398
|
-
disabled: [...disabled].some(stored => (
|
|
462
|
+
disabled: antigravityFamilyDisabled(config, m, models) || [...disabled].some(stored => (
|
|
399
463
|
(!nativeAlias && stored === namespaced) || slugEquals(stored, m.provider, m.id)
|
|
400
464
|
)),
|
|
401
465
|
...(contextCap !== undefined ? { contextCap, contextCapped: m.contextCapped === true } : {}),
|
|
@@ -407,13 +471,25 @@ export async function listManagementModelRows(
|
|
|
407
471
|
...(routed ? { reasoningOverridden: reasoningOverrideFor(config, m.provider, m.id, catalogLadders) } : {}),
|
|
408
472
|
...(inputModalitiesDeclared !== undefined ? { inputModalitiesDeclared } : {}),
|
|
409
473
|
...(contextWindowDeclared !== undefined ? { contextWindowDeclared } : {}),
|
|
474
|
+
// The authoritative export default is the GATHERED row's own declared default — never
|
|
475
|
+
// the picker value above, whose medium → low → first fallback invents a default no
|
|
476
|
+
// source declared. The object exists even when empty: its presence is what tells
|
|
477
|
+
// `toExportModel` and the CLI that an absent default means "no default", not "fall
|
|
478
|
+
// back to the row". A default outside the effective ladder is dropped for the same
|
|
479
|
+
// reason the settings route rejects one: the proxy would never send it.
|
|
480
|
+
exportMetadata: {
|
|
481
|
+
...(m.defaultReasoningEffort !== undefined
|
|
482
|
+
&& (reasoningEfforts === undefined || reasoningEfforts.includes(m.defaultReasoningEffort))
|
|
483
|
+
? { defaultReasoningEffort: m.defaultReasoningEffort }
|
|
484
|
+
: {}),
|
|
485
|
+
},
|
|
410
486
|
};
|
|
411
487
|
}).filter((row): row is ManagementModelRow => row !== null);
|
|
412
488
|
// Manual OpenAI rows retain their routed selector but replace the bare dashboard row.
|
|
413
489
|
// Account-qualified rows remain distinct, explicitly selected routes.
|
|
414
490
|
const visibleNative = native.filter(model => model.id.includes("/")
|
|
415
491
|
|| !customNamespaced.has(routedSlug(model.provider, model.id)));
|
|
416
|
-
const rows = [...visibleNative, ...dedupedRouted, ...
|
|
492
|
+
const rows = [...visibleNative, ...dedupedRouted, ...customRowsWithExportMetadata];
|
|
417
493
|
// Include disabled rows and configured aliases before the export visibility filter:
|
|
418
494
|
// a hidden real `x--fast` must never become a synthetic selector for another model.
|
|
419
495
|
const knownIds = config.fastRows === false ? new Set<string>() : knownEffortRowIds(config);
|
|
@@ -422,6 +498,23 @@ export async function listManagementModelRows(
|
|
|
422
498
|
const pending = initialModelSelectionPending(config.providers[row.provider]);
|
|
423
499
|
const modelCosts = Object.hasOwn(config.providers, row.provider)
|
|
424
500
|
? config.providers[row.provider]?.modelCosts : undefined;
|
|
501
|
+
// Derived once here so the wire row carries the same booleans the export projection
|
|
502
|
+
// serializes; the CLI reads /api/models and cannot re-derive them without duplicating
|
|
503
|
+
// this policy. Custom rows read their effective view from `exportMetadata`; routed and
|
|
504
|
+
// native rows read their own evidence.
|
|
505
|
+
const supportsTools = row.exportMetadata?.supportsTools
|
|
506
|
+
?? row.supportsTools
|
|
507
|
+
?? knownToolsSupport({
|
|
508
|
+
capabilities: row.capabilities,
|
|
509
|
+
parallelToolCalls: row.parallelToolCalls,
|
|
510
|
+
native: row.native,
|
|
511
|
+
});
|
|
512
|
+
const supportsReasoning = row.exportMetadata?.supportsReasoning
|
|
513
|
+
?? row.supportsReasoning
|
|
514
|
+
?? knownReasoningSupport({
|
|
515
|
+
reasoningEfforts: row.reasoningEfforts,
|
|
516
|
+
supportsReasoningSummaries: row.supportsReasoningSummaries,
|
|
517
|
+
});
|
|
425
518
|
return {
|
|
426
519
|
...row,
|
|
427
520
|
...(!row.native && modelCosts !== undefined && Object.hasOwn(modelCosts, row.id)
|
|
@@ -429,12 +522,52 @@ export async function listManagementModelRows(
|
|
|
429
522
|
...(pending ? { disabled: true, initialSelectionPending: true } : {}),
|
|
430
523
|
fastRowAvailable: !row.disabled && !pending
|
|
431
524
|
&& !knownIds.has(fastRowId(row.namespaced)) && catalogFastRowEligible(config, row),
|
|
525
|
+
// An explicit false survives; derivation itself is true-or-nothing.
|
|
526
|
+
...(typeof supportsTools === "boolean" ? { supportsTools } : {}),
|
|
527
|
+
...(typeof supportsReasoning === "boolean" ? { supportsReasoning } : {}),
|
|
432
528
|
};
|
|
433
529
|
});
|
|
434
530
|
}
|
|
435
531
|
|
|
436
|
-
/**
|
|
532
|
+
/**
|
|
533
|
+
* `/api/models` row → the narrower input the client-config serializers accept.
|
|
534
|
+
*
|
|
535
|
+
* A row's `exportMetadata`, when present, is the authority: custom rows project through it
|
|
536
|
+
* because their own fields are stored overrides (an absent field means "not overridden"),
|
|
537
|
+
* and routed/native rows carry the declared default in it because the row-level
|
|
538
|
+
* `defaultReasoningEffort` is the picker's preference order, whose medium → low → first
|
|
539
|
+
* fallback fabricates a default no source declared.
|
|
540
|
+
*/
|
|
437
541
|
export function toExportModel(row: ManagementModelRow): ExportModel {
|
|
542
|
+
const effective = row.exportMetadata;
|
|
543
|
+
const contextWindow = effective?.contextWindow ?? row.contextWindow;
|
|
544
|
+
const maxTokens = effective?.maxTokens ?? row.maxOutputTokens;
|
|
545
|
+
const maxInputTokens = effective?.maxInputTokens ?? row.maxInputTokens;
|
|
546
|
+
const inputModalities = effective?.inputModalities ?? row.inputModalities;
|
|
547
|
+
const reasoningEfforts = effective?.reasoningEfforts ?? row.reasoningEfforts;
|
|
548
|
+
// The projection is the authority on the default. When it exists, an absent default means
|
|
549
|
+
// NO declared default — falling back to the row would reintroduce the picker's synthetic
|
|
550
|
+
// medium/low value, which is exactly what this projection exists to keep out of exports.
|
|
551
|
+
// Rows without a projection (native-era shapes, hand-built rows) keep the row's own value.
|
|
552
|
+
const defaultReasoningEffort = effective !== undefined
|
|
553
|
+
? effective.defaultReasoningEffort
|
|
554
|
+
: row.defaultReasoningEffort;
|
|
555
|
+
const supportsReasoningSummaries = effective?.supportsReasoningSummaries ?? row.supportsReasoningSummaries;
|
|
556
|
+
// Projection first, then the row's own explicit boolean (an external producer may state
|
|
557
|
+
// false, which is evidence), and only then derivation from positive evidence.
|
|
558
|
+
const supportsTools = effective?.supportsTools
|
|
559
|
+
?? row.supportsTools
|
|
560
|
+
?? knownToolsSupport({
|
|
561
|
+
capabilities: row.capabilities,
|
|
562
|
+
parallelToolCalls: row.parallelToolCalls,
|
|
563
|
+
native: row.native,
|
|
564
|
+
});
|
|
565
|
+
const supportsReasoning = effective?.supportsReasoning
|
|
566
|
+
?? row.supportsReasoning
|
|
567
|
+
?? knownReasoningSupport({
|
|
568
|
+
reasoningEfforts: row.reasoningEfforts,
|
|
569
|
+
supportsReasoningSummaries: row.supportsReasoningSummaries,
|
|
570
|
+
});
|
|
438
571
|
return {
|
|
439
572
|
namespaced: row.namespaced,
|
|
440
573
|
provider: row.provider,
|
|
@@ -442,11 +575,17 @@ export function toExportModel(row: ManagementModelRow): ExportModel {
|
|
|
442
575
|
fastRowAvailable: row.fastRowAvailable === true,
|
|
443
576
|
...(row.native ? { native: true } : {}),
|
|
444
577
|
...(row.displayName && row.displayNameSource !== "fallback" ? { displayName: row.displayName } : {}),
|
|
445
|
-
...(
|
|
446
|
-
...(
|
|
447
|
-
...(
|
|
448
|
-
...(
|
|
449
|
-
|
|
578
|
+
...(contextWindow !== undefined ? { contextWindow } : {}),
|
|
579
|
+
...(maxTokens !== undefined ? { maxTokens } : {}),
|
|
580
|
+
...(maxInputTokens !== undefined ? { maxInputTokens } : {}),
|
|
581
|
+
...(inputModalities ? { inputModalities } : {}),
|
|
582
|
+
// An empty array is a declaration ("no rungs"), and `[]` is truthy: it survives here.
|
|
583
|
+
...(reasoningEfforts ? { reasoningEfforts } : {}),
|
|
584
|
+
...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
|
|
585
|
+
// An explicit false is evidence and survives; only derivation is true-or-nothing.
|
|
586
|
+
...(typeof supportsTools === "boolean" ? { supportsTools } : {}),
|
|
587
|
+
...(typeof supportsReasoning === "boolean" ? { supportsReasoning } : {}),
|
|
588
|
+
...(supportsReasoningSummaries !== undefined ? { supportsReasoningSummaries } : {}),
|
|
450
589
|
};
|
|
451
590
|
}
|
|
452
591
|
|
|
@@ -51,7 +51,7 @@ import type { CodexNativeRestoreResult } from "../../codex/inject";
|
|
|
51
51
|
import type { OcxConfig } from "../../types";
|
|
52
52
|
import { jsonResponse } from "../auth-cors";
|
|
53
53
|
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
54
|
-
import type
|
|
54
|
+
import { managementInferencePort, type ManagementContext } from "./context";
|
|
55
55
|
|
|
56
56
|
export type NativeIntegrationClientId = "claude" | "grok" | "codex" | "claude-desktop";
|
|
57
57
|
|
|
@@ -540,7 +540,7 @@ async function handleGrokToggle(ctx: ManagementContext): Promise<Response> {
|
|
|
540
540
|
* a stale config.hostname picks the wrong loopback policy branch entirely.
|
|
541
541
|
*/
|
|
542
542
|
const runtime = (deps.readRuntimePort ?? readRuntimePort)(process.pid);
|
|
543
|
-
const port = runtime?.port ?? (
|
|
543
|
+
const port = deps.liveListenPort?.() ?? runtime?.port ?? managementInferencePort(ctx);
|
|
544
544
|
const hostname = runtime?.hostname ?? config.hostname;
|
|
545
545
|
|
|
546
546
|
/*
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readConfigFileSnapshot, validateConfigCandidate } from "../../config/diagnostics";
|
|
2
|
+
import { ConfigWritePublishedError } from "../../config/persist-unlocked";
|
|
1
3
|
import { parseAnthropicModelRoutes, readAnthropicModelRoutes } from "../../oauth/anthropic-model-routes";
|
|
2
4
|
import { effectiveAnthropicAccountThreshold } from "../../oauth/anthropic-account-threshold";
|
|
3
5
|
import { handleAnthropicAccountThreshold } from "./anthropic-account-threshold";
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
readConfigDiagnostics,
|
|
17
19
|
reconcileLiveConfigFromDisk,
|
|
18
20
|
saveConfigPreservingClaudeCode,
|
|
21
|
+
mutatePersistedConfig,
|
|
19
22
|
} from "../../config";
|
|
20
23
|
import {
|
|
21
24
|
clearLoginState,
|
|
@@ -210,6 +213,44 @@ function genericOAuthProviderConfig(provider: string, config: ManagementContext[
|
|
|
210
213
|
return definition?.resolveProviderConfig?.(config) ?? definition?.providerConfig;
|
|
211
214
|
}
|
|
212
215
|
|
|
216
|
+
/** Both Anthropic writers share publication-aware recovery from the atomic mutation owner. */
|
|
217
|
+
function persistAnthropicPoolPatch(
|
|
218
|
+
config: OcxConfig,
|
|
219
|
+
patch: (target: OcxConfig) => { changed: boolean; value: NonNullable<OcxConfig["anthropicAccountPool"]> },
|
|
220
|
+
): { status: "saved"; warning?: "config_bookkeeping_failed" } | { status: "failed"; response: Response } {
|
|
221
|
+
const before = readConfigFileSnapshot();
|
|
222
|
+
const unknown = () => ({ status: "failed" as const, response: jsonResponse({
|
|
223
|
+
error: "Pool settings save state is unknown; reload settings before editing again",
|
|
224
|
+
code: "config_save_state_unknown",
|
|
225
|
+
}, 409) });
|
|
226
|
+
try {
|
|
227
|
+
const saved = mutatePersistedConfig(patch);
|
|
228
|
+
if (saved.status === "unavailable") return unknown();
|
|
229
|
+
config.anthropicAccountPool = saved.value;
|
|
230
|
+
reconcileLiveStateStores();
|
|
231
|
+
return { status: "saved" };
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const persisted = readConfigFileSnapshot();
|
|
234
|
+
if (persisted.diagnostics.source !== "file" || persisted.raw === undefined) return unknown();
|
|
235
|
+
// File reads may salvage hand edits; recovery success requires a strict, authoritative document.
|
|
236
|
+
let validated: ReturnType<typeof validateConfigCandidate>;
|
|
237
|
+
try { validated = validateConfigCandidate(JSON.parse(persisted.raw.replace(/^\uFEFF/, ""))); }
|
|
238
|
+
catch { return unknown(); }
|
|
239
|
+
if (!validated.ok) return unknown();
|
|
240
|
+
const current = validated.config;
|
|
241
|
+
const expected = patch(structuredClone(current)).value;
|
|
242
|
+
const matches = JSON.stringify(current.anthropicAccountPool) === JSON.stringify(expected);
|
|
243
|
+
if (matches) {
|
|
244
|
+
config.anthropicAccountPool = current.anthropicAccountPool;
|
|
245
|
+
// A failed reconcile must not turn confirmed publication back into a rollback.
|
|
246
|
+
try { reconcileLiveStateStores(); } catch { /* Fixed warning covers bookkeeping failure. */ }
|
|
247
|
+
return { status: "saved", warning: "config_bookkeeping_failed" };
|
|
248
|
+
}
|
|
249
|
+
if (error instanceof ConfigWritePublishedError || persisted.raw !== before.raw) return unknown();
|
|
250
|
+
return { status: "failed", response: jsonResponse({ error: "Pool settings could not be saved" }, 500) };
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
213
254
|
export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
214
255
|
const { req, url, config, deps, principal, syncClaudeAgentDefsBestEffort } = ctx;
|
|
215
256
|
|
|
@@ -565,7 +606,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
565
606
|
if (req.method !== "GET" && !isPlainRecord(rawBody)) {
|
|
566
607
|
return jsonResponse({ error: "body must be an object" }, 400);
|
|
567
608
|
}
|
|
568
|
-
const fields = rawBody as { provider?: unknown; enabled?: unknown; strategy?: unknown; stickyLimit?: unknown; autoSwitchThreshold?: unknown; quotaWindow?: unknown; maxConcurrentPerAccount?: unknown; routes?: unknown };
|
|
609
|
+
const fields = rawBody as { provider?: unknown; enabled?: unknown; strategy?: unknown; stickyLimit?: unknown; autoSwitchThreshold?: unknown; quotaWindow?: unknown; maxConcurrentPerAccount?: unknown; routes?: unknown; nativeMessages?: unknown };
|
|
569
610
|
const provider = req.method === "GET"
|
|
570
611
|
? (url.searchParams.get("provider") ?? "").trim().toLowerCase()
|
|
571
612
|
: (typeof fields.provider === "string" ? fields.provider.trim().toLowerCase() : "");
|
|
@@ -573,6 +614,10 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
573
614
|
if (!provider || !kind) {
|
|
574
615
|
return jsonResponse({ error: "pool settings are only available for the codex, anthropic and generic OAuth pools" }, 400);
|
|
575
616
|
}
|
|
617
|
+
if (Object.hasOwn(fields, "nativeMessages")) {
|
|
618
|
+
if (kind !== "anthropic") return jsonResponse({ error: "nativeMessages is only part of the anthropic pool contract" }, 400);
|
|
619
|
+
if (typeof fields.nativeMessages !== "boolean") return jsonResponse({ error: "nativeMessages must be a boolean" }, 400);
|
|
620
|
+
}
|
|
576
621
|
// Validated by the SHARED parsers before any kind-specific write, so a bad strategy or
|
|
577
622
|
// sticky limit is refused identically whichever pool is addressed.
|
|
578
623
|
let strategy: string | undefined;
|
|
@@ -625,17 +670,24 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
625
670
|
if (stickyLimit !== undefined) config.accountPoolStickyLimit = stickyLimit;
|
|
626
671
|
if (autoSwitchThreshold !== undefined) config.autoSwitchThreshold = autoSwitchThreshold;
|
|
627
672
|
} else if (kind === "anthropic") {
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
if (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
673
|
+
const patchPool = (target: OcxConfig) => {
|
|
674
|
+
const pool = { ...(target.anthropicAccountPool ?? {}) };
|
|
675
|
+
if (fields.nativeMessages !== undefined) pool.nativeMessages = fields.nativeMessages as boolean;
|
|
676
|
+
if (fields.enabled !== undefined) pool.enabled = fields.enabled as boolean;
|
|
677
|
+
if (strategy !== undefined) pool.strategy = strategy as never;
|
|
678
|
+
if (stickyLimit !== undefined) pool.stickyLimit = stickyLimit;
|
|
679
|
+
if (autoSwitchThreshold !== undefined) pool.autoSwitchThreshold = autoSwitchThreshold;
|
|
680
|
+
if (quotaWindow !== undefined) pool.quotaWindow = quotaWindow as never;
|
|
681
|
+
if (Object.hasOwn(fields, "routes")) {
|
|
682
|
+
if (fields.routes === null) delete pool.routes;
|
|
683
|
+
else if (parsedRoutes?.ok) pool.routes = parsedRoutes.routes;
|
|
684
|
+
}
|
|
685
|
+
target.anthropicAccountPool = pool;
|
|
686
|
+
return { changed: true, value: pool };
|
|
687
|
+
};
|
|
688
|
+
const saved = persistAnthropicPoolPatch(config, patchPool);
|
|
689
|
+
if (saved.status === "failed") return saved.response;
|
|
690
|
+
return jsonResponse({ ...unifiedPoolSettingsDto(config, provider, kind), ...(saved.warning ? { warning: saved.warning } : {}) });
|
|
639
691
|
} else {
|
|
640
692
|
const prov = config.providers[provider]!;
|
|
641
693
|
const next = { ...(prov.oauthAccountFailover ?? {}) };
|
|
@@ -679,6 +731,7 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
679
731
|
stickyLimit: normalizeAccountPoolStickyLimit(pool.stickyLimit),
|
|
680
732
|
quotaWindow: normalizeAccountPoolQuotaWindow(pool.quotaWindow),
|
|
681
733
|
...readAnthropicModelRoutes(pool.routes),
|
|
734
|
+
nativeMessages: !Object.hasOwn(pool, "nativeMessages") || pool.nativeMessages === true,
|
|
682
735
|
experimental: true,
|
|
683
736
|
});
|
|
684
737
|
}
|
|
@@ -696,8 +749,13 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
696
749
|
quotaWindow?: unknown;
|
|
697
750
|
maxConcurrentPerAccount?: unknown;
|
|
698
751
|
routes?: unknown;
|
|
752
|
+
nativeMessages?: unknown;
|
|
699
753
|
};
|
|
700
754
|
const provider = typeof body.provider === "string" ? body.provider.trim().toLowerCase() : "";
|
|
755
|
+
if (Object.hasOwn(body, "nativeMessages")) {
|
|
756
|
+
if (provider !== "anthropic") return jsonResponse({ error: "nativeMessages is only part of the anthropic pool contract" }, 400);
|
|
757
|
+
if (typeof body.nativeMessages !== "boolean") return jsonResponse({ error: "nativeMessages must be a boolean" }, 400);
|
|
758
|
+
}
|
|
701
759
|
if (provider !== "anthropic") {
|
|
702
760
|
const {
|
|
703
761
|
poolSettingsCapability, genericPoolSettingsDto, parseGenericPoolStrategy, parseGenericAutoSwitchThreshold,
|
|
@@ -796,28 +854,36 @@ export async function handleOauthAccountRoutes(ctx: ManagementContext): Promise<
|
|
|
796
854
|
const parsedLegacyRoutes = Object.hasOwn(body, "routes") && body.routes !== null
|
|
797
855
|
? parseAnthropicModelRoutes(body.routes) : null;
|
|
798
856
|
if (parsedLegacyRoutes && !parsedLegacyRoutes.ok) return jsonResponse({ error: parsedLegacyRoutes.error }, 400);
|
|
799
|
-
const
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
autoSwitchThreshold
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
857
|
+
const legacyPatch = (target: OcxConfig) => {
|
|
858
|
+
const pool = { ...(target.anthropicAccountPool ?? {}) };
|
|
859
|
+
if (body.enabled !== undefined) pool.enabled = enabled;
|
|
860
|
+
else if (pool.enabled === undefined) pool.enabled = false;
|
|
861
|
+
if (body.nativeMessages !== undefined) pool.nativeMessages = body.nativeMessages as boolean;
|
|
862
|
+
if (body.autoSwitchThreshold !== undefined) pool.autoSwitchThreshold = threshold;
|
|
863
|
+
else if (pool.autoSwitchThreshold === undefined) pool.autoSwitchThreshold = 80;
|
|
864
|
+
if (body.strategy !== undefined) pool.strategy = strategy;
|
|
865
|
+
if (body.stickyLimit !== undefined) pool.stickyLimit = stickyLimit;
|
|
866
|
+
if (body.quotaWindow !== undefined) pool.quotaWindow = quotaWindow;
|
|
867
|
+
if (Object.hasOwn(body, "routes")) {
|
|
868
|
+
if (body.routes === null) delete pool.routes;
|
|
869
|
+
else if (parsedLegacyRoutes?.ok) pool.routes = parsedLegacyRoutes.routes;
|
|
870
|
+
}
|
|
871
|
+
target.anthropicAccountPool = pool;
|
|
872
|
+
return { changed: true, value: pool };
|
|
809
873
|
};
|
|
810
|
-
|
|
811
|
-
|
|
874
|
+
const saved = persistAnthropicPoolPatch(config, legacyPatch);
|
|
875
|
+
if (saved.status === "failed") return saved.response;
|
|
812
876
|
return jsonResponse({
|
|
813
877
|
ok: true,
|
|
814
878
|
provider,
|
|
815
|
-
enabled,
|
|
816
|
-
autoSwitchThreshold:
|
|
817
|
-
strategy: normalizeAccountPoolStrategy(strategy),
|
|
818
|
-
stickyLimit: normalizeAccountPoolStickyLimit(stickyLimit),
|
|
819
|
-
quotaWindow: normalizeAccountPoolQuotaWindow(quotaWindow),
|
|
820
|
-
routes: routes ?? null,
|
|
879
|
+
enabled: config.anthropicAccountPool?.enabled === true,
|
|
880
|
+
autoSwitchThreshold: config.anthropicAccountPool?.autoSwitchThreshold ?? 80,
|
|
881
|
+
strategy: normalizeAccountPoolStrategy(config.anthropicAccountPool?.strategy),
|
|
882
|
+
stickyLimit: normalizeAccountPoolStickyLimit(config.anthropicAccountPool?.stickyLimit),
|
|
883
|
+
quotaWindow: normalizeAccountPoolQuotaWindow(config.anthropicAccountPool?.quotaWindow),
|
|
884
|
+
routes: config.anthropicAccountPool?.routes ?? null,
|
|
885
|
+
nativeMessages: !Object.hasOwn(config.anthropicAccountPool ?? {}, "nativeMessages") || config.anthropicAccountPool?.nativeMessages === true,
|
|
886
|
+
...(saved.warning ? { warning: saved.warning } : {}),
|
|
821
887
|
experimental: true,
|
|
822
888
|
});
|
|
823
889
|
}
|