@bitkyc08/opencodex 2.86.0 → 2.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-DyR9yBee.js → Tray-DCd-A1az.js} +1 -1
- package/gui/dist/assets/index-CfYDMj34.js +94 -0
- package/gui/dist/assets/index-DPeB9HpV.css +1 -0
- package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +9 -8
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/passthrough.ts +7 -23
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/base.ts +2 -0
- package/src/adapters/claude-cli/adapter.ts +111 -23
- package/src/adapters/claude-cli/profiles.ts +2 -1
- package/src/adapters/claude-cli/stable-replay.ts +108 -0
- package/src/adapters/claude-cli/usage-admission.ts +230 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +18 -2
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google.ts +35 -8
- package/src/adapters/openai-chat/messages.ts +4 -3
- package/src/adapters/openai-chat/passthrough.ts +8 -2
- package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
- package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
- package/src/adapters/openai-responses/passthrough.ts +57 -125
- package/src/adapters/openai-responses/request-strips.ts +25 -3
- package/src/adapters/openai-responses/transport.ts +81 -0
- package/src/adapters/qoder/adapter.ts +4 -2
- package/src/adapters/registry.ts +5 -0
- package/src/adapters/zed.ts +26 -1
- package/src/advisor/activity.ts +55 -0
- package/src/advisor/budget.ts +62 -0
- package/src/advisor/consult.ts +4 -1
- package/src/advisor/context.ts +16 -4
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +79 -31
- package/src/advisor/settings.ts +11 -2
- package/src/bridge/errors.ts +19 -1
- package/src/bridge/internal.ts +3 -1
- package/src/bridge/response-json.ts +18 -3
- package/src/bridge/sse.ts +58 -8
- package/src/bridge/terminal-refusal.ts +59 -0
- package/src/claude/desktop-3p-library.ts +32 -7
- package/src/claude/desktop-3p.ts +55 -13
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +33 -0
- package/src/claude/intercept/picker-ca-startup.ts +2 -1
- package/src/claude/intercept/picker-runtime.ts +6 -5
- package/src/claude/intercept/picker-trust-windows.ts +338 -0
- package/src/claude/intercept/picker-trust.ts +19 -0
- package/src/claude/intercept/port-policy.ts +30 -0
- package/src/claude/intercept/runtime.ts +97 -94
- package/src/claude/outbound.ts +38 -13
- package/src/cli/account-api.ts +1 -0
- package/src/cli/account-policy-dto.ts +6 -0
- package/src/cli/account-policy.ts +34 -5
- package/src/cli/account-preservation.ts +44 -0
- package/src/cli/account.ts +8 -2
- package/src/cli/advisor.ts +17 -3
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +5 -2
- package/src/cli/capabilities-base.ts +38 -10
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/codex-cli-update.ts +151 -41
- package/src/cli/codex-provider.ts +44 -0
- package/src/cli/combo-input.ts +10 -1
- package/src/cli/combo-result.ts +1 -1
- package/src/cli/combo.ts +2 -1
- package/src/cli/cross-home-owner.ts +17 -14
- package/src/cli/dispatch.ts +25 -4
- package/src/cli/doctor.ts +6 -1
- package/src/cli/ensure-desktop-cold-start.ts +60 -7
- package/src/cli/help.ts +3 -2
- package/src/cli/index.ts +1 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/log-history.ts +172 -0
- package/src/cli/models-runtime-subcommands.ts +1 -0
- package/src/cli/models-runtime.ts +72 -8
- package/src/cli/observe-stream.ts +2 -0
- package/src/cli/observe.ts +16 -4
- package/src/cli/provider.ts +27 -5
- package/src/cli/registry.ts +28 -8
- package/src/cli/runtime-api.ts +5 -3
- package/src/cli/system-command.ts +40 -8
- package/src/cli/uninstall-client-state.ts +32 -0
- package/src/cli/usage-compare.ts +309 -0
- package/src/cli/usage-model-search.ts +4 -4
- package/src/cli/usage-report.ts +2 -2
- package/src/cli/v2.ts +1 -1
- package/src/client/machine-listener.ts +1 -3
- package/src/client/runtime.ts +1 -0
- package/src/clients/client-path.ts +51 -0
- package/src/clients/config-export/contracts.ts +2 -1
- package/src/clients/config-export.ts +18 -44
- package/src/clients/gentle-shell-path.ts +87 -0
- package/src/codex/account-auto-switch.ts +2 -0
- package/src/codex/account-identity.ts +29 -0
- package/src/codex/account-label.ts +1 -1
- package/src/codex/account-lifecycle.ts +21 -5
- package/src/codex/account-preservation.ts +28 -0
- package/src/codex/account-use-remaining.ts +23 -0
- package/src/codex/app-server-processes.ts +97 -7
- package/src/codex/app-server-restart-service.ts +55 -4
- package/src/codex/auth-api/account-list.ts +4 -0
- package/src/codex/auth-api/account-pause-group.ts +6 -50
- package/src/codex/auth-api/account-preservation.ts +57 -0
- package/src/codex/auth-api/routes.ts +8 -0
- package/src/codex/auth-api/use-remaining-route.ts +36 -0
- package/src/codex/auth-api.ts +0 -1
- package/src/codex/auth-context.ts +2 -2
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +9 -1
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +1 -1
- package/src/codex/catalog/gather-capture.ts +2 -0
- package/src/codex/catalog/metadata.ts +11 -9
- package/src/codex/catalog/model-hints.ts +21 -16
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/parsing.ts +30 -86
- package/src/codex/catalog/provider-models.ts +36 -34
- package/src/codex/catalog/routed-gather.ts +12 -13
- package/src/codex/cli-install-provenance.ts +105 -17
- package/src/codex/cli-update-plan.ts +545 -0
- package/src/codex/config-write-lock.ts +23 -2
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app-restart.ts +21 -0
- package/src/codex/desktop-compatibility/installed-build.ts +24 -6
- package/src/codex/desktop-compatibility/routing-preflight.ts +1 -1
- package/src/codex/desktop-compatibility/runtime.ts +12 -3
- package/src/codex/desktop-compatibility/windows-key-protection.ts +115 -9
- package/src/codex/features.ts +85 -45
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/restore.ts +24 -2
- package/src/codex/inject/routing-classify.ts +4 -3
- package/src/codex/inject.ts +18 -27
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/native-profile-startup.ts +1 -1
- package/src/codex/ocx-compaction-history.ts +12 -10
- package/src/codex/paths.ts +86 -1
- package/src/codex/provider-routing.ts +174 -0
- package/src/codex/refresh.ts +2 -2
- package/src/codex/routing/health-store.ts +1 -1
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/signed-in-identity.ts +43 -0
- package/src/codex/subagent-model-fallback.ts +1 -1
- package/src/codex/sync.ts +6 -1
- package/src/codex/user-identity.ts +16 -5
- package/src/codex/windows-local-appdata.ts +84 -0
- package/src/combos/failover.ts +6 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/resolve.ts +46 -10
- package/src/combos/types.ts +15 -0
- package/src/config/diagnostics.ts +33 -6
- package/src/config/live-reconcile.ts +38 -2
- package/src/config/load-degrade.ts +17 -0
- package/src/config/opencode-wire-upgrade.ts +54 -0
- package/src/config/opencode-zen-upgrade.ts +52 -0
- package/src/config/owner-registry.ts +22 -17
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +22 -0
- package/src/config/schema/leaf-validators.ts +38 -9
- package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
- package/src/config/schema/native-reasoning-retention.ts +26 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +608 -284
- package/src/grok/sync.ts +1 -1
- package/src/images/loop.ts +46 -8
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/mutation-plan.ts +1 -0
- package/src/integrations/registry.ts +8 -0
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-upstream-tunnel.ts +18 -53
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +19 -3
- package/src/lib/gcp-adc.ts +96 -9
- package/src/lib/gcp-credential-input.ts +12 -0
- package/src/lib/lab-live-route-production.ts +5 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/redact.ts +2 -2
- package/src/oauth/account-quota-rank.ts +69 -2
- package/src/oauth/anthropic-account-refusal.ts +20 -2
- package/src/oauth/anthropic-oauth-definitions.ts +3 -1
- package/src/oauth/anthropic-routing.ts +1 -21
- package/src/oauth/anthropic.ts +38 -7
- package/src/oauth/callback-server.ts +27 -1
- package/src/oauth/chatgpt-device.ts +1 -1
- package/src/oauth/chatgpt-shared.ts +83 -0
- package/src/oauth/chatgpt.ts +8 -72
- package/src/oauth/dsh.ts +203 -0
- package/src/oauth/generic-account-failover.ts +33 -7
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +184 -109
- package/src/oauth/local-token-detect.ts +21 -1
- package/src/oauth/login-cli-errors.ts +44 -0
- package/src/oauth/login-cli.ts +69 -7
- package/src/oauth/login-flow-state.ts +371 -23
- package/src/oauth/meta-muse-errors.ts +41 -0
- package/src/oauth/meta-muse-keychain.ts +52 -0
- package/src/oauth/meta-muse.ts +51 -84
- package/src/oauth/model-discovery-auth.ts +3 -0
- package/src/oauth/pool-settings-capability.ts +4 -2
- package/src/oauth/refresh-flight-retirement.ts +48 -0
- package/src/oauth/session-affinity-key.ts +23 -0
- package/src/oauth/store-anthropic-instance.ts +9 -2
- package/src/oauth/store.ts +12 -5
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/provider-summary.ts +2 -1
- package/src/providers/api-key-cooldowns.ts +49 -0
- package/src/providers/api-key-resolve.ts +11 -6
- package/src/providers/api-key-response-ownership.ts +57 -0
- package/src/providers/api-key-selection.ts +31 -6
- package/src/providers/api-keys.ts +214 -3
- package/src/providers/claude-cli-usage.ts +96 -0
- package/src/providers/dsh-account-destination.ts +31 -0
- package/src/providers/dsh-account-models.ts +41 -0
- package/src/providers/gcp-credential-cleanup.ts +59 -0
- package/src/providers/key-failover.ts +54 -50
- package/src/providers/key-store.ts +7 -2
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/model-rename-migration.ts +2 -2
- package/src/providers/openai-tiers-destination.ts +11 -2
- package/src/providers/openai-tiers.ts +5 -12
- package/src/providers/opencode-go-transport.ts +3 -1
- package/src/providers/quota/account-cache.ts +6 -5
- package/src/providers/quota/account-row.ts +34 -0
- package/src/providers/quota/antigravity.ts +28 -7
- package/src/providers/quota/dsh-account.ts +70 -0
- package/src/providers/quota/vendor-probes-oauth.ts +1 -93
- package/src/providers/quota-types.ts +2 -0
- package/src/providers/quota.ts +14 -20
- package/src/providers/registry/entries-core.ts +37 -8
- package/src/providers/registry/types.ts +1 -1
- package/src/providers/request-pacing.ts +77 -19
- package/src/providers/routed-config.ts +339 -0
- package/src/providers/service-tier.ts +2 -1
- package/src/quota/window-mapping.ts +5 -2
- package/src/responses/anthropic-progress.ts +34 -0
- package/src/responses/assistant-replay.ts +35 -0
- package/src/responses/compaction-history-framing.ts +65 -0
- package/src/responses/compaction.ts +6 -1
- package/src/responses/incomplete-reason.ts +31 -0
- package/src/responses/parser.ts +50 -5
- package/src/responses/reasoning-envelope.ts +4 -0
- package/src/responses/reasoning-replay-cache.ts +24 -2
- package/src/responses/reasoning-retention.ts +173 -0
- package/src/responses/retained-compaction.ts +46 -0
- package/src/responses/schema.ts +23 -0
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/indexer.ts +6 -5
- package/src/routing/history/ingest.ts +6 -2
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +17 -0
- package/src/server/auth-cors.ts +11 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native-reasoning.ts +123 -0
- package/src/server/chat-native-sse.ts +110 -7
- package/src/server/chat-native.ts +76 -17
- package/src/server/claude-messages.ts +55 -414
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/model-alias-id.ts +36 -0
- package/src/server/index/optional-listeners.ts +2 -0
- package/src/server/index/serve-options.ts +9 -1
- package/src/server/index/startup-warnings.ts +2 -0
- package/src/server/index.ts +1 -1
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/management/advisor-routes.ts +15 -4
- package/src/server/management/claude-desktop-picker-routes.ts +1 -1
- package/src/server/management/combo-routes.ts +176 -50
- package/src/server/management/companion-routes.ts +1 -1
- package/src/server/management/config-routes.ts +75 -4
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/model-routes.ts +11 -6
- package/src/server/management/model-rows.ts +14 -0
- package/src/server/management/native-integration-routes.ts +2 -7
- package/src/server/management/native-reasoning-retention-routes.ts +41 -0
- package/src/server/management/oauth-account-routes.ts +87 -14
- package/src/server/management/provider-gcp-credential-routes.ts +29 -0
- package/src/server/management/provider-key-pacing-routes.ts +31 -0
- package/src/server/management/provider-overwrite-carry.ts +7 -0
- package/src/server/management/provider-routes.ts +25 -30
- package/src/server/management/route-registry.ts +10 -1
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +101 -21
- package/src/server/messages-shared.ts +431 -0
- package/src/server/relay.ts +1 -1
- package/src/server/request-log-terminal-status.ts +1 -1
- package/src/server/request-log.ts +6 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +13 -1
- package/src/server/responses/adapter-delivery.ts +19 -6
- package/src/server/responses/adapter-dispatch.ts +19 -2
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +73 -41
- package/src/server/responses/codex-ws-wire.ts +15 -8
- package/src/server/responses/compact.ts +303 -125
- package/src/server/responses/compaction-recovery.ts +18 -10
- package/src/server/responses/compaction-retention.ts +61 -0
- package/src/server/responses/context-overflow.ts +11 -6
- package/src/server/responses/core-auth.ts +6 -0
- package/src/server/responses/core-codex-account.ts +11 -1
- package/src/server/responses/core-combo-failure.ts +2 -0
- package/src/server/responses/core-combo.ts +120 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +5 -2
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +18 -2
- package/src/server/responses/core.ts +2 -115
- package/src/server/responses/fetch-helpers.ts +73 -19
- package/src/server/responses/main-hard-lock-routing.ts +109 -0
- package/src/server/responses/native-response-control.ts +4 -2
- package/src/server/responses/passthrough-delivery.ts +3 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -13
- package/src/server/responses/request-prepare.ts +76 -12
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +104 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +96 -30
- package/src/server/responses/sidecar-execution.ts +20 -2
- package/src/server/responses/terminal-guard.ts +2 -16
- package/src/service/auth-preflight.ts +71 -0
- package/src/service/cli.ts +4 -2
- package/src/service/definition.ts +97 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +76 -57
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +72 -9
- package/src/service/repair.ts +2 -1
- package/src/service/state.ts +7 -8
- package/src/service/systemd.ts +1 -1
- package/src/service/windows-guarded-manager.ts +2 -1
- package/src/service/windows-ops.ts +1 -1
- package/src/service/windows-scheduler.ts +2 -7
- package/src/service/windows-taskxml.ts +2 -2
- package/src/service/windows-wscript.ts +14 -0
- package/src/service.ts +4 -2
- package/src/sidecar/auth.ts +2 -0
- package/src/types/config.ts +64 -3
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +32 -6
- package/src/types/request.ts +23 -2
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/npm-invocation.mjs +12 -2
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +3 -0
- package/src/usage/ledger-retention.ts +2 -1
- package/src/usage/ledger-scanner.ts +6 -5
- package/src/usage/log.ts +17 -8
- package/src/usage/summary.ts +22 -9
- package/src/usage/user-cost-overlays.ts +14 -19
- package/src/usage/user-cost-validation.ts +38 -0
- package/src/vision/eligibility.ts +13 -0
- package/src/web-search/loop.ts +55 -5
- package/src/web-search/progress-stream.ts +4 -1
- package/src/web-search/run-turn-loop.ts +9 -1
- package/gui/dist/assets/App-BjlttVLy.js +0 -58
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-BMssP4k3.js +0 -94
- package/gui/dist/assets/quota-summary-D8M1bc_R.js +0 -1
package/src/cli/help.ts
CHANGED
|
@@ -85,14 +85,14 @@ Usage:
|
|
|
85
85
|
ocx inspect <sub> Effective config, catalog, analytics, pacing, client-config
|
|
86
86
|
ocx route <sub> Routing features (combo, policy)
|
|
87
87
|
ocx logs [filters] Alias of ocx observe logs
|
|
88
|
-
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>]
|
|
88
|
+
ocx usage [--range <today|1d|7d|30d|all>] [--provider <name>] [--model <id>] [--top <1-1000>]
|
|
89
89
|
Token and estimated-cost report (alias of ocx observe usage)
|
|
90
90
|
ocx storage <sub> Storage report, cleanup, trash, and the cleanup policy
|
|
91
91
|
ocx memory [--json] Alias of ocx observe memory
|
|
92
92
|
ocx api-key <sub> Alias of ocx access key
|
|
93
93
|
ocx access <sub> External API keys and endpoint information
|
|
94
94
|
ocx api <sub> Protocol paths: vocabulary, request-path preview, and policy
|
|
95
|
-
ocx export --client <id> Print a client config wired to the running proxy (
|
|
95
|
+
ocx export --client <id> Print a client config wired to the running proxy (20 clients)
|
|
96
96
|
ocx integration client <sub> Enable, disable, inspect or roll back a client integration
|
|
97
97
|
ocx grok <sub> Grok Build model selection and apply
|
|
98
98
|
ocx system <sub> Runtime settings, startup, sync, OpenCodex updates, and Codex CLI inspection
|
|
@@ -100,6 +100,7 @@ Usage:
|
|
|
100
100
|
ocx advisor <status|on|off|set|consent> Advisor sidecar settings and context-sharing consent
|
|
101
101
|
ocx companion <show|set|reset> Menu-bar and widget companion usage settings
|
|
102
102
|
ocx lab <sub> Inspect Lab evidence and control local automation
|
|
103
|
+
ocx codex-provider <sub> Confirm custom Codex provider routing: status|allow|revoke
|
|
103
104
|
ocx chatgpt <sub> Experimental app-server shim: launch|restore|status (macOS)
|
|
104
105
|
ocx claude [args...] Launch Claude Code wired to the proxy (model discovery on)
|
|
105
106
|
ocx claude desktop [sub] Manage and apply Claude Desktop's four-family profile
|
package/src/cli/index.ts
CHANGED
|
@@ -660,7 +660,7 @@ async function handleStart(options: { block?: boolean } = {}) {
|
|
|
660
660
|
}
|
|
661
661
|
removePid(process.pid);
|
|
662
662
|
removeRuntimePort(process.pid);
|
|
663
|
-
if (teardown.restoreNativeCodex
|
|
663
|
+
if (teardown.restoreNativeCodex) {
|
|
664
664
|
try {
|
|
665
665
|
if (!codexHomeIsAbsent(getCodexHome())) {
|
|
666
666
|
const restored = restoreNativeCodex();
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indexed request-history reads: page the derived ledger, or fetch one canonical row.
|
|
3
|
+
*
|
|
4
|
+
* Distinct from `ocx logs` (the recent ring) and `ocx logs explain` (route-decision).
|
|
5
|
+
*/
|
|
6
|
+
import { REQUEST_HISTORY_DEFAULT_PAGE_SIZE, REQUEST_HISTORY_MAX_PAGE_SIZE } from "../routing/history/indexer";
|
|
7
|
+
import { parseUsageTimeWindow } from "../usage/time-range";
|
|
8
|
+
import {
|
|
9
|
+
CliUsageError,
|
|
10
|
+
printData,
|
|
11
|
+
rejectArgs,
|
|
12
|
+
runtimeRequest,
|
|
13
|
+
takeFlag,
|
|
14
|
+
takeIntegerOption,
|
|
15
|
+
takeOption,
|
|
16
|
+
type RuntimeApiDeps,
|
|
17
|
+
} from "./runtime-api";
|
|
18
|
+
|
|
19
|
+
type LogRow = Record<string, unknown>;
|
|
20
|
+
|
|
21
|
+
export const LOG_HISTORY_USAGE = `Usage:
|
|
22
|
+
ocx logs history [--provider <name>] [--model <id>] [--requested-model <id>]
|
|
23
|
+
[--status <code>] [--conversation <id>] [--surface <name>] [--protocol <responses|chat|messages>]
|
|
24
|
+
[--api-key-id <id>] [--profile <id>] [--fallback <true|false>]
|
|
25
|
+
[--from <epoch-ms|ISO-datetime>] [--to <epoch-ms|ISO-datetime>]
|
|
26
|
+
[--cursor <token>] [--limit <1-${REQUEST_HISTORY_MAX_PAGE_SIZE}>] [--json|--jsonl]
|
|
27
|
+
ocx logs get <request-id> [--json]`;
|
|
28
|
+
|
|
29
|
+
const GET_USAGE = "Usage: ocx logs get <request-id> [--json]";
|
|
30
|
+
const PROTOCOLS = ["responses", "chat", "messages"] as const;
|
|
31
|
+
|
|
32
|
+
function query(params: Record<string, string | number | boolean | undefined>): string {
|
|
33
|
+
const search = new URLSearchParams();
|
|
34
|
+
for (const [key, value] of Object.entries(params)) {
|
|
35
|
+
if (value !== undefined) search.set(key, String(value));
|
|
36
|
+
}
|
|
37
|
+
const encoded = search.toString();
|
|
38
|
+
return encoded ? `?${encoded}` : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function timestampOption(args: string[], flag: string): number | undefined {
|
|
42
|
+
const raw = takeOption(args, flag);
|
|
43
|
+
if (raw === undefined) return undefined;
|
|
44
|
+
try {
|
|
45
|
+
return parseUsageTimeWindow(raw, raw)!.since;
|
|
46
|
+
} catch (error) {
|
|
47
|
+
throw new CliUsageError(error instanceof Error ? `${flag}: ${error.message}` : `${flag} is invalid`, LOG_HISTORY_USAGE);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function record(value: unknown): value is Record<string, unknown> {
|
|
52
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatCost(row: Record<string, unknown>): string {
|
|
56
|
+
const metrics = record(row.displayMetrics) ? row.displayMetrics : undefined;
|
|
57
|
+
const cost = metrics && record(metrics.cost) ? metrics.cost : undefined;
|
|
58
|
+
if (!cost || cost.kind !== "value" || !record(cost.estimate) || !record(cost.estimate.cost)) return "";
|
|
59
|
+
const total = cost.estimate.cost.total;
|
|
60
|
+
return typeof total === "number" && Number.isFinite(total) ? `~$${total.toFixed(4)}` : "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatHistoryRow(row: LogRow, formatRow: (row: LogRow) => string): string {
|
|
64
|
+
const tokens = typeof row.totalTokens === "number" && Number.isFinite(row.totalTokens)
|
|
65
|
+
? `${row.totalTokens}tok`
|
|
66
|
+
: "";
|
|
67
|
+
return [formatRow(row), tokens, formatCost(row)].filter(Boolean).join(" ");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function historyPage(raw: unknown): {
|
|
71
|
+
entries: Record<string, unknown>[];
|
|
72
|
+
nextCursor?: string;
|
|
73
|
+
hasMore: boolean;
|
|
74
|
+
index?: Record<string, unknown>;
|
|
75
|
+
} {
|
|
76
|
+
if (!record(raw) || !Array.isArray(raw.entries) || typeof raw.hasMore !== "boolean") {
|
|
77
|
+
throw new Error("Invalid request-history page. Upgrade the proxy and retry.");
|
|
78
|
+
}
|
|
79
|
+
const entries = raw.entries;
|
|
80
|
+
if (!entries.every(record)) throw new Error("Invalid request-history page. Upgrade the proxy and retry.");
|
|
81
|
+
const nextCursor = typeof raw.nextCursor === "string" && raw.nextCursor.length > 0 ? raw.nextCursor : undefined;
|
|
82
|
+
const index = record(raw.index) ? raw.index : undefined;
|
|
83
|
+
return { entries, nextCursor, hasMore: raw.hasMore, index };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Page the derived request-history index through GET /api/request-history. */
|
|
87
|
+
export async function handleLogHistoryCommand(
|
|
88
|
+
argv: string[],
|
|
89
|
+
deps: RuntimeApiDeps,
|
|
90
|
+
formatRow: (row: LogRow) => string,
|
|
91
|
+
): Promise<void> {
|
|
92
|
+
const args = [...argv];
|
|
93
|
+
const wantsJson = takeFlag(args, "--json");
|
|
94
|
+
const wantsJsonl = takeFlag(args, "--jsonl");
|
|
95
|
+
const provider = takeOption(args, "--provider");
|
|
96
|
+
const model = takeOption(args, "--model");
|
|
97
|
+
const requestedModel = takeOption(args, "--requested-model");
|
|
98
|
+
const conversationId = takeOption(args, "--conversation") ?? takeOption(args, "--conversationId");
|
|
99
|
+
const surface = takeOption(args, "--surface");
|
|
100
|
+
const protocol = takeOption(args, "--protocol");
|
|
101
|
+
const apiKeyId = takeOption(args, "--api-key-id");
|
|
102
|
+
const profileId = takeOption(args, "--profile");
|
|
103
|
+
const fallbackRaw = takeOption(args, "--fallback");
|
|
104
|
+
const cursor = takeOption(args, "--cursor");
|
|
105
|
+
const status = takeIntegerOption(args, "--status", { min: 100 });
|
|
106
|
+
const limit = takeIntegerOption(args, "--limit", { min: 1 }) ?? REQUEST_HISTORY_DEFAULT_PAGE_SIZE;
|
|
107
|
+
const from = timestampOption(args, "--from");
|
|
108
|
+
const to = timestampOption(args, "--to");
|
|
109
|
+
rejectArgs(args, LOG_HISTORY_USAGE);
|
|
110
|
+
if (wantsJson && wantsJsonl) throw new CliUsageError("--json and --jsonl cannot be combined", LOG_HISTORY_USAGE);
|
|
111
|
+
if (status !== undefined && status > 599) throw new CliUsageError("--status must be an integer from 100 to 599", LOG_HISTORY_USAGE);
|
|
112
|
+
if (limit > REQUEST_HISTORY_MAX_PAGE_SIZE) {
|
|
113
|
+
throw new CliUsageError(`--limit must be between 1 and ${REQUEST_HISTORY_MAX_PAGE_SIZE}`, LOG_HISTORY_USAGE);
|
|
114
|
+
}
|
|
115
|
+
if (protocol !== undefined && !PROTOCOLS.includes(protocol as (typeof PROTOCOLS)[number])) {
|
|
116
|
+
throw new CliUsageError(`--protocol must be one of ${PROTOCOLS.join(", ")}`, LOG_HISTORY_USAGE);
|
|
117
|
+
}
|
|
118
|
+
let fallback: boolean | undefined;
|
|
119
|
+
if (fallbackRaw !== undefined) {
|
|
120
|
+
if (fallbackRaw !== "true" && fallbackRaw !== "false") {
|
|
121
|
+
throw new CliUsageError("--fallback must be true or false", LOG_HISTORY_USAGE);
|
|
122
|
+
}
|
|
123
|
+
fallback = fallbackRaw === "true";
|
|
124
|
+
}
|
|
125
|
+
if (from !== undefined && to !== undefined && from > to) {
|
|
126
|
+
throw new CliUsageError("--from must not be after --to", LOG_HISTORY_USAGE);
|
|
127
|
+
}
|
|
128
|
+
const data = await runtimeRequest(`/api/request-history${query({
|
|
129
|
+
provider, model, requestedModel, status, conversationId, surface,
|
|
130
|
+
inboundProtocol: protocol, apiKeyId, profileId, fallback, from, to, cursor, limit,
|
|
131
|
+
})}`, {}, deps);
|
|
132
|
+
const page = historyPage(data);
|
|
133
|
+
if (wantsJson) {
|
|
134
|
+
printData(data, true);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (wantsJsonl) {
|
|
138
|
+
for (const row of page.entries) console.log(JSON.stringify(row));
|
|
139
|
+
if (page.hasMore && page.nextCursor) console.error(`nextCursor=${page.nextCursor}`);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const indexed = typeof page.index?.indexedRows === "number" ? page.index.indexedRows : undefined;
|
|
143
|
+
if (page.entries.length === 0) {
|
|
144
|
+
printData(data, false, [
|
|
145
|
+
"No request-history row matched the filters.",
|
|
146
|
+
...(indexed !== undefined ? [`indexedRows=${indexed}`] : []),
|
|
147
|
+
]);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const footer = [
|
|
151
|
+
...(indexed !== undefined ? [`indexedRows=${indexed}`] : []),
|
|
152
|
+
`hasMore=${page.hasMore}`,
|
|
153
|
+
...(page.nextCursor ? [`nextCursor=${page.nextCursor}`] : []),
|
|
154
|
+
];
|
|
155
|
+
printData(data, false, [...page.entries.map(row => formatHistoryRow(row, formatRow)), ...footer]);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Read one canonical request-history row through GET /api/request-history/{id}. */
|
|
159
|
+
export async function handleLogGetCommand(
|
|
160
|
+
argv: string[],
|
|
161
|
+
deps: RuntimeApiDeps,
|
|
162
|
+
formatRow: (row: LogRow) => string,
|
|
163
|
+
): Promise<void> {
|
|
164
|
+
const args = [...argv];
|
|
165
|
+
const requestId = args.shift();
|
|
166
|
+
const wantsJson = takeFlag(args, "--json");
|
|
167
|
+
if (!requestId) throw new CliUsageError("request id is required", GET_USAGE);
|
|
168
|
+
rejectArgs(args, GET_USAGE);
|
|
169
|
+
const result = await runtimeRequest(`/api/request-history/${encodeURIComponent(requestId)}`, {}, deps);
|
|
170
|
+
if (!record(result)) throw new Error("Invalid request-history row. Upgrade the proxy and retry.");
|
|
171
|
+
printData(result, wantsJson, wantsJson ? undefined : [formatHistoryRow(result, formatRow)]);
|
|
172
|
+
}
|
|
@@ -21,7 +21,7 @@ import { redactSecretString } from "../lib/redact";
|
|
|
21
21
|
import type { ProviderCostOverlay } from "../types";
|
|
22
22
|
import { resolveMatchedPrice } from "../usage/cost";
|
|
23
23
|
import { MAX_COST4_RATE } from "../usage/expected-prices";
|
|
24
|
-
import { isValidCost4Rate } from "../usage/user-cost-overlays";
|
|
24
|
+
import { isValidCost4Rate, validCost4 } from "../usage/user-cost-overlays";
|
|
25
25
|
|
|
26
26
|
const USAGE = `Usage:
|
|
27
27
|
ocx models display-name <provider/raw-model> (--set <text> | --clear) [--json]
|
|
@@ -48,6 +48,7 @@ const USAGE = `Usage:
|
|
|
48
48
|
ocx models new-arrivals [--json]
|
|
49
49
|
${MODELS_CONTEXT_USAGE}
|
|
50
50
|
ocx models shadow <status|set> [model|-] [--enabled <on|off>] [--json]
|
|
51
|
+
ocx models auto-review <status|set> [model|-] [--enabled <on|off>] [--json]
|
|
51
52
|
|
|
52
53
|
Prices are USD per 1M tokens. Omitted cache rates default to 0.
|
|
53
54
|
Price selectors use the exact upstream model ID after the first slash.`;
|
|
@@ -89,8 +90,8 @@ function priceRecord(value: unknown): value is Record<string, unknown> {
|
|
|
89
90
|
const PRICE_RATE_KEYS = ["input", "output", "cacheRead", "cacheWrite"] as const;
|
|
90
91
|
|
|
91
92
|
function validPriceCost(value: unknown): value is ProviderCostOverlay {
|
|
92
|
-
return priceRecord(value) &&
|
|
93
|
-
&&
|
|
93
|
+
return priceRecord(value) && validCost4(value)
|
|
94
|
+
&& Object.keys(value).every(key => key === "promptLengthPricing" || (PRICE_RATE_KEYS as readonly string[]).includes(key));
|
|
94
95
|
}
|
|
95
96
|
|
|
96
97
|
async function price(write: boolean, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -135,7 +136,9 @@ async function priceRequest(write: boolean, argv: string[], deps: RuntimeApiDeps
|
|
|
135
136
|
}
|
|
136
137
|
// The API map owns manual overrides; bundled defaults remain derived rather
|
|
137
138
|
// than being persisted as overrides that would mask later catalog updates.
|
|
138
|
-
const effectiveCost = cost
|
|
139
|
+
const effectiveCost = cost
|
|
140
|
+
? { input: cost.input, output: cost.output, cacheRead: cost.cacheRead, cacheWrite: cost.cacheWrite }
|
|
141
|
+
: resolveMatchedPrice(provider, modelId, undefined, [])?.cost4 ?? null;
|
|
139
142
|
printData({ provider, modelId, cost, effectiveCost }, wantsJson, [
|
|
140
143
|
effectiveCost === null ? `${selector}: automatic pricing (unknown)`
|
|
141
144
|
: `${selector}: ${JSON.stringify(effectiveCost)} USD per 1M tokens${cost === null ? " (automatic estimate)" : ""}`,
|
|
@@ -161,17 +164,42 @@ async function priceRequest(write: boolean, argv: string[], deps: RuntimeApiDeps
|
|
|
161
164
|
}
|
|
162
165
|
return value;
|
|
163
166
|
};
|
|
164
|
-
|
|
167
|
+
// Validate every rate before any request, so a bad flag never reaches the management plane.
|
|
168
|
+
const rates: ProviderCostOverlay | undefined = auto ? undefined : {
|
|
165
169
|
input: rate(input!, "--input"),
|
|
166
170
|
output: rate(output!, "--output"),
|
|
167
171
|
cacheRead: rate(cacheRead ?? "0", "--cache-read"),
|
|
168
172
|
cacheWrite: rate(cacheWrite ?? "0", "--cache-write"),
|
|
169
173
|
};
|
|
174
|
+
// A base-rate write replaces the whole row, so read the current row first and carry the
|
|
175
|
+
// stored prompt-length policy for exactly this model forward instead of silently deleting it.
|
|
176
|
+
let carriedPromptLengthPricing: ProviderCostOverlay["promptLengthPricing"];
|
|
177
|
+
if (rates) {
|
|
178
|
+
const current = await runtimeRequest<unknown>(path, {}, deps);
|
|
179
|
+
if (!priceRecord(current) || current.provider !== provider || !priceRecord(current.modelCosts)
|
|
180
|
+
|| !Object.values(current.modelCosts).every(validPriceCost)) {
|
|
181
|
+
throw new Error("Invalid model price response");
|
|
182
|
+
}
|
|
183
|
+
if (Object.hasOwn(current.modelCosts, modelId)) {
|
|
184
|
+
carriedPromptLengthPricing = (current.modelCosts[modelId] as ProviderCostOverlay).promptLengthPricing;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const cost: ProviderCostOverlay | null = rates
|
|
188
|
+
? { ...rates, ...(carriedPromptLengthPricing ? { promptLengthPricing: carriedPromptLengthPricing } : {}) }
|
|
189
|
+
: null;
|
|
170
190
|
const result = await runtimeRequest(path, { method: "PUT", body: JSON.stringify({ modelId, cost }) }, deps);
|
|
191
|
+
// Absent and { mode: "automatic" } are the same policy (the server stores automatic as
|
|
192
|
+
// absent), and key order carries no meaning in a JSON receipt, so compare canonically.
|
|
193
|
+
const canonicalPolicy = (policy: ProviderCostOverlay["promptLengthPricing"]): string =>
|
|
194
|
+
policy === undefined || policy.mode === "automatic" ? `{"mode":"automatic"}`
|
|
195
|
+
: JSON.stringify(policy, (_key, item: unknown) => (item && typeof item === "object" && !Array.isArray(item)
|
|
196
|
+
? Object.fromEntries(Object.entries(item as Record<string, unknown>).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)))
|
|
197
|
+
: item));
|
|
171
198
|
const receivedCost = priceRecord(result) ? result.cost : undefined;
|
|
172
199
|
if (!priceRecord(result) || result.ok !== true || result.provider !== provider || result.modelId !== modelId
|
|
173
200
|
|| (cost === null ? receivedCost !== null : !validPriceCost(receivedCost)
|
|
174
|
-
|| !PRICE_RATE_KEYS.every(key => receivedCost[key] === cost[key])
|
|
201
|
+
|| !PRICE_RATE_KEYS.every(key => receivedCost[key] === cost[key])
|
|
202
|
+
|| canonicalPolicy(receivedCost.promptLengthPricing) !== canonicalPolicy(cost.promptLengthPricing))) {
|
|
175
203
|
throw new Error("Invalid model price persistence receipt");
|
|
176
204
|
}
|
|
177
205
|
// Project the acknowledged fields only; unrelated response fields are not CLI output.
|
|
@@ -566,6 +594,39 @@ async function shadow(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
566
594
|
printData(result, wantsJson, ["Shadow-call settings updated."]);
|
|
567
595
|
}
|
|
568
596
|
|
|
597
|
+
async function autoReview(argv: string[], deps: RuntimeApiDeps): Promise<number | void> {
|
|
598
|
+
const args = [...argv];
|
|
599
|
+
const action = (args.shift() ?? "status").toLowerCase();
|
|
600
|
+
const wantsJson = takeFlag(args, "--json");
|
|
601
|
+
if (action === "status") {
|
|
602
|
+
rejectArgs(args, USAGE);
|
|
603
|
+
const result = await runtimeRequest("/api/auto-review-settings", {}, deps);
|
|
604
|
+
printData(result, wantsJson);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (action !== "set") throw new CliUsageError("unknown auto-review action " + action, USAGE);
|
|
608
|
+
const modelRaw = args[0] && !args[0].startsWith("--") ? args.shift() : undefined;
|
|
609
|
+
const enabled = takeBooleanOption(args, "--enabled");
|
|
610
|
+
rejectArgs(args, USAGE);
|
|
611
|
+
const body: Record<string, unknown> = {};
|
|
612
|
+
if (modelRaw !== undefined) body.model = modelRaw === "-" ? "" : modelRaw;
|
|
613
|
+
if (enabled !== undefined) body.enabled = enabled;
|
|
614
|
+
if (Object.keys(body).length === 0) throw new CliUsageError("model and/or --enabled is required", USAGE);
|
|
615
|
+
const result = await runtimeRequest<{ catalogRefresh?: { status?: string; retryable?: boolean } }>(
|
|
616
|
+
"/api/auto-review-settings", { method: "PUT", body: JSON.stringify(body) }, deps,
|
|
617
|
+
);
|
|
618
|
+
const refresh = result.catalogRefresh;
|
|
619
|
+
printData(result, wantsJson, [
|
|
620
|
+
"Auto-review settings updated.",
|
|
621
|
+
...(refresh?.status === "failed"
|
|
622
|
+
? ["Settings saved, but the Codex model catalog refresh failed. Resolve the refresh issue before running ocx sync."]
|
|
623
|
+
: refresh?.status === "skipped" && refresh.retryable === true
|
|
624
|
+
? ["Settings saved, but the Codex model catalog refresh is pending. Run ocx sync to retry."]
|
|
625
|
+
: []),
|
|
626
|
+
]);
|
|
627
|
+
if (refresh?.status === "failed") return 1;
|
|
628
|
+
}
|
|
629
|
+
|
|
569
630
|
export async function handleModelsRuntimeCommand(sub: string, argv: string[], deps: RuntimeApiDeps = {}): Promise<number | null> {
|
|
570
631
|
if (sub === "order" || sub === "display-name") {
|
|
571
632
|
const { handleModelsOrderCommand, handleModelsDisplayNameCommand } = await import("./models-order");
|
|
@@ -574,7 +635,7 @@ export async function handleModelsRuntimeCommand(sub: string, argv: string[], de
|
|
|
574
635
|
// The dispatch below and MODELS_RUNTIME_SUBCOMMANDS must name the same set;
|
|
575
636
|
// tests/cli/cli-models-runtime-dispatch.test.ts fails if they drift (#3094).
|
|
576
637
|
if (!isModelsRuntimeSubcommand(sub)) return null;
|
|
577
|
-
let action: (() => Promise<void>) | undefined;
|
|
638
|
+
let action: (() => Promise<number | void>) | undefined;
|
|
578
639
|
if (sub === "live") action = () => live(argv, deps);
|
|
579
640
|
else if (sub === "price") action = () => price(false, argv, deps);
|
|
580
641
|
else if (sub === "set-price") action = () => price(true, argv, deps);
|
|
@@ -589,8 +650,11 @@ export async function handleModelsRuntimeCommand(sub: string, argv: string[], de
|
|
|
589
650
|
else if (sub === "new-arrivals") action = () => newArrivals(argv, deps);
|
|
590
651
|
else if (sub === "context") action = () => context(argv, deps);
|
|
591
652
|
else if (sub === "shadow") action = () => shadow(argv, deps);
|
|
653
|
+
else if (sub === "auto-review") action = () => autoReview(argv, deps);
|
|
592
654
|
if (!action) return null;
|
|
593
|
-
|
|
655
|
+
let outcome: number | void = 0;
|
|
656
|
+
const exit = await runCliAction(async () => { outcome = await action(); });
|
|
657
|
+
return exit || outcome || 0;
|
|
594
658
|
}
|
|
595
659
|
|
|
596
660
|
export const MODELS_RUNTIME_USAGE = USAGE;
|
|
@@ -14,6 +14,8 @@ export interface ObserveStreamDeps extends RuntimeApiDeps {
|
|
|
14
14
|
/** Reduced timings for isolated tests only; never command-line options. */
|
|
15
15
|
pollIntervalMs?: number;
|
|
16
16
|
requestTimeoutMs?: number;
|
|
17
|
+
/** Frozen clock for usage-compare tests; never a command-line option. */
|
|
18
|
+
now?: () => number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export class ObservationError extends Error {}
|
package/src/cli/observe.ts
CHANGED
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
} from "./runtime-api";
|
|
15
15
|
import { followLogs } from "./log-follow";
|
|
16
16
|
import { handleLogFilterCommand } from "./log-view-filter";
|
|
17
|
+
import { handleLogGetCommand, handleLogHistoryCommand } from "./log-history";
|
|
17
18
|
import { followInjection } from "./injection-follow";
|
|
18
19
|
import type { ObserveStreamDeps } from "./observe-stream";
|
|
19
20
|
import { formatUsageReport } from "./usage-report";
|
|
21
|
+
import { handleUsageCompareCommand } from "./usage-compare";
|
|
20
22
|
import { selectUsageModelView, takeUsageSearchOption } from "./usage-model-search";
|
|
21
23
|
import { USAGE_RANGES, USAGE_SURFACES, type UsageSummary, type UsageFilterEcho } from "../usage/summary";
|
|
22
24
|
import { parseUsageTimeWindow, type UsageTimeWindow } from "../usage/time-range";
|
|
@@ -30,12 +32,16 @@ const USAGE = `Usage:
|
|
|
30
32
|
ocx observe logs [--provider <name>] [--model <id>] [--status <code>]
|
|
31
33
|
[--conversation <id>] [--account <label>] [--limit <n>] [--follow] [--json|--jsonl] [--events]
|
|
32
34
|
ocx logs explain <request-id> [--json]
|
|
35
|
+
ocx logs history [selectors] [--cursor <token>] [--limit <1-100>] [--json|--jsonl]
|
|
36
|
+
ocx logs get <request-id> [--json]
|
|
33
37
|
ocx logs filter [selectors] [--scan-limit <1..2000>] [--limit <1..2000>] [--json|--jsonl]
|
|
34
38
|
ocx logs rebuild-index
|
|
35
39
|
ocx logs index-status
|
|
36
40
|
ocx observe usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>]
|
|
37
41
|
[--since <epoch-ms|ISO-datetime>] [--until <epoch-ms|ISO-datetime>]
|
|
38
|
-
[--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--json]
|
|
42
|
+
[--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--top <1-1000>] [--json]
|
|
43
|
+
ocx usage compare [--range <today|1d|7d|30d>] [--surface <all|codex|claude|grok>]
|
|
44
|
+
[--provider <name>] [--model <id>] [--api-key-id <id>] [--json]
|
|
39
45
|
ocx observe storage [codex-logs [status|protect|unprotect|repair|compact] [--mode <compat|quiet>]] [--json]
|
|
40
46
|
ocx observe memory [--json]
|
|
41
47
|
ocx observe debug [--json]
|
|
@@ -174,6 +180,7 @@ async function indexStatus(argv: string[], deps: RuntimeApiDeps): Promise<void>
|
|
|
174
180
|
async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
175
181
|
const args = [...argv];
|
|
176
182
|
const search = takeUsageSearchOption(args);
|
|
183
|
+
const top = takeIntegerOption(args, "--top", { min: 1, max: 1000 });
|
|
177
184
|
const wantsJson = takeFlag(args, "--json");
|
|
178
185
|
const range = takeOption(args, "--range") ?? "30d";
|
|
179
186
|
const surface = takeOption(args, "--surface") ?? "all";
|
|
@@ -246,10 +253,10 @@ async function usage(argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
|
246
253
|
// the call, so passing formatUsageReport(...) inline would run the human
|
|
247
254
|
// renderer during --json and let its assumptions affect a path that is meant
|
|
248
255
|
// to bypass it entirely.
|
|
249
|
-
const view = search === undefined ? undefined : selectUsageModelView(result, search);
|
|
256
|
+
const view = search === undefined ? undefined : selectUsageModelView(result, search, wantsJson ? undefined : top);
|
|
250
257
|
const displayed = view ?? result;
|
|
251
258
|
if (wantsJson) printData(displayed, true);
|
|
252
|
-
else printData(displayed, false, formatUsageReport(displayed as Parameters<typeof formatUsageReport>[0],
|
|
259
|
+
else printData(displayed, false, formatUsageReport(displayed as Parameters<typeof formatUsageReport>[0], { modelView: view?.modelView, modelRowLimit: top }));
|
|
253
260
|
}
|
|
254
261
|
|
|
255
262
|
async function simple(path: string, argv: string[], deps: RuntimeApiDeps): Promise<void> {
|
|
@@ -320,12 +327,17 @@ export async function handleObserveCommand(argv: string[], deps: ObserveStreamDe
|
|
|
320
327
|
if (sub === "logs") {
|
|
321
328
|
const action = rest[0];
|
|
322
329
|
if (action === "filter") streamExit = await handleLogFilterCommand(rest.slice(1), deps, formatLog);
|
|
330
|
+
else if (action === "history") await handleLogHistoryCommand(rest.slice(1), deps, formatLog);
|
|
331
|
+
else if (action === "get") await handleLogGetCommand(rest.slice(1), deps, formatLog);
|
|
323
332
|
else if (action === "explain") await explain(rest.slice(1), deps);
|
|
324
333
|
else if (action === "rebuild-index") await rebuildIndex(rest.slice(1), deps);
|
|
325
334
|
else if (action === "index-status") await indexStatus(rest.slice(1), deps);
|
|
326
335
|
else streamExit = await logs(rest, deps);
|
|
327
336
|
}
|
|
328
|
-
else if (sub === "usage")
|
|
337
|
+
else if (sub === "usage") {
|
|
338
|
+
if (rest[0] === "compare") await handleUsageCompareCommand(rest.slice(1), deps);
|
|
339
|
+
else await usage(rest, deps);
|
|
340
|
+
}
|
|
329
341
|
else if (sub === "storage") await storage(rest, deps);
|
|
330
342
|
else if (sub === "memory") await simple("/api/system/memory", rest, deps);
|
|
331
343
|
else if (sub === "debug") await simple("/api/debug", rest, deps);
|
package/src/cli/provider.ts
CHANGED
|
@@ -8,13 +8,15 @@
|
|
|
8
8
|
* show <name> Show provider config details (secrets masked)
|
|
9
9
|
* set-default <name> Change the default provider
|
|
10
10
|
*/
|
|
11
|
-
import { hasOwnProvider, isValidProviderName, loadConfig, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config";
|
|
11
|
+
import { hasOwnProvider, isValidProviderName, loadConfig, readConfigAdmissionSnapshot, sanitizeModelCostsForDisplay, saveConfig, validateConfigCandidate, withConfigMutationLockSync } from "../config";
|
|
12
12
|
import { apiKeyTransportConfigError, modelCapabilitiesConfigError, mergeModelCapabilities } from "../config/provider-validation";
|
|
13
13
|
import { hasHelpFlag, printSubcommandUsage } from "./help";
|
|
14
14
|
import { getProviderRegistryEntry, PROVIDER_REGISTRY } from "../providers/registry";
|
|
15
15
|
import { providerConfigSeed } from "../providers/derive";
|
|
16
16
|
import { assertAnthropicInstanceLoginConfig } from "../oauth/store-anthropic-instance";
|
|
17
17
|
import { dropProviderCustomModels } from "../providers/provider-id-rewrite";
|
|
18
|
+
import { cleanupRemovedGcpCredentials, commitProviderGcpCredentialRemoval, providerGcpCredentialMarkers } from "../providers/gcp-credential-cleanup";
|
|
19
|
+
import { providerCredentialInputError } from "../providers/api-keys";
|
|
18
20
|
import type { OcxProviderConfig } from "../types";
|
|
19
21
|
import { findLiveProxy } from "../server/proxy-liveness";
|
|
20
22
|
import { syncModelsToCodex } from "../codex/sync";
|
|
@@ -223,6 +225,12 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
223
225
|
if (registryEntry) {
|
|
224
226
|
provConfig = providerConfigSeed(registryEntry);
|
|
225
227
|
if (apiKey) {
|
|
228
|
+
const candidateError = providerCredentialInputError({ apiKey });
|
|
229
|
+
if (candidateError) {
|
|
230
|
+
console.error(`Error: ${candidateError}`);
|
|
231
|
+
process.exitCode = 2;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
226
234
|
if ((authMode ?? registryEntry.authKind) === "forward") {
|
|
227
235
|
console.warn(`Warning: provider "${name}" uses ChatGPT login (forward auth); --api-key is ignored.`);
|
|
228
236
|
} else if ((authMode ?? registryEntry.authKind) === "oauth") {
|
|
@@ -249,6 +257,12 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
249
257
|
}
|
|
250
258
|
|
|
251
259
|
if (responsesPath !== undefined) provConfig.responsesPath = responsesPath;
|
|
260
|
+
const credentialError = providerCredentialInputError(provConfig);
|
|
261
|
+
if (credentialError) {
|
|
262
|
+
console.error(`Error: ${credentialError}`);
|
|
263
|
+
process.exitCode = 2;
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
252
266
|
if (authMode !== undefined) provConfig.authMode = authMode as OcxProviderConfig["authMode"];
|
|
253
267
|
if (name === "anthropic2") {
|
|
254
268
|
if (provConfig.adapter !== "anthropic" || provConfig.authMode !== "oauth") delete provConfig.anthropicOAuthInstance;
|
|
@@ -328,12 +342,17 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
328
342
|
}
|
|
329
343
|
const { initializeProviderModelSelection } = await import("../providers/initial-model-selection");
|
|
330
344
|
let markerCollision = false;
|
|
345
|
+
let removedCredentials: string[] = [];
|
|
331
346
|
withConfigMutationLockSync(() => {
|
|
332
347
|
if (provConfig.anthropicOAuthInstance) {
|
|
333
348
|
try { assertAnthropicInstanceLoginConfig(config, name); }
|
|
334
349
|
catch { markerCollision = true; return; }
|
|
335
350
|
}
|
|
336
351
|
initializeProviderModelSelection(name, provConfig, existingProvider, config);
|
|
352
|
+
const durable = readConfigAdmissionSnapshot();
|
|
353
|
+
const durableProvider = durable.kind === "read" && durable.diagnostics.source === "file"
|
|
354
|
+
&& durable.diagnostics.error === null ? durable.diagnostics.config.providers[name] : undefined;
|
|
355
|
+
removedCredentials = [config.providers[name], durableProvider].flatMap(provider => provider ? providerGcpCredentialMarkers(provider) : []);
|
|
337
356
|
config.providers[name] = provConfig;
|
|
338
357
|
if (setDefault) config.defaultProvider = name;
|
|
339
358
|
validateAndSave(config);
|
|
@@ -344,6 +363,7 @@ async function handleAdd(args: string[], deps: ProviderCommandDeps): Promise<voi
|
|
|
344
363
|
return;
|
|
345
364
|
}
|
|
346
365
|
|
|
366
|
+
cleanupRemovedGcpCredentials(removedCredentials);
|
|
347
367
|
let sync: LocalSyncResult | undefined;
|
|
348
368
|
if (wantsSync) {
|
|
349
369
|
try {
|
|
@@ -440,10 +460,12 @@ function handleRemove(args: string[]): void {
|
|
|
440
460
|
process.exit(1);
|
|
441
461
|
}
|
|
442
462
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
463
|
+
const droppedCustomModels = commitProviderGcpCredentialRemoval(config, name, () => {
|
|
464
|
+
delete config.providers[name];
|
|
465
|
+
const dropped = dropProviderCustomModels(config, name);
|
|
466
|
+
validateAndSave(config);
|
|
467
|
+
return dropped;
|
|
468
|
+
});
|
|
447
469
|
|
|
448
470
|
if (wantsJson) {
|
|
449
471
|
console.log(JSON.stringify({
|
package/src/cli/registry.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface CliCommandEntry {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
11
|
+
{
|
|
12
|
+
name: "codex-provider",
|
|
13
|
+
usage: "ocx codex-provider <status|allow <id> [--yes]|revoke <id>>",
|
|
14
|
+
summary: "Explicitly allow or revoke local routing management for a custom Codex provider.",
|
|
15
|
+
details: ["Consent is bound to the selected CODEX_HOME and provider ID. Only base_url is managed; transport settings and history are preserved."],
|
|
16
|
+
},
|
|
11
17
|
{
|
|
12
18
|
name: "message",
|
|
13
19
|
usage: "ocx message <sessions|send> [options]",
|
|
@@ -178,7 +184,7 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
178
184
|
summary: "Fetch provider models and inject them into Codex config.",
|
|
179
185
|
details: [
|
|
180
186
|
"After writing the catalog, warns if long-lived Codex app-server processes are still running.",
|
|
181
|
-
"--restart-codex restarts the app-servers AND fully quits and relaunches the Codex desktop app on macOS, Linux and Windows,
|
|
187
|
+
"--restart-codex restarts the app-servers AND fully quits and relaunches the Codex desktop app on macOS, Linux and Windows, after catalog/cache writes or a committed configuration change. It ends live conversations.",
|
|
182
188
|
"--restart-app-server-only keeps the narrow behaviour: SIGTERM to matching app-server / code-mode-host processes, desktop app left running. It wins over --restart-codex when both are given.",
|
|
183
189
|
"--restart-desktop-app is a deprecated alias of --restart-codex and prints a notice.",
|
|
184
190
|
],
|
|
@@ -226,9 +232,10 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
226
232
|
},
|
|
227
233
|
{
|
|
228
234
|
name: "login",
|
|
229
|
-
usage: "ocx login <provider>",
|
|
235
|
+
usage: "ocx login <provider> [--headless]",
|
|
230
236
|
summary: "OAuth or API-key login for a provider.",
|
|
231
237
|
details: [
|
|
238
|
+
"--headless (anthropic, anthropic2): no browser and no local port; approve in any browser and paste the displayed code.",
|
|
232
239
|
"Codex/ChatGPT: ocx login codex runs the Codex account-pool login (same flow as ocx account login codex).",
|
|
233
240
|
"That one needs a running proxy; the OAuth and API-key providers log in locally.",
|
|
234
241
|
"'chatgpt' and 'openai' are the same route; an OpenAI platform API key is 'ocx login openai-apikey'.",
|
|
@@ -336,6 +343,7 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
336
343
|
"add-key <provider> [--label <label>] Add a key read only from piped stdin.",
|
|
337
344
|
"pool <provider> Read/edit runtime-supported pool policy.",
|
|
338
345
|
"credits openai ... Explicit paid-credit-after-limit policy; separate from displaying balances.",
|
|
346
|
+
"use-remaining openai <id|main> <on|off|status> Use included quota up to its limit.",
|
|
339
347
|
"quota-activation openai Enable/disable one account reset-window refresh policy.",
|
|
340
348
|
"anthropic-reset-grants Read grant status only; consumption remains in the dashboard.",
|
|
341
349
|
"login supports explicit browser/add-account options only for applicable flows; human verification remains required.",
|
|
@@ -444,11 +452,21 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
444
452
|
"`inspect star` reads the repository star status only. Starring uses your GitHub identity and is available from the dashboard alone.",
|
|
445
453
|
],
|
|
446
454
|
},
|
|
447
|
-
{
|
|
455
|
+
{
|
|
456
|
+
name: "logs",
|
|
457
|
+
usage: "ocx logs [filters] [--follow] [--json|--jsonl|--events]; ocx logs history [selectors] [--cursor <token>] [--json|--jsonl]; ocx logs get <request-id> [--json]",
|
|
458
|
+
summary: "Read or follow request logs.",
|
|
459
|
+
details: [
|
|
460
|
+
"Default reads the recent ring. `history` pages the derived index with a resumable cursor; `get` reads one canonical row; `explain` is the route-decision view.",
|
|
461
|
+
],
|
|
462
|
+
},
|
|
448
463
|
{
|
|
449
464
|
name: "usage",
|
|
450
|
-
usage: "ocx usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>] [--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--json]",
|
|
465
|
+
usage: "ocx usage [--range <today|1d|7d|30d|all>] [--surface <all|codex|claude|grok>] [--provider <name>] [--model <id>] [--api-key-id <id>] [--search <text>] [--top <1-1000>] [--json]; ocx usage compare [--range <today|1d|7d|30d>] [--json]",
|
|
451
466
|
summary: "Report token usage and estimated cost (alias of ocx observe usage).",
|
|
467
|
+
details: [
|
|
468
|
+
"`compare` reads the current named range and the previous equal window; `--range all` is refused.",
|
|
469
|
+
],
|
|
452
470
|
},
|
|
453
471
|
{
|
|
454
472
|
name: "storage",
|
|
@@ -486,13 +504,14 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
486
504
|
},
|
|
487
505
|
{
|
|
488
506
|
name: "export",
|
|
489
|
-
usage: "ocx export --client <qoder|opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid> [--json] [--out <path>] [--force]",
|
|
490
|
-
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid, Qoder) wired to the running proxy.",
|
|
507
|
+
usage: "ocx export --client <qoder|opencode|pi|omp|hermes|openclaw|kimi|gajae|dsh|mcode|zcode|commandcode|prime|aside|raycast|omo|cline|kilo|droid|gentle-shell> [--json] [--out <path>] [--force]",
|
|
508
|
+
summary: "Print a client config (OpenCode, Pi, OMP, Hermes, OpenClaw, Kimi Code, gjc, DeepSeek Harness, MiniMax Code, ZCode, Command Code, Prime Agent, Aside, Raycast, omo, Cline, Kilo, Factory Droid, Qoder, Gentle Shell) wired to the running proxy.",
|
|
491
509
|
details: [
|
|
492
510
|
"--json prints the generated document as JSON on stdout; use --out for the client's native format.",
|
|
493
511
|
"--out <path> writes the native config there and refuses to replace an existing file without --force.",
|
|
494
512
|
"Cline exports a two-document bundle: settings for providers.json and catalog for sibling models.json. Use integration client enable --client cline for a journaled write.",
|
|
495
513
|
"Droid exports documented customModels for settings.json; use integration client enable --client droid for a journaled write.",
|
|
514
|
+
"Gentle Shell uses its persistent selected home; use integration client enable --client gentle-shell for a journaled write, or Pi for link/shared homes.",
|
|
496
515
|
"The config never contains a real key; it carries a documented env reference or a non-secret loopback placeholder.",
|
|
497
516
|
"The destination path is printed for merging by hand — ocx never writes your real client config.",
|
|
498
517
|
],
|
|
@@ -516,14 +535,15 @@ export const CLI_COMMANDS: CliCommandEntry[] = [
|
|
|
516
535
|
{
|
|
517
536
|
name: "system",
|
|
518
537
|
usage: "ocx system <status|health|settings|startup|diagnostics|sync|codex-app-server|codex-restart|update|codex-cli-update> ...",
|
|
519
|
-
summary: "Manage headless runtime settings, startup, sync, diagnostics, OpenCodex updates, and read-only Codex CLI inspection.",
|
|
538
|
+
summary: "Manage headless runtime settings, health, startup, sync, diagnostics, OpenCodex updates, and read-only Codex CLI inspection and planning.",
|
|
520
539
|
details: [
|
|
521
540
|
"system update manages OpenCodex itself.",
|
|
522
541
|
"system health reads authenticated service and spend-ledger diagnostics; root health remains liveness.",
|
|
523
542
|
"ocx system codex-cli-update check [--json]",
|
|
524
543
|
"ocx system codex-cli-update attest [--json]",
|
|
525
544
|
"ocx system codex-cli-update attest --candidate <absolute-path> --npm-prefix <absolute-path> --npm-cli <absolute-path> --node <absolute-path> [--json]",
|
|
526
|
-
"
|
|
545
|
+
"ocx system codex-cli-update plan [--channel latest] [--json]",
|
|
546
|
+
"check and attest make no package-registry request, do not execute Codex or npm, and write nothing. plan adds a registry query and a process-table read and mutates no application state (temporary isolated npm files only, removed best-effort).",
|
|
527
547
|
],
|
|
528
548
|
},
|
|
529
549
|
{
|