@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
|
@@ -30,6 +30,8 @@ export type ExemptionReason =
|
|
|
30
30
|
| "gui-invalidation"
|
|
31
31
|
/** Requires a dashboard browser session. Includes the user-consent star boundary. */
|
|
32
32
|
| "session-only"
|
|
33
|
+
/** Opens a browser view; data operations remain independently CLI-addressable. */
|
|
34
|
+
| "browser-navigation"
|
|
33
35
|
/** Deliberately returns 405; there is nothing to drive. */
|
|
34
36
|
| "disabled"
|
|
35
37
|
/** Gated on a process-scoped capability principal, not an operator action. */
|
|
@@ -43,10 +45,8 @@ export type ExemptionReason =
|
|
|
43
45
|
/** Older clients use this alias; the current CLI drives its declared replacement. */
|
|
44
46
|
| "compatibility-alias"
|
|
45
47
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* only meaningful to the caller that is about to commit it, and a scripted caller drives the
|
|
49
|
-
* mutation directly.
|
|
48
|
+
* An interactive-only preview that lacks a supported terminal confirmation workflow.
|
|
49
|
+
* Named CLI preview/commit workflows are operational capabilities, not this exemption.
|
|
50
50
|
*/
|
|
51
51
|
| "interactive-preview"
|
|
52
52
|
/** Unreachable in the live dispatch order; delete rather than expose. */
|
|
@@ -121,7 +121,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
121
121
|
{ method: "PUT", path: "/api/codex-auth/accounts/alias", module: "codex/auth-api/routes", mutates: true },
|
|
122
122
|
{ method: "PUT", path: "/api/codex-auth/accounts/pause", module: "codex/auth-api/routes", mutates: true },
|
|
123
123
|
{ method: "PUT", path: "/api/codex-auth/accounts/pause-exhausted", module: "codex/auth-api/routes", mutates: true },
|
|
124
|
-
{ method: "PUT", path: "/api/codex-auth/accounts/credits", module: "codex/auth-api/routes", mutates: true
|
|
124
|
+
{ method: "PUT", path: "/api/codex-auth/accounts/credits", module: "codex/auth-api/routes", mutates: true },
|
|
125
125
|
{ method: "PUT", path: "/api/codex-auth/accounts/priority", module: "codex/auth-api/routes", mutates: true },
|
|
126
126
|
{ method: "PUT", path: "/api/codex-auth/active", module: "codex/auth-api/routes", mutates: true },
|
|
127
127
|
{ method: "PUT", path: "/api/codex-auth/auto-switch", module: "codex/auth-api/routes", mutates: true },
|
|
@@ -147,13 +147,12 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
147
147
|
{ method: "GET", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: false },
|
|
148
148
|
{ method: "POST", path: "/api/injection-model/suggest", module: "server/management/agent-settings-routes", mutates: false },
|
|
149
149
|
{ method: "GET", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: false },
|
|
150
|
-
{ method: "GET", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: false },
|
|
151
150
|
{ method: "GET", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: false },
|
|
152
151
|
{ method: "POST", path: "/api/claude-desktop/apply", module: "server/management/agent-settings-routes", mutates: true },
|
|
153
152
|
{ method: "POST", path: "/api/grok/apply", module: "server/management/agent-settings-routes", mutates: true },
|
|
154
153
|
{ method: "GET", path: "/api/grok/reset-coupons", module: "server/management/grok-coupon-routes", mutates: false },
|
|
155
154
|
{ method: "POST", path: "/api/grok/reset-coupons/consume", module: "server/management/grok-coupon-routes", mutates: true },
|
|
156
|
-
{ method: "GET", path: "/api/anthropic/reset-grants", module: "server/management/anthropic-reset-grant-routes", mutates: false
|
|
155
|
+
{ method: "GET", path: "/api/anthropic/reset-grants", module: "server/management/anthropic-reset-grant-routes", mutates: false },
|
|
157
156
|
{ method: "POST", path: "/api/anthropic/reset-grants/consume", module: "server/management/anthropic-reset-grant-routes", mutates: true, exempt: { reason: "session-only", why: "Spending a Claude reset grant requires the gui-session principal (anthropic-reset-grant-routes.ts handleConsume); the admin token is refused." } },
|
|
158
157
|
{ method: "PUT", path: "/api/claude-code", module: "server/management/agent-settings-routes", mutates: true },
|
|
159
158
|
{ method: "PUT", path: "/api/claude-desktop", module: "server/management/agent-settings-routes", mutates: true },
|
|
@@ -169,8 +168,10 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
169
168
|
{ method: "PUT", path: "/api/grok/selection", module: "server/management/agent-settings-routes", mutates: true },
|
|
170
169
|
{ method: "PUT", path: "/api/injection-model", module: "server/management/agent-settings-routes", mutates: true },
|
|
171
170
|
{ method: "PUT", path: "/api/subagent-model-fallback", module: "server/management/agent-settings-routes", mutates: true },
|
|
172
|
-
{ method: "PUT", path: "/api/subagent-models", module: "server/management/agent-settings-routes", mutates: true },
|
|
173
171
|
{ method: "PUT", path: "/api/v2", module: "server/management/agent-settings-routes", mutates: true },
|
|
172
|
+
// server/management/subagent-model-routes
|
|
173
|
+
{ method: "GET", path: "/api/subagent-models", module: "server/management/subagent-model-routes", mutates: false },
|
|
174
|
+
{ method: "PUT", path: "/api/subagent-models", module: "server/management/subagent-model-routes", mutates: true },
|
|
174
175
|
// server/management/codex-agent-role-routes
|
|
175
176
|
{ method: "GET", path: "/api/codex-agent-roles", module: "server/management/codex-agent-role-routes", mutates: false },
|
|
176
177
|
{ method: "POST", path: "/api/codex-agent-roles/auto-assign", module: "server/management/codex-agent-role-routes", mutates: false, mechanism: "path-constant" },
|
|
@@ -184,10 +185,10 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
184
185
|
{ method: "GET", path: "/api/client-integrations/aside/profiles/{profileId}", module: "server/management/aside-profile-routes", mutates: false, mechanism: "prefix-decode" },
|
|
185
186
|
{ method: "PUT", path: "/api/client-integrations/aside/profiles/{profileId}", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode" },
|
|
186
187
|
{ method: "GET", path: "/api/client-integrations/aside/profiles/journal", module: "server/management/aside-profile-routes", mutates: false, mechanism: "prefix-decode" },
|
|
187
|
-
{ method: "DELETE", path: "/api/client-integrations/aside/profiles/journal", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode"
|
|
188
|
+
{ method: "DELETE", path: "/api/client-integrations/aside/profiles/journal", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode" },
|
|
188
189
|
{ method: "GET", path: "/api/client-integrations/aside/profiles/{profileId}/journal", module: "server/management/aside-profile-routes", mutates: false, mechanism: "prefix-decode" },
|
|
189
|
-
{ method: "POST", path: "/api/client-integrations/aside/profiles/{profileId}/preview", module: "server/management/aside-profile-routes", mutates: false, mechanism: "prefix-decode"
|
|
190
|
-
{ method: "DELETE", path: "/api/client-integrations/aside/profiles/{profileId}/journal", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode"
|
|
190
|
+
{ method: "POST", path: "/api/client-integrations/aside/profiles/{profileId}/preview", module: "server/management/aside-profile-routes", mutates: false, mechanism: "prefix-decode" },
|
|
191
|
+
{ method: "DELETE", path: "/api/client-integrations/aside/profiles/{profileId}/journal", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode" },
|
|
191
192
|
{ method: "POST", path: "/api/client-integrations/aside/profiles/{profileId}/restore", module: "server/management/aside-profile-routes", mutates: true, mechanism: "prefix-decode" },
|
|
192
193
|
// server/management/codex-prompt-routes
|
|
193
194
|
{ method: "GET", path: "/api/codex-prompt", module: "server/management/codex-prompt-routes", mutates: false },
|
|
@@ -226,15 +227,15 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
226
227
|
// server/management/integration-routes
|
|
227
228
|
{ method: "GET", path: "/api/client-integrations", module: "server/management/integration-routes", mutates: false },
|
|
228
229
|
{ method: "GET", path: "/api/client-integrations/journal", module: "server/management/integration-routes", mutates: false },
|
|
229
|
-
{ method: "DELETE", path: "/api/client-integrations/journal", module: "server/management/integration-routes", mutates: true
|
|
230
|
+
{ method: "DELETE", path: "/api/client-integrations/journal", module: "server/management/integration-routes", mutates: true },
|
|
230
231
|
{ method: "POST", path: "/api/client-integrations/restore", module: "server/management/integration-routes", mutates: true },
|
|
231
|
-
{ method: "POST", path: "/api/client-integrations/preview", module: "server/management/integration-routes", mutates: false
|
|
232
|
-
{ method: "POST", path: "/api/client-integrations/restore/preview", module: "server/management/integration-routes", mutates: false
|
|
232
|
+
{ method: "POST", path: "/api/client-integrations/preview", module: "server/management/integration-routes", mutates: false },
|
|
233
|
+
{ method: "POST", path: "/api/client-integrations/restore/preview", module: "server/management/integration-routes", mutates: false },
|
|
233
234
|
// server/management/lab-automation-routes
|
|
234
235
|
{ method: "GET", path: "/api/lab/automation", module: "server/management/lab-automation-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
235
236
|
{ method: "GET", path: "/api/lab/automation/runs", module: "server/management/lab-automation-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
236
|
-
{ method: "POST", path: "/api/lab/automation/run", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
237
|
-
{ method: "PUT", path: "/api/lab/automation", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
237
|
+
{ method: "POST", path: "/api/lab/automation/run", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab run explicitly executes a local scenario. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
238
|
+
{ method: "PUT", path: "/api/lab/automation", module: "server/management/lab-automation-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab automation enable/disable updates local policy. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
238
239
|
// server/management/lab-routes
|
|
239
240
|
{ method: "GET", path: "/api/lab/artifacts", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
240
241
|
{ method: "GET", path: "/api/lab/catalog", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
@@ -245,10 +246,10 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
245
246
|
{ method: "GET", path: "/api/lab/status", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
246
247
|
{ method: "GET", path: "/api/lab/subjects", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
247
248
|
{ method: "GET", path: "/api/lab/verdicts", module: "server/management/lab-routes", mutates: false, exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
248
|
-
{ method: "POST", path: "/api/lab/public/community/import", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
249
|
-
{ method: "POST", path: "/api/lab/public/export", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
250
|
-
{ method: "POST", path: "/api/lab/public/preview", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
251
|
-
{ method: "POST", path: "/api/lab/public/verify", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "
|
|
249
|
+
{ method: "POST", path: "/api/lab/public/community/import", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab public import reads a local bundle file. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
250
|
+
{ method: "POST", path: "/api/lab/public/export", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab public export uses local evidence. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
251
|
+
{ method: "POST", path: "/api/lab/public/preview", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab public preview uses local evidence. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
252
|
+
{ method: "POST", path: "/api/lab/public/verify", module: "server/management/lab-routes", mutates: true, exempt: { reason: "deferred-verb", why: "ocx lab public verify reads a local bundle file. The local invocation does not control the selected running proxy; this HTTP operation remains deferred.", owner: "wp7", ownerDoc: "devlog/_plan/260828_ocx_agentic_control/060_phase_gui_parity.md" } },
|
|
252
253
|
// server/management/logs-usage-routes
|
|
253
254
|
{ method: "GET", path: "/api/claude/inbound-debug", module: "server/management/logs-usage-routes", mutates: false },
|
|
254
255
|
{ method: "GET", path: "/api/debug", module: "server/management/logs-usage-routes", mutates: false },
|
|
@@ -264,7 +265,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
264
265
|
// server/management/usage-timeline-routes
|
|
265
266
|
{ method: "GET", path: "/api/usage/timeline", module: "server/management/usage-timeline-routes", mutates: false },
|
|
266
267
|
// server/management/companion-routes
|
|
267
|
-
{ method: "POST", path: "/api/companion/open-in-browser", module: "server/management/companion-routes", mutates: true, exempt: { reason: "
|
|
268
|
+
{ method: "POST", path: "/api/companion/open-in-browser", module: "server/management/companion-routes", mutates: true, exempt: { reason: "browser-navigation", why: "Opens the companion's current view in the system browser under normal management admission; the CLI reads the underlying settings and usage directly." } },
|
|
268
269
|
{ method: "GET", path: "/api/companion/settings", module: "server/management/companion-routes", mutates: false },
|
|
269
270
|
{ method: "PUT", path: "/api/companion/settings", module: "server/management/companion-routes", mutates: true },
|
|
270
271
|
{ method: "POST", path: "/api/storage/cleanup", module: "server/management/logs-usage-routes", mutates: true },
|
|
@@ -314,7 +315,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
314
315
|
{ method: "GET", path: "/api/oauth/accounts/pool", module: "server/management/oauth-account-routes", mutates: false },
|
|
315
316
|
{ method: "GET", path: "/api/pool/settings", module: "server/management/oauth-account-routes", mutates: false },
|
|
316
317
|
{ method: "PUT", path: "/api/pool/settings", module: "server/management/oauth-account-routes", mutates: true },
|
|
317
|
-
{ method: "PATCH", path: "/api/pool/settings", module: "server/management/oauth-account-routes", mutates: true },
|
|
318
|
+
{ method: "PATCH", path: "/api/pool/settings", module: "server/management/oauth-account-routes", mutates: true, exempt: { reason: "compatibility-alias", why: "Handled by the same partial settings branch as PUT /api/pool/settings; current CLI strategy, sticky and routes commands use PUT, not PATCH." } },
|
|
318
319
|
{ method: "GET", path: "/api/oauth/providers", module: "server/management/oauth-account-routes", mutates: false },
|
|
319
320
|
{ method: "GET", path: "/api/oauth/status", module: "server/management/oauth-account-routes", mutates: false },
|
|
320
321
|
{ method: "GET", path: "/api/providers/keys", module: "server/management/oauth-account-routes", mutates: false },
|
|
@@ -349,7 +350,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
349
350
|
{ method: "PATCH", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
350
351
|
{ method: "POST", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
351
352
|
{ method: "POST", path: "/api/providers/test", module: "server/management/provider-routes", mutates: true },
|
|
352
|
-
{ method: "PUT", path: "/api/providers", module: "server/management/provider-routes", mutates: true
|
|
353
|
+
{ method: "PUT", path: "/api/providers", module: "server/management/provider-routes", mutates: true },
|
|
353
354
|
{ method: "PUT", path: "/api/provider-context-caps", module: "server/management/provider-routes", mutates: true },
|
|
354
355
|
// server/management/quota-reset-routes
|
|
355
356
|
{ method: "GET", path: "/api/quota-resets", module: "server/management/quota-reset-routes", mutates: false, mechanism: "negated-guard" },
|
|
@@ -388,14 +389,14 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
388
389
|
{ method: "GET", path: "/api/link/candidates", module: "server/management/link-routes", mutates: false, exempt: { reason: "session-only", why: "SSH candidates are a dashboard surface: a paired session, or on a standalone runtime the current loopback dashboard session on trusted loopback ingress; admin-token and Tailscale identity sessions are refused." } },
|
|
389
390
|
{ method: "POST", path: "/api/link/probe", module: "server/management/link-routes", mutates: true, exempt: { reason: "session-only", why: "SSH probing and host-key presentation are part of the interactive dashboard consent flow (paired, or standalone loopback dashboard on trusted loopback ingress)." } },
|
|
390
391
|
{ method: "POST", path: "/api/link/confirm-host", module: "server/management/link-routes", mutates: true, exempt: { reason: "session-only", why: "Persisting a host key requires the dashboard session that saw the fingerprint (paired, or standalone loopback dashboard on trusted loopback ingress)." } },
|
|
391
|
-
{ method: "POST", path: "/api/link/join", module: "server/management/link-routes", mutates: true, exempt: { reason: "session-only", why: "Joining a confirmed Home issues a link credential and restarts this standalone runtime as a client on its configured port, which briefly interrupts running Codex turns. It
|
|
392
|
+
{ method: "POST", path: "/api/link/join", module: "server/management/link-routes", mutates: true, exempt: { reason: "session-only", why: "Joining a confirmed Home issues a link credential and restarts this standalone runtime as a client on its configured port, which briefly interrupts running Codex turns. It requires an operator-paired dashboard session and a pre-join notice; unpaired standalone loopback, admin-token and Tailscale identity sessions are refused." } },
|
|
392
393
|
{ method: "POST", path: "/api/link/apply", module: "server/management/link-routes", mutates: true, exempt: { reason: "session-only", why: "Applying a link issues a data key and starts a remote tunnel, so it requires a dashboard session (paired, or standalone loopback dashboard on trusted loopback ingress)." } },
|
|
393
394
|
{ method: "DELETE", path: "/api/link/{id}", module: "server/management/link-routes", mutates: true, mechanism: "regex" },
|
|
394
395
|
{ method: "POST", path: "/api/link/issue", module: "server/management/link-routes", mutates: true },
|
|
395
396
|
// server/management/remote-workspace-routes
|
|
396
|
-
{ method: "GET", path: "/api/remote-workspace", module: "server/management/remote-workspace-routes", mutates: false
|
|
397
|
-
{ method: "GET", path: "/api/remote-workspace/runtimes", module: "server/management/remote-workspace-routes", mutates: false
|
|
398
|
-
{ method: "GET", path: "/api/remote-workspace/sessions", module: "server/management/remote-workspace-routes", mutates: false
|
|
397
|
+
{ method: "GET", path: "/api/remote-workspace", module: "server/management/remote-workspace-routes", mutates: false },
|
|
398
|
+
{ method: "GET", path: "/api/remote-workspace/runtimes", module: "server/management/remote-workspace-routes", mutates: false },
|
|
399
|
+
{ method: "GET", path: "/api/remote-workspace/sessions", module: "server/management/remote-workspace-routes", mutates: false },
|
|
399
400
|
{ method: "POST", path: "/api/remote-workspace/pairing", module: "server/management/remote-workspace-routes", mutates: true, exempt: { reason: "session-only", why: "Creating a device enrollment grant authorizes another computer, so only a dashboard consent session may request one." } },
|
|
400
401
|
{ method: "POST", path: "/api/remote-workspace/sessions", module: "server/management/remote-workspace-routes", mutates: true, exempt: { reason: "session-only", why: "Starting a Hub-authenticated model session against a remote computer requires an interactive dashboard consent session." } },
|
|
401
402
|
{ method: "POST", path: "/api/remote-workspace/sessions/{id}/prompt", module: "server/management/remote-workspace-routes", mutates: true, mechanism: "regex", exempt: { reason: "session-only", why: "A prompt can execute tools on the selected remote computer and therefore requires an interactive dashboard consent session." } },
|
|
@@ -421,6 +422,7 @@ export const MANAGEMENT_ROUTES: readonly ManagementRoute[] = [
|
|
|
421
422
|
{ method: "PUT", path: "/api/providers/{provider}/model-aliases", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
|
|
422
423
|
{ method: "GET", path: "/api/providers/{provider}/model-costs", module: "server/management/model-routes", mutates: false, mechanism: "regex" },
|
|
423
424
|
{ method: "PUT", path: "/api/providers/{provider}/model-costs", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
|
|
425
|
+
{ method: "PUT", path: "/api/providers/{provider}/model-display-names", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
|
|
424
426
|
{ method: "PUT", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
|
|
425
427
|
{ method: "DELETE", path: "/api/custom-models/{id}", module: "server/management/model-routes", mutates: true, mechanism: "regex" },
|
|
426
428
|
{ method: "GET", path: "/api/lab/subjects/{id}", module: "server/management/lab-routes", mutates: false, mechanism: "regex", exempt: { reason: "local-transport", why: "ocx lab reads the same rows from the local SQLite projection; src/cli/lab.ts imports ../lab/query directly and never fetches /api/lab." } },
|
|
@@ -100,7 +100,7 @@ export type CostResult =
|
|
|
100
100
|
| { kind: "value"; estimate: NonNullable<ReturnType<typeof estimateRequestCost>>; estimateReasons: CostEstimateReason[] }
|
|
101
101
|
| { kind: "unavailable"; reason: MetricUnavailableReason };
|
|
102
102
|
|
|
103
|
-
export type MetricSource = Pick<RequestLogEntry, "provider" | "model" | "durationMs" | "firstOutputMs" | "usageStatus" | "usage" | "requestedServiceTier" | "configuredServiceTier" | "responseServiceTier" | "tierOutcome" | "routeDecision" | "cacheProvenance"> & {
|
|
103
|
+
export type MetricSource = Pick<RequestLogEntry, "provider" | "model" | "durationMs" | "firstOutputMs" | "genStartMs" | "lastOutputMs" | "usageStatus" | "usage" | "requestedServiceTier" | "configuredServiceTier" | "responseServiceTier" | "tierOutcome" | "routeDecision" | "cacheProvenance"> & {
|
|
104
104
|
attempts?: readonly PersistedUsageAttempt[];
|
|
105
105
|
};
|
|
106
106
|
|
|
@@ -129,10 +129,11 @@ export function tokPerSecondResult(entry: Pick<MetricSource, "durationMs" | "usa
|
|
|
129
129
|
export const MIN_DECODE_WINDOW_MS = 1_000;
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
|
-
* Estimated DECODE throughput: output tokens over the window
|
|
132
|
+
* Estimated DECODE throughput: output tokens over the measured output window (#4038): the generation window when
|
|
133
|
+
* recorded, otherwise the window after the first token.
|
|
133
134
|
*
|
|
134
|
-
* Strictly additive. `tokensPerSecond
|
|
135
|
-
*
|
|
135
|
+
* Strictly additive. `tokensPerSecond` and `tokPerSecondResult` are untouched, rows only gain the
|
|
136
|
+
* optional window pair, and the end-to-end rate beside it keeps meaning exactly what it
|
|
136
137
|
* has always meant — it is documented as end-to-end, so this is a missing metric rather than a
|
|
137
138
|
* miscalculated one.
|
|
138
139
|
*
|
|
@@ -140,13 +141,26 @@ export const MIN_DECODE_WINDOW_MS = 1_000;
|
|
|
140
141
|
* not the provider's own generation start, so this can never be more than an estimate no matter
|
|
141
142
|
* how long the window is. Saying so in the payload is the honest half of the answer to #4040;
|
|
142
143
|
* MIN_DECODE_WINDOW_MS is the other half.
|
|
144
|
+
*
|
|
145
|
+
* When the row carries the generation window (`genStartMs` / `lastOutputMs`, #6309) that window is
|
|
146
|
+
* used instead. `firstOutputMs` is the first VISIBLE delta, so on a reasoning model the post-TTFT
|
|
147
|
+
* window leaves out the reasoning phase while `outputTokens` still counts its tokens; the
|
|
148
|
+
* generation window spans both and stops at the last delta rather than at stream close.
|
|
143
149
|
*/
|
|
144
150
|
export function decodeTokPerSecondResult(
|
|
145
|
-
entry: Pick<MetricSource, "durationMs" | "firstOutputMs" | "usageStatus" | "usage">,
|
|
151
|
+
entry: Pick<MetricSource, "durationMs" | "firstOutputMs" | "genStartMs" | "lastOutputMs" | "usageStatus" | "usage">,
|
|
146
152
|
): TokPerSecondResult {
|
|
147
153
|
if (!entry.usage) return { kind: "unavailable", reason: "usage_missing" };
|
|
148
154
|
if (entry.usageStatus === "unsupported") return { kind: "unavailable", reason: "usage_unsupported" };
|
|
149
155
|
if (entry.usage.outputTokens <= 0) return { kind: "unavailable", reason: "output_missing" };
|
|
156
|
+
const { genStartMs, lastOutputMs } = entry;
|
|
157
|
+
if (genStartMs !== undefined && lastOutputMs !== undefined && Number.isFinite(genStartMs) && Number.isFinite(lastOutputMs)) {
|
|
158
|
+
const generationMs = lastOutputMs - genStartMs;
|
|
159
|
+
if (generationMs <= 0) return { kind: "unavailable", reason: "invalid_duration" };
|
|
160
|
+
if (generationMs < MIN_DECODE_WINDOW_MS) return { kind: "unavailable", reason: "decode_window_too_short" };
|
|
161
|
+
const value = tokensPerSecond(entry.usage.outputTokens, generationMs);
|
|
162
|
+
return value === null ? { kind: "unavailable", reason: "invalid_duration" } : { kind: "value", value, estimated: true };
|
|
163
|
+
}
|
|
150
164
|
// A row that predates TTFT capture, or a non-streaming turn that never recorded one, has no
|
|
151
165
|
// window to measure. That is a different fact from a bad duration, so it gets its own reason.
|
|
152
166
|
if (entry.firstOutputMs === undefined) return { kind: "unavailable", reason: "ttft_missing" };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { ManagementContext } from "./context";
|
|
2
|
+
import { catalogModelSlug, filterCatalogVisibleModels, nativeModelRows, listCatalogNativeSlugs } from "../../codex/catalog";
|
|
3
|
+
import { subagentSelectableModels } from "../../codex/subagent-selectable-models";
|
|
4
|
+
import { saveConfigPreservingClaudeCode, deleteConfigTopLevelKey, mutatePersistedConfig, adoptPersistedClaudeCode, initializePersistedConfigIfMissing, observeInitialConfigState } from "../../config";
|
|
5
|
+
import { captureConfigTopLevelRollback, parsedConfigRebaseDeletionKeys, projectConfigRebaseProvenance, deleteConfigObjectChildKey } from "../../config/rebase-provenance";
|
|
6
|
+
import { InitialConfigPublicationError } from "../../config/initialize";
|
|
7
|
+
import { ConfigWritePublishedError } from "../../config/persist-unlocked";
|
|
8
|
+
import { commitClaudeCodeBlock } from "../../claude/claude-code-block";
|
|
9
|
+
import { resolveSubagentForceModel, SAFE_AGENT_MODEL_ID } from "../../claude/subagent-model";
|
|
10
|
+
import { inspectSubagentForceStatus } from "../../claude/subagent-force-status";
|
|
11
|
+
import { jsonResponse } from "../auth-cors";
|
|
12
|
+
import { isPlainRecord, fetchInitializedModels as fetchAllModels } from "./shared";
|
|
13
|
+
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
14
|
+
|
|
15
|
+
export async function handleSubagentModelRoutes(ctx: ManagementContext, autoApplyDesktopBestEffort: () => Promise<void>): Promise<Response | null> {
|
|
16
|
+
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
17
|
+
// Featured roster and saved picker order are separate settings. Native Codex advertises
|
|
18
|
+
// the first five eligible visible rows by display priority; OCX guidance uses natural ranks.
|
|
19
|
+
if (url.pathname === "/api/subagent-models" && req.method === "GET") {
|
|
20
|
+
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
21
|
+
// Native gpt (passthrough) are also valid subagent picks — they're picker-visible models in the
|
|
22
|
+
// catalog, just buried by priority. List them first so the user can feature them over routed.
|
|
23
|
+
const chosen = config.subagentModels ?? [];
|
|
24
|
+
// A saved roster slot must stay representable even after its model is disabled
|
|
25
|
+
// elsewhere (Models page, provider allowlist, a provider row going away). The
|
|
26
|
+
// dashboard treats `available` as the set of rows it can render, so a chosen id
|
|
27
|
+
// missing from it disappears from the roster UI and the next Save — which PUTs
|
|
28
|
+
// exactly what the UI holds — silently truncates the persisted list. Losing a
|
|
29
|
+
// deliberate 5-model roster to an unrelated visibility toggle is data loss, not a
|
|
30
|
+
// filter. Same reasoning as `fetchGrokCandidateModels`, which deliberately lists a
|
|
31
|
+
// model the user already excluded so its switch remains reachable.
|
|
32
|
+
const available = subagentSelectableModels(config, models, listCatalogNativeSlugs());
|
|
33
|
+
// #857: let CLI/GUI show when a running Codex app-server keeps an older
|
|
34
|
+
// in-memory catalog than the one on disk. Bounded request-path read: the synchronous
|
|
35
|
+
// collector blocked the event loop for the whole Windows CIM walk (4-7s measured).
|
|
36
|
+
const {
|
|
37
|
+
collectCodexAppServerCatalogStateWithin,
|
|
38
|
+
DASHBOARD_CATALOG_STATE_DEADLINE_MS,
|
|
39
|
+
} = await import("../../codex/app-server-processes");
|
|
40
|
+
const catalogState = await collectCodexAppServerCatalogStateWithin(DASHBOARD_CATALOG_STATE_DEADLINE_MS);
|
|
41
|
+
const forceAvailable = subagentSelectableModels({ ...config, subagentModels: [] }, filterCatalogVisibleModels(models, config), listCatalogNativeSlugs());
|
|
42
|
+
return jsonResponse({
|
|
43
|
+
chosen, available, catalogState,
|
|
44
|
+
force: config.claudeCode?.subagentModelForce ?? null,
|
|
45
|
+
forceAvailable,
|
|
46
|
+
forceStatus: config.claudeCode?.subagentModelForce === undefined ? null : await inspectSubagentForceStatus(
|
|
47
|
+
resolveSubagentForceModel(config, {}, { entries: forceAvailable }) !== null,
|
|
48
|
+
),
|
|
49
|
+
pickerAvailable: [...new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")))],
|
|
50
|
+
pickerOrder: config.modelPickerOrder ?? [],
|
|
51
|
+
pickerOrderMode: config.modelPickerOrderMode ?? null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (url.pathname === "/api/subagent-models" && req.method === "PUT") {
|
|
55
|
+
// Observe before body/discovery awaits: disappearance of an existing config
|
|
56
|
+
// must never turn a scoped update into first-run initialization.
|
|
57
|
+
const initialConfigState = deps.saveConfigPreservingClaudeCode ? undefined : observeInitialConfigState();
|
|
58
|
+
let rawBody: unknown;
|
|
59
|
+
try { rawBody = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
60
|
+
if (!isPlainRecord(rawBody)) return jsonResponse({ error: "JSON body must be an object" }, 400);
|
|
61
|
+
const body = rawBody as { models?: unknown; pickerOrder?: unknown; pickerOrderMode?: unknown; force?: unknown };
|
|
62
|
+
const updatesForce = Object.hasOwn(body, "force");
|
|
63
|
+
const updatesRoster = body.models !== undefined;
|
|
64
|
+
const updatesPicker = body.pickerOrder !== undefined;
|
|
65
|
+
if (!updatesRoster && !updatesPicker && !updatesForce) return jsonResponse({ error: "models, pickerOrder or force is required" }, 400);
|
|
66
|
+
let chosen: string[] | undefined;
|
|
67
|
+
if (updatesRoster) {
|
|
68
|
+
if (!Array.isArray(body.models) || body.models.some(model => typeof model !== "string")) {
|
|
69
|
+
return jsonResponse({ error: "models must be an array of strings" }, 400);
|
|
70
|
+
}
|
|
71
|
+
// Keep the original valid roster contract: no discovery validation, trimming or deduping.
|
|
72
|
+
chosen = body.models.slice(0, 5);
|
|
73
|
+
}
|
|
74
|
+
const mode = body.pickerOrderMode;
|
|
75
|
+
if (mode !== undefined && (!updatesPicker || (mode !== null
|
|
76
|
+
&& mode !== "alphabetical" && mode !== "provider" && mode !== "most-used"))) {
|
|
77
|
+
return jsonResponse({ error: "pickerOrderMode requires pickerOrder and must be alphabetical, provider, most-used, or null" }, 400);
|
|
78
|
+
}
|
|
79
|
+
let pickerOrder: string[] | undefined;
|
|
80
|
+
if (updatesPicker) {
|
|
81
|
+
if (body.pickerOrder !== null && (!Array.isArray(body.pickerOrder)
|
|
82
|
+
|| body.pickerOrder.some(model => typeof model !== "string" || model.trim() === ""))) {
|
|
83
|
+
return jsonResponse({ error: "pickerOrder must be an array of non-empty routed model ids, or null" }, 400);
|
|
84
|
+
}
|
|
85
|
+
pickerOrder = body.pickerOrder === null ? [] : (body.pickerOrder as string[]).map(model => model.trim());
|
|
86
|
+
if (new Set(pickerOrder).size !== pickerOrder.length) {
|
|
87
|
+
return jsonResponse({ error: "pickerOrder must not contain duplicate ids" }, 400);
|
|
88
|
+
}
|
|
89
|
+
if (pickerOrder.length > 0) {
|
|
90
|
+
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
91
|
+
// Evaluate visibility AFTER discovery: a concurrent visibility write may have completed.
|
|
92
|
+
const visible = new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")));
|
|
93
|
+
// A bare native id orders the complete picker (docs: guides/model-ordering.md).
|
|
94
|
+
for (const row of nativeModelRows(config)) if (!row.disabled) visible.add(row.slug);
|
|
95
|
+
if (pickerOrder.some(model => !visible.has(model))) {
|
|
96
|
+
return jsonResponse({ error: "pickerOrder must contain visible routed or native model ids, each at most once" }, 400);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (updatesForce && body.force !== null) {
|
|
102
|
+
if (typeof body.force !== "string" || !SAFE_AGENT_MODEL_ID.test(body.force)) {
|
|
103
|
+
return jsonResponse({ error: "force must be a safe exposed model id or null" }, 400);
|
|
104
|
+
}
|
|
105
|
+
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
106
|
+
const exposed = subagentSelectableModels({ ...config, subagentModels: [] }, filterCatalogVisibleModels(models, config), listCatalogNativeSlugs());
|
|
107
|
+
if (!resolveSubagentForceModel({ ...config, claudeCode: { ...config.claudeCode, subagentModelForce: body.force } }, {}, { entries: exposed })) {
|
|
108
|
+
return jsonResponse({ error: "force must resolve to an exposed model" }, 400);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (updatesForce && initialConfigState === "invalid") {
|
|
113
|
+
return jsonResponse({ error: "force settings could not be persisted" }, 409);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Everything above can await. From this snapshot through persistence there is no yield.
|
|
117
|
+
// Stage deletion intent before adopting the touched fields through the canonical
|
|
118
|
+
// live deletion owner. A failed save restores both fields and pending intent.
|
|
119
|
+
if ((updatesPicker || updatesForce) && config.configRebaseProvenance !== undefined
|
|
120
|
+
&& parsedConfigRebaseDeletionKeys(config) === null) {
|
|
121
|
+
// A newer provenance format must not silently discard this clear's intent on rebase.
|
|
122
|
+
return jsonResponse({ error: "unsupported config deletion provenance" }, 409);
|
|
123
|
+
}
|
|
124
|
+
const draft = { ...projectConfigRebaseProvenance(config) };
|
|
125
|
+
if (chosen !== undefined) draft.subagentModels = chosen;
|
|
126
|
+
if (updatesForce) {
|
|
127
|
+
commitClaudeCodeBlock(draft, { ...config.claudeCode });
|
|
128
|
+
if (body.force === null) deleteConfigObjectChildKey(draft, "claudeCode", "subagentModelForce");
|
|
129
|
+
else draft.claudeCode!.subagentModelForce = body.force as string;
|
|
130
|
+
}
|
|
131
|
+
if (pickerOrder !== undefined) {
|
|
132
|
+
if (pickerOrder.length === 0) {
|
|
133
|
+
deleteConfigTopLevelKey(draft, "modelPickerOrder");
|
|
134
|
+
deleteConfigTopLevelKey(draft, "modelPickerOrderMode");
|
|
135
|
+
} else {
|
|
136
|
+
draft.modelPickerOrder = pickerOrder;
|
|
137
|
+
if (mode === "alphabetical" || mode === "provider" || mode === "most-used") draft.modelPickerOrderMode = mode;
|
|
138
|
+
else deleteConfigTopLevelKey(draft, "modelPickerOrderMode");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const projected = projectConfigRebaseProvenance(draft);
|
|
142
|
+
const touched = [
|
|
143
|
+
...(updatesRoster ? ["subagentModels" as const] : []),
|
|
144
|
+
...(updatesForce ? ["claudeCode" as const] : []),
|
|
145
|
+
...(updatesPicker ? ["modelPickerOrder" as const, "modelPickerOrderMode" as const] : []),
|
|
146
|
+
"configRebaseProvenance" as const,
|
|
147
|
+
];
|
|
148
|
+
const rollback = captureConfigTopLevelRollback(config, touched);
|
|
149
|
+
try {
|
|
150
|
+
for (const key of touched) {
|
|
151
|
+
if (Object.hasOwn(projected, key)) Object.defineProperty(config, key, {
|
|
152
|
+
value: projected[key], writable: true, enumerable: true, configurable: true,
|
|
153
|
+
});
|
|
154
|
+
else deleteConfigTopLevelKey(config, key);
|
|
155
|
+
}
|
|
156
|
+
if (updatesForce && body.force === null) deleteConfigObjectChildKey(config, "claudeCode", "subagentModelForce");
|
|
157
|
+
if (updatesForce && initialConfigState === "missing") {
|
|
158
|
+
// Create-only publication rechecks under the canonical lock and never
|
|
159
|
+
// replaces a file another writer supplied while discovery was pending.
|
|
160
|
+
if (initializePersistedConfigIfMissing(config) !== "created") {
|
|
161
|
+
rollback();
|
|
162
|
+
return jsonResponse({ error: "force settings could not be persisted" }, 409);
|
|
163
|
+
}
|
|
164
|
+
adoptPersistedClaudeCode(config, structuredClone(config.claudeCode));
|
|
165
|
+
} else if (updatesForce && !deps.saveConfigPreservingClaudeCode) {
|
|
166
|
+
const outcome = mutatePersistedConfig(persisted => {
|
|
167
|
+
commitClaudeCodeBlock(persisted, { ...persisted.claudeCode });
|
|
168
|
+
if (body.force === null) delete persisted.claudeCode!.subagentModelForce;
|
|
169
|
+
else persisted.claudeCode!.subagentModelForce = body.force as string;
|
|
170
|
+
if (updatesRoster) persisted.subagentModels = chosen!;
|
|
171
|
+
if (updatesPicker) {
|
|
172
|
+
if (pickerOrder!.length === 0) {
|
|
173
|
+
deleteConfigTopLevelKey(persisted, "modelPickerOrder");
|
|
174
|
+
deleteConfigTopLevelKey(persisted, "modelPickerOrderMode");
|
|
175
|
+
} else {
|
|
176
|
+
persisted.modelPickerOrder = pickerOrder;
|
|
177
|
+
if (mode === "alphabetical" || mode === "provider" || mode === "most-used") persisted.modelPickerOrderMode = mode;
|
|
178
|
+
else deleteConfigTopLevelKey(persisted, "modelPickerOrderMode");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { changed: true, value: structuredClone(persisted.claudeCode) };
|
|
182
|
+
});
|
|
183
|
+
if (outcome.status === "unavailable") {
|
|
184
|
+
rollback();
|
|
185
|
+
return jsonResponse({ error: "force settings could not be persisted" }, 409);
|
|
186
|
+
}
|
|
187
|
+
adoptPersistedClaudeCode(config, outcome.value);
|
|
188
|
+
} else {
|
|
189
|
+
(deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode)(config);
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
// Once publication succeeded (or cannot be ruled out), restoring only the
|
|
193
|
+
// live object would falsely report the old setting while disk may hold new bytes.
|
|
194
|
+
if (!(error instanceof ConfigWritePublishedError)
|
|
195
|
+
&& !(error instanceof InitialConfigPublicationError && error.publication !== "not-published")) rollback();
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
// Capture the result before convergence yields to another settings mutation.
|
|
199
|
+
const saved = {
|
|
200
|
+
applied: [...(config.subagentModels ?? [])],
|
|
201
|
+
force: config.claudeCode?.subagentModelForce ?? null,
|
|
202
|
+
pickerOrder: [...(config.modelPickerOrder ?? [])],
|
|
203
|
+
pickerOrderMode: config.modelPickerOrderMode ?? null,
|
|
204
|
+
};
|
|
205
|
+
const catalogRefresh = updatesRoster || updatesPicker ? await convergeCodexCatalog() : undefined;
|
|
206
|
+
if (updatesRoster) {
|
|
207
|
+
await syncClaudeAgentDefsBestEffort();
|
|
208
|
+
await autoApplyDesktopBestEffort();
|
|
209
|
+
}
|
|
210
|
+
return jsonResponse({ ok: true, ...saved, catalogRefresh });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { anthropicBodyElidesBlockedSkill } from "../claude/inbound";
|
|
9
9
|
import { isClaudeWebSearchToolName } from "../claude/outbound";
|
|
10
|
-
import { isAnthropicAccountPoolEnabled } from "../oauth/anthropic-routing";
|
|
11
10
|
import type { ProtocolReasonCode } from "../protocols/contract";
|
|
12
11
|
import { featuresFromMessagesBody } from "../protocols/features";
|
|
13
12
|
import { credentialDomainFor } from "../protocols/opaque-state";
|
|
@@ -96,8 +95,7 @@ function bridgeOnlyPolicyApplies(
|
|
|
96
95
|
* The credential rule. A proxy-managed key is native since PF-08. An Anthropic OAuth account
|
|
97
96
|
* is native only with `managedMessagesNativeOAuth` on (itself effective only with
|
|
98
97
|
* `managedMessagesNative`), only for the `anthropic` provider the OAuth store serves, and only
|
|
99
|
-
* to `api.anthropic.com`.
|
|
100
|
-
* ranking live in the Responses pipeline's transport and are not replicated here. `forward`
|
|
98
|
+
* to `api.anthropic.com`. Pool selection and recovery are resolved at native dispatch. `forward`
|
|
101
99
|
* belongs to the caller.
|
|
102
100
|
*/
|
|
103
101
|
function credentialDeclineReason(
|
|
@@ -108,10 +106,9 @@ function credentialDeclineReason(
|
|
|
108
106
|
const provider = route.provider;
|
|
109
107
|
if (provider.authMode === undefined || provider.authMode === "key") return undefined;
|
|
110
108
|
if (provider.authMode !== "oauth") return "auth-mode-not-native";
|
|
111
|
-
if (!resolveProtocolSettings(config).rollout.managedMessagesNativeOAuth) return "auth-mode-not-native";
|
|
109
|
+
if (!resolveProtocolSettings(config, route.providerName).rollout.managedMessagesNativeOAuth) return "auth-mode-not-native";
|
|
112
110
|
if (route.providerName !== "anthropic") return "auth-mode-not-native";
|
|
113
111
|
if (!credentialDomainFor(provider)?.firstPartyAnthropic) return "auth-mode-not-native";
|
|
114
|
-
if (isAnthropicAccountPoolEnabled(config) || selector.oauthFailoverQuorum === true) return "oauth-account-pool";
|
|
115
112
|
return undefined;
|
|
116
113
|
}
|
|
117
114
|
|
|
@@ -135,7 +132,7 @@ export function nativeMessagesDeclineReason(
|
|
|
135
132
|
config: OcxConfig,
|
|
136
133
|
selector: NativeMessagesSelector = {},
|
|
137
134
|
): NativeMessagesDeclineReason | undefined {
|
|
138
|
-
if (!resolveProtocolSettings(config).rollout.managedMessagesNative) return "rollout-disabled";
|
|
135
|
+
if (!resolveProtocolSettings(config, route.providerName).rollout.managedMessagesNative) return "rollout-disabled";
|
|
139
136
|
const provider = route.provider;
|
|
140
137
|
if (provider.adapter !== "anthropic") return "cross-wire-ir";
|
|
141
138
|
const credentialDecline = credentialDeclineReason(route, config, selector);
|