@bitkyc08/opencodex 2.84.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/README.md +0 -4
- package/bin/ocx.mjs +61 -3
- package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
- package/gui/dist/assets/App-wT3mXqFz.css +1 -0
- package/gui/dist/assets/{Tray-C9TGtwad.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 -2
- package/src/adapters/anthropic/beta-allowlist.ts +91 -11
- package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
- package/src/adapters/anthropic/client-identity.ts +1 -1
- package/src/adapters/anthropic/passthrough.ts +16 -3
- package/src/adapters/anthropic-progress.ts +84 -0
- package/src/adapters/anthropic.ts +108 -21
- package/src/adapters/azure.ts +19 -1
- 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/client-fingerprint.ts +24 -0
- package/src/adapters/coding-agent/protocol.ts +1 -1
- package/src/adapters/coding-agent/turn.ts +40 -11
- package/src/adapters/cursor/arg-codec.ts +17 -5
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +3 -0
- package/src/adapters/cursor/freeform-args.ts +75 -0
- package/src/adapters/cursor/protobuf-events.ts +36 -23
- package/src/adapters/dsh-account.ts +97 -0
- package/src/adapters/google-antigravity-wire.ts +22 -1
- package/src/adapters/google.ts +37 -10
- 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-chat/response-events.ts +59 -1
- package/src/adapters/openai-chat.ts +8 -5
- 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 +44 -3
- 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 +223 -0
- package/src/advisor/context.ts +238 -0
- package/src/advisor/disclosure.ts +20 -0
- package/src/advisor/evidence-tool.ts +21 -0
- package/src/advisor/evidence.ts +117 -0
- package/src/advisor/runtime.ts +353 -0
- package/src/advisor/settings.ts +144 -0
- package/src/advisor/state.ts +369 -0
- package/src/advisor/synthetic-tool.ts +33 -0
- package/src/bridge/errors.ts +24 -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/chat/inbound.ts +30 -6
- package/src/chat/outbound.ts +5 -0
- package/src/claude/desktop-3p-library.ts +72 -7
- package/src/claude/desktop-3p.ts +75 -19
- package/src/claude/desktop-first-party.ts +1 -1
- package/src/claude/desktop-picker-platform.ts +4 -0
- package/src/claude/desktop-picker-profile.ts +11 -4
- package/src/claude/desktop-picker.ts +15 -8
- package/src/claude/desktop-profile.ts +10 -1
- package/src/claude/desktop-remote-store-io.ts +5 -3
- package/src/claude/desktop-system-proxy.ts +50 -4
- package/src/claude/inbound-content-options.ts +11 -0
- package/src/claude/inbound.ts +154 -50
- package/src/claude/intercept/local-ca.ts +8 -1
- 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/model-default-settings.ts +154 -0
- package/src/claude/model-info.ts +7 -7
- package/src/claude/outbound.ts +51 -19
- 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 +165 -0
- package/src/cli/auxiliary-listener-preflight.ts +37 -0
- package/src/cli/capabilities-accounts.ts +20 -1
- package/src/cli/capabilities-agents-routing.ts +23 -2
- package/src/cli/capabilities-base.ts +39 -11
- package/src/cli/capabilities-observe-system.ts +63 -3
- package/src/cli/capabilities-provider-models.ts +11 -0
- package/src/cli/chatgpt-command.ts +35 -14
- package/src/cli/claude.ts +46 -6
- 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 +30 -5
- package/src/cli/doctor.ts +15 -1
- package/src/cli/ensure-desktop-cold-start.ts +382 -0
- package/src/cli/export-command.ts +1 -1
- package/src/cli/help.ts +4 -2
- package/src/cli/index.ts +14 -5
- package/src/cli/integration-plan-dto.ts +3 -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 +41 -9
- package/src/cli/restart-scope.ts +3 -1
- 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 +3 -1
- package/src/clients/config-export/model-metadata.ts +3 -2
- package/src/clients/config-export/qoder.ts +93 -0
- package/src/clients/config-export.ts +35 -48
- 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/pool-mode-gate.ts +5 -2
- 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 +81 -7
- package/src/codex/autostart-health.ts +4 -0
- package/src/codex/catalog/auto-review.ts +20 -3
- package/src/codex/catalog/build-entries.ts +19 -2
- package/src/codex/catalog/combo-member.ts +14 -5
- package/src/codex/catalog/configured-tiers.ts +50 -0
- package/src/codex/catalog/effort.ts +4 -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 +42 -19
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-ultrafast.ts +106 -0
- package/src/codex/catalog/parsing.ts +35 -86
- package/src/codex/catalog/provider-models.ts +40 -34
- package/src/codex/catalog/retained-sync.ts +2 -0
- package/src/codex/catalog/routed-gather.ts +33 -16
- package/src/codex/catalog/vendor-context.ts +82 -0
- 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 +63 -4
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desktop-app/picker-freshness.ts +96 -0
- package/src/codex/desktop-app/types.ts +2 -0
- package/src/codex/desktop-app/windows.ts +29 -16
- package/src/codex/desktop-app-restart.ts +25 -2
- package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
- package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
- package/src/codex/desktop-compatibility/connection-store.ts +87 -0
- package/src/codex/desktop-compatibility/installed-build.ts +73 -0
- package/src/codex/desktop-compatibility/json-body.ts +29 -0
- package/src/codex/desktop-compatibility/native-identity.ts +62 -0
- package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
- package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
- package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
- package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
- package/src/codex/desktop-compatibility/runtime.ts +182 -0
- package/src/codex/desktop-compatibility/service.ts +30 -0
- package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
- package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
- package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
- package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
- package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
- package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
- package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
- package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
- package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
- package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
- package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
- package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
- package/src/codex/features.ts +85 -45
- package/src/codex/history-provider.ts +1 -1
- package/src/codex/inject/config-write-section.ts +4 -1
- package/src/codex/inject/history-failure.ts +27 -0
- package/src/codex/inject/remove.ts +18 -6
- 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/journal.ts +16 -1
- package/src/codex/main-account-hard-lock.ts +30 -1
- package/src/codex/main-account.ts +99 -15
- package/src/codex/native-main-refresh-mode.ts +217 -0
- 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 +2 -2
- package/src/codex/routing/pool-recovery.ts +79 -0
- package/src/codex/routing/selection.ts +4 -3
- package/src/codex/routing/thread-affinity.ts +1 -1
- package/src/codex/shim-diagnostics.ts +6 -0
- package/src/codex/shim-overlay.ts +7 -2
- 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 +35 -7
- package/src/config/live-reconcile.ts +51 -2
- package/src/config/load-degrade.ts +23 -1
- 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/paths.ts +10 -3
- package/src/config/provider-validation.ts +45 -2
- package/src/config/schema/config-schema.ts +39 -0
- package/src/config/schema/desktop-compatibility.ts +9 -0
- package/src/config/schema/leaf-validators.ts +39 -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 +962 -350
- package/src/generated/model-metadata.ts +1 -1
- 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/config-io.ts +20 -2
- package/src/integrations/journal.ts +15 -1
- package/src/integrations/mutation-plan.ts +2 -0
- package/src/integrations/registry.ts +15 -0
- package/src/integrations/store.ts +3 -0
- package/src/integrations/writer.ts +10 -1
- package/src/lib/advisor-activation.ts +16 -0
- package/src/lib/background-work.ts +65 -0
- package/src/lib/bounded-file-read.ts +58 -0
- package/src/lib/bun-path-runtime.d.mts +10 -1
- package/src/lib/bun-path-runtime.mjs +62 -3
- package/src/lib/codex-restart-contract.ts +15 -0
- package/src/lib/desktop-proxy-route.ts +30 -0
- package/src/lib/desktop-upstream-tunnel.ts +72 -0
- package/src/lib/empty-tool-catalog.ts +62 -0
- package/src/lib/errors.ts +26 -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/local-internal-call-capability.ts +55 -0
- package/src/lib/opaque-proxy-tunnel.ts +217 -0
- package/src/lib/package-tree-integrity.ts +2 -2
- package/src/lib/redact.ts +2 -2
- package/src/lib/socks5-fetch.ts +48 -71
- package/src/lib/socks5-handshake.ts +112 -0
- package/src/lib/translator-budget.ts +7 -1
- 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 +62 -12
- package/src/oauth/generic-session-affinity.ts +125 -0
- package/src/oauth/index.ts +188 -111
- package/src/oauth/kiro-device-login.ts +2 -1
- package/src/oauth/kiro.ts +7 -1
- 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/codecs/chat-ir.ts +467 -0
- package/src/protocols/codecs/chat.ts +10 -1
- package/src/protocols/codecs/messages-ir.ts +500 -0
- package/src/protocols/encoders/adapter-events.ts +5 -4
- package/src/protocols/encoders/messages.ts +9 -6
- package/src/protocols/features.ts +10 -2
- package/src/protocols/path.ts +11 -1
- package/src/protocols/plan-snapshot.ts +39 -16
- package/src/protocols/plan.ts +9 -1
- package/src/protocols/provider-summary.ts +2 -1
- package/src/protocols/settings.ts +8 -0
- package/src/providers/antigravity-account-roster.ts +102 -0
- package/src/providers/antigravity-models.ts +85 -7
- 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 +5 -0
- package/src/providers/model-rename-migration.ts +7 -5
- 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 +44 -14
- package/src/providers/registry/entries-extended.ts +19 -0
- 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 +78 -22
- 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/responses/state.ts +32 -29
- package/src/router.ts +8 -321
- package/src/routing/health.ts +37 -22
- package/src/routing/history/build-file.ts +92 -0
- package/src/routing/history/indexer.ts +254 -221
- package/src/routing/history/ingest.ts +242 -0
- package/src/routing/history/rebuild-job.ts +111 -0
- package/src/routing/history/rebuild-protocol.ts +15 -0
- package/src/routing/history/rebuild-worker.ts +34 -0
- package/src/routing/profile.ts +1 -1
- package/src/server/admission-model-scope.ts +21 -1
- package/src/server/antigravity-direct-eligibility.ts +208 -0
- package/src/server/auth-cors.ts +15 -0
- package/src/server/chat-completions.ts +82 -10
- 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 +164 -433
- package/src/server/effort-policy.ts +1 -1
- package/src/server/images.ts +15 -2
- package/src/server/index/desktop-compatibility-startup.ts +56 -0
- 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 +11 -3
- package/src/server/index/startup-warnings.ts +11 -0
- package/src/server/index/websocket-handler.ts +9 -0
- package/src/server/index.ts +14 -11
- package/src/server/inference/advisor-diagnostics.ts +31 -0
- package/src/server/inference/client-encoder-delivery.ts +6 -5
- package/src/server/login-shell-path.ts +146 -0
- package/src/server/management/advisor-routes.ts +202 -0
- package/src/server/management/agent-settings-routes.ts +22 -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 +4 -0
- package/src/server/management/config-routes.ts +77 -6
- package/src/server/management/context.ts +4 -0
- package/src/server/management/cursor-integration-routes.ts +1 -1
- package/src/server/management/desktop-compatibility-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
- package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
- package/src/server/management/model-routes.ts +13 -8
- 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/protocol-settings-patch.ts +1 -0
- 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 +21 -1
- package/src/server/management/sibling-guard.ts +1 -0
- package/src/server/management-api.ts +9 -1
- package/src/server/messages-antigravity-direct.ts +195 -0
- package/src/server/messages-native-tool-guard.ts +185 -0
- package/src/server/messages-native.ts +106 -22
- 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 +13 -1
- package/src/server/responses/account-change-state.ts +8 -2
- package/src/server/responses/adapter-continuation.ts +48 -19
- package/src/server/responses/adapter-delivery.ts +43 -8
- package/src/server/responses/adapter-dispatch.ts +48 -4
- package/src/server/responses/advisor-plan-slot.ts +27 -0
- package/src/server/responses/advisor-retention.ts +117 -0
- package/src/server/responses/advisor-slot.ts +368 -0
- package/src/server/responses/code-mode-collab-surface.ts +116 -8
- 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 +12 -1
- package/src/server/responses/core-combo.ts +141 -36
- package/src/server/responses/core-errors.ts +3 -3
- package/src/server/responses/core-lifetime.ts +7 -2
- package/src/server/responses/core-normalize.ts +11 -0
- package/src/server/responses/core-opaque-recovery.ts +35 -0
- package/src/server/responses/core-options.ts +51 -0
- package/src/server/responses/core-pipeline.ts +141 -0
- package/src/server/responses/core-replay.ts +25 -6
- package/src/server/responses/core.ts +3 -116
- 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 +19 -11
- package/src/server/responses/passthrough-dispatch.ts +29 -16
- package/src/server/responses/request-prepare.ts +144 -17
- package/src/server/responses/request-sidecar-auth.ts +2 -2
- package/src/server/responses/request-transport.ts +120 -27
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +166 -38
- package/src/server/responses/sidecar-execution.ts +98 -5
- package/src/server/responses/terminal-guard.ts +4 -17
- package/src/server/responses-undeclared-tool-guard.ts +30 -9
- package/src/server/startup-action-control.ts +16 -2
- package/src/server/startup-health-cache.ts +1 -0
- 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/desktop-startup.ts +14 -0
- package/src/service/guards.ts +1 -61
- package/src/service/health-sentinel-linux.ts +112 -0
- package/src/service/health-sentinel-main.ts +99 -61
- package/src/service/health-sentinel-probe.ts +24 -7
- package/src/service/health.ts +5 -89
- package/src/service/launchd.ts +27 -7
- package/src/service/logging.ts +135 -0
- 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/tray/windows-tray.ps1 +43 -24
- package/src/types/config.ts +124 -6
- package/src/types/opencode-go.ts +61 -0
- package/src/types/opencode-zen.ts +29 -0
- package/src/types/provider.ts +39 -6
- package/src/types/request.ts +38 -2
- package/src/types/tools.ts +2 -0
- package/src/types/wire.ts +29 -37
- package/src/types.ts +1 -0
- package/src/update/async-check.ts +1 -1
- package/src/update/badge.ts +2 -2
- package/src/update/index.ts +88 -15
- package/src/update/job-supervised-restart.ts +107 -0
- package/src/update/job.ts +32 -32
- package/src/update/notify.ts +1 -1
- package/src/update/npm-invocation.mjs +12 -2
- package/src/update/refresh-scheduler.ts +2 -2
- package/src/update/supervised-recovery.d.mts +27 -0
- package/src/update/supervised-recovery.mjs +157 -0
- package/src/usage/cache-diagnostic.ts +2 -0
- package/src/usage/cost.ts +48 -12
- package/src/usage/expected-prices.ts +15 -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/gemini-executor.ts +2 -2
- 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/assets/sponsors/packycode.png +0 -0
- package/gui/dist/assets/App-CSJvmjNs.css +0 -1
- package/gui/dist/assets/App-CZtqiGcw.js +0 -56
- package/gui/dist/assets/index-BB6uK6tU.css +0 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
- package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Manager-mediated runtime recovery after an update (#7043).
|
|
5
|
+
*
|
|
6
|
+
* When the service refresh after an update fails or leaves a non-viable manager, both update
|
|
7
|
+
* lanes used to fall straight back to a detached `ocx start`. That proxy answers, but launchd
|
|
8
|
+
* or systemd no longer owns it: a crash is not restarted, and the next `launchctl kickstart`
|
|
9
|
+
* races the detached process for the port. Before that fallback, the lanes now ask the manager
|
|
10
|
+
* to restart the job it already holds, and count it as recovered only when the serving proxy is
|
|
11
|
+
* provably the manager's process: the runtime record's PID is the manager's main PID, or that
|
|
12
|
+
* PID's direct child (the job execs the Node launcher, which runs the Bun proxy as its child).
|
|
13
|
+
*
|
|
14
|
+
* Plain synchronous ESM because `bin/ocx.mjs` runs under Node, cannot import TypeScript, and
|
|
15
|
+
* keeps straight-line control flow; `src/update/index.ts` composes the same pieces with its own
|
|
16
|
+
* asynchronous identity wait.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Must match `LABEL` / `TASK` in src/service/state.ts; a test pins the pairing. */
|
|
20
|
+
export const LAUNCHD_SERVICE_LABEL = "com.opencodex.proxy";
|
|
21
|
+
export const SYSTEMD_SERVICE_UNIT = "opencodex-proxy";
|
|
22
|
+
|
|
23
|
+
function positivePid(value) {
|
|
24
|
+
if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return null;
|
|
25
|
+
const pid = Number(value.trim());
|
|
26
|
+
return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function defaultRun(file, args) {
|
|
30
|
+
// An armed test process must never reach the developer's real launchd or systemd job;
|
|
31
|
+
// same refusal as `assertLiveServiceManagerAllowed` for the TypeScript service layer.
|
|
32
|
+
if (process.env.OCX_TEST_HOME_GUARD === "1") {
|
|
33
|
+
return { ok: false, stdout: "", stderr: `refusing to run ${file} from an armed test process` };
|
|
34
|
+
}
|
|
35
|
+
// A bare SSH session has no user-bus address; systemd's user manager still lives at the
|
|
36
|
+
// conventional runtime directory (same fallback as the service layer's ensureUserBusEnv).
|
|
37
|
+
const env = file === "systemctl" && !process.env.XDG_RUNTIME_DIR && typeof process.getuid === "function"
|
|
38
|
+
? { ...process.env, XDG_RUNTIME_DIR: `/run/user/${process.getuid()}` }
|
|
39
|
+
: process.env;
|
|
40
|
+
const result = spawnSync(file, args, { encoding: "utf8", timeout: 20_000, windowsHide: true, env });
|
|
41
|
+
if (result.error) {
|
|
42
|
+
// A timeout kills only the waiting client; the manager's queued job keeps running.
|
|
43
|
+
const timedOut = result.error.code === "ETIMEDOUT";
|
|
44
|
+
return { ok: false, stdout: "", stderr: String(result.error.message ?? ""), ...(timedOut ? { timedOut } : {}) };
|
|
45
|
+
}
|
|
46
|
+
return { ok: result.status === 0, stdout: String(result.stdout ?? ""), stderr: String(result.stderr ?? "").trim() };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function launchdTarget(uid) {
|
|
50
|
+
return `gui/${uid}/${LAUNCHD_SERVICE_LABEL}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** `launchctl print` reports a running job as a `pid = <n>` line. */
|
|
54
|
+
export function parseLaunchdJobPid(printOutput) {
|
|
55
|
+
const match = /^\s*pid\s*=\s*(\d+)\s*$/m.exec(String(printOutput ?? ""));
|
|
56
|
+
return positivePid(match?.[1]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** `systemctl show -p MainPID` prints `MainPID=<n>`; 0 means not running. */
|
|
60
|
+
export function parseSystemdMainPid(showOutput) {
|
|
61
|
+
const match = /^MainPID=(\d+)\s*$/m.exec(String(showOutput ?? ""));
|
|
62
|
+
return positivePid(match?.[1]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolve(deps) {
|
|
66
|
+
const platform = deps.platform ?? process.platform;
|
|
67
|
+
const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
|
|
68
|
+
return { platform, uid, run: deps.run ?? defaultRun };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Whether this platform has a manager this module can ask (launchd or systemd). */
|
|
72
|
+
export function hasRestartableServiceManager(deps = {}) {
|
|
73
|
+
const { platform } = resolve(deps);
|
|
74
|
+
return platform === "darwin" || platform === "linux";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Ask the manager to restart the job in place. Never throws. `unsettled` means the client gave up
|
|
79
|
+
* waiting while the manager may still complete the restart: callers must not start a competing
|
|
80
|
+
* proxy on that answer.
|
|
81
|
+
*/
|
|
82
|
+
export function restartServiceManagerJob(deps = {}) {
|
|
83
|
+
const { platform, uid, run } = resolve(deps);
|
|
84
|
+
const result = platform === "darwin"
|
|
85
|
+
? run("/bin/launchctl", ["kickstart", "-k", launchdTarget(uid)])
|
|
86
|
+
: platform === "linux"
|
|
87
|
+
? run("systemctl", ["--user", "restart", SYSTEMD_SERVICE_UNIT])
|
|
88
|
+
: null;
|
|
89
|
+
if (result === null) return { ok: false, reason: "no launchd or systemd manager on this platform" };
|
|
90
|
+
if (result.timedOut) return { ok: false, unsettled: true, reason: "the service manager did not finish the restart in time" };
|
|
91
|
+
if (!result.ok) return { ok: false, reason: result.stderr.split("\n")[0] || "the service manager refused the restart" };
|
|
92
|
+
return { ok: true };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The manager's main PID for the job, or null when it reports none. Never throws. */
|
|
96
|
+
export function readServiceManagerPid(deps = {}) {
|
|
97
|
+
const { platform, uid, run } = resolve(deps);
|
|
98
|
+
if (platform === "darwin") {
|
|
99
|
+
const printed = run("/bin/launchctl", ["print", launchdTarget(uid)]);
|
|
100
|
+
return printed.ok ? parseLaunchdJobPid(printed.stdout) : null;
|
|
101
|
+
}
|
|
102
|
+
if (platform === "linux") {
|
|
103
|
+
const shown = run("systemctl", ["--user", "show", "-p", "MainPID", SYSTEMD_SERVICE_UNIT]);
|
|
104
|
+
return shown.ok ? parseSystemdMainPid(shown.stdout) : null;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Parent PID of a process, or null when it cannot be read. Never throws. */
|
|
110
|
+
export function readParentPid(pid, deps = {}) {
|
|
111
|
+
const { run } = resolve(deps);
|
|
112
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) return null;
|
|
113
|
+
const result = run("ps", ["-o", "ppid=", "-p", String(pid)]);
|
|
114
|
+
return result.ok ? positivePid(result.stdout) : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Whether the serving proxy is the manager's job. `parentPid` is consulted only when the
|
|
119
|
+
* serving PID is not the manager's own.
|
|
120
|
+
*/
|
|
121
|
+
export function supervisionVerdict({ servingPid, managerPid, parentPid }) {
|
|
122
|
+
if (!Number.isSafeInteger(servingPid) || servingPid <= 0) {
|
|
123
|
+
return { kind: "failed", reason: "the manager restarted the job but no proxy answered" };
|
|
124
|
+
}
|
|
125
|
+
if (!Number.isSafeInteger(managerPid) || managerPid <= 0) {
|
|
126
|
+
return { kind: "failed", reason: "the manager reports no running process for the job" };
|
|
127
|
+
}
|
|
128
|
+
if (servingPid === managerPid || parentPid(servingPid) === managerPid) {
|
|
129
|
+
return { kind: "supervised", managerPid, servingPid };
|
|
130
|
+
}
|
|
131
|
+
return { kind: "failed", reason: `the serving proxy (pid ${servingPid}) is not the manager's job (pid ${managerPid})` };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The serving PID to judge after a restart: a record still naming the pre-restart process is not
|
|
136
|
+
* evidence about the restarted one.
|
|
137
|
+
*/
|
|
138
|
+
export function freshServingPid(recordPid, staleRecordPid) {
|
|
139
|
+
return Number.isSafeInteger(recordPid) && recordPid > 0 && recordPid !== staleRecordPid ? recordPid : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** The warning printed when an update had to leave a proxy the manager does not own. */
|
|
143
|
+
export function unsupervisedRuntimeWarning(port) {
|
|
144
|
+
return [
|
|
145
|
+
`⚠️ The proxy on port ${port} is running UNSUPERVISED: the background service manager did not take it back after the update.`,
|
|
146
|
+
" A crash will not be restarted, and a later service start can race it for the port.",
|
|
147
|
+
" Run 'ocx service repair' to hand it back to the background service.",
|
|
148
|
+
].join("\n");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The warning printed when the manager may still be restarting; no second proxy is started. */
|
|
152
|
+
export function unsettledManagerWarning(port) {
|
|
153
|
+
return [
|
|
154
|
+
`⚠️ The service manager is still restarting the proxy on port ${port}; no second proxy was started.`,
|
|
155
|
+
" Check it with 'ocx service status', and run 'ocx service repair' if it does not come back.",
|
|
156
|
+
].join("\n");
|
|
157
|
+
}
|
|
@@ -294,11 +294,13 @@ export function appendFinalCacheDiagnostic(facts: CacheDiagnosticFinalFacts): vo
|
|
|
294
294
|
|
|
295
295
|
const CACHE_DIAGNOSTIC_HOOK = Symbol.for("opencodex.cache-diagnostic.v1");
|
|
296
296
|
interface CacheDiagnosticHooks {
|
|
297
|
+
enabled(): boolean;
|
|
297
298
|
observeInbound(body: unknown, headers: Headers, source: PromptCacheKeySource): CacheDiagnosticDraft;
|
|
298
299
|
rebind(body: unknown, draft: CacheDiagnosticDraft | undefined): void;
|
|
299
300
|
finalize(facts: CacheDiagnosticFinalFacts): void;
|
|
300
301
|
}
|
|
301
302
|
(globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK] = {
|
|
303
|
+
enabled: isCacheDiagnosticEnabled,
|
|
302
304
|
observeInbound,
|
|
303
305
|
rebind: rebindCacheDiagnosticBodyAlias,
|
|
304
306
|
finalize: appendFinalCacheDiagnostic,
|
package/src/usage/cost.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
resolveMetadataProvider,
|
|
16
16
|
} from "../generated/model-metadata";
|
|
17
17
|
import type { AttemptTierOutcome, OcxUsage } from "../types";
|
|
18
|
+
import type { PromptLengthPricing } from "../types/provider";
|
|
18
19
|
import { canonicalFastTierMarker } from "../providers/fastwire";
|
|
19
20
|
import { baseProviderLabel } from "../providers/label";
|
|
20
21
|
import type { PersistedUsageAttempt, UsageStatus } from "./log";
|
|
@@ -79,6 +80,7 @@ export interface MatchedPrice {
|
|
|
79
80
|
modelId: string;
|
|
80
81
|
jawcodeProvider?: string;
|
|
81
82
|
cost4: Cost4;
|
|
83
|
+
promptLengthPricing?: PromptLengthPricing;
|
|
82
84
|
source: "jawcode" | "expected" | "user";
|
|
83
85
|
sourceRef?: string;
|
|
84
86
|
verifiedAt?: string;
|
|
@@ -97,6 +99,8 @@ export interface AttemptCostEstimate {
|
|
|
97
99
|
priorityMultiplier?: number;
|
|
98
100
|
/** Set when the published long-context rate was applied (#908). */
|
|
99
101
|
contextTier?: ContextTierName;
|
|
102
|
+
/** User absolute rates selected for this attempt, separate from catalog tiers. */
|
|
103
|
+
customThresholdApplied?: true;
|
|
100
104
|
/** The numeric estimate is a known floor because the exact Priority price is unavailable. */
|
|
101
105
|
priorityLowerBound?: boolean;
|
|
102
106
|
}
|
|
@@ -111,6 +115,8 @@ export interface CostEstimate {
|
|
|
111
115
|
priorityMultiplier?: number;
|
|
112
116
|
/** Set when any priced attempt used the published long-context rate (#908). */
|
|
113
117
|
contextTier?: ContextTierName;
|
|
118
|
+
/** At least one request/attempt selected the user threshold rates. */
|
|
119
|
+
customThresholdApplied?: true;
|
|
114
120
|
/** The aggregate is a known floor because every priced attempt is a lower bound. */
|
|
115
121
|
priorityLowerBound?: boolean;
|
|
116
122
|
}
|
|
@@ -343,6 +349,7 @@ function userOverlayMatch(
|
|
|
343
349
|
modelId,
|
|
344
350
|
cost4: overlay.cost4,
|
|
345
351
|
source: "user",
|
|
352
|
+
...(overlay.promptLengthPricing ? { promptLengthPricing: overlay.promptLengthPricing } : {}),
|
|
346
353
|
sourceRef: overlay.source,
|
|
347
354
|
verifiedAt: overlay.verifiedAt,
|
|
348
355
|
status: "verified",
|
|
@@ -397,6 +404,15 @@ function vendorPrefixedCost(modelId: string): ReturnType<typeof findVendorCostBy
|
|
|
397
404
|
return normalize(found.provider) === normalize(claimedVendor) ? found : undefined;
|
|
398
405
|
}
|
|
399
406
|
|
|
407
|
+
/**
|
|
408
|
+
* Only measured usage is priced. An `unreported` or `unsupported` row can still carry tokens — a
|
|
409
|
+
* combo whose first leg failed without usage keeps the second leg's — and pricing them reports an
|
|
410
|
+
* understated cost as if it were complete.
|
|
411
|
+
*/
|
|
412
|
+
export function isMeasuredStatus(usageStatus: UsageStatus): boolean {
|
|
413
|
+
return usageStatus === "reported" || usageStatus === "estimated";
|
|
414
|
+
}
|
|
415
|
+
|
|
400
416
|
function isEstimated(usage: OcxUsage, usageStatus: UsageStatus, priceStatus: ExpectedPriceStatus | "verified"): boolean {
|
|
401
417
|
return usage.estimated === true || usageStatus === "estimated" || priceStatus === "verified-derived";
|
|
402
418
|
}
|
|
@@ -475,8 +491,8 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
|
|
|
475
491
|
}
|
|
476
492
|
|
|
477
493
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
494
|
+
* Select user prompt-length rates, or apply the published context rule in automatic mode.
|
|
495
|
+
* Returns [effectiveCost4, catalogTier, priorityLowerBound, customThresholdApplied].
|
|
480
496
|
*
|
|
481
497
|
* `rawInputTokens` MUST be `usage.inputTokens` (total prompt size), never the
|
|
482
498
|
* normalized billable input — normalization subtracts cache read/write, so a
|
|
@@ -486,13 +502,27 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
|
|
|
486
502
|
* API rows can stack the bands. xAI publishes neither a combined rate nor an exclusion,
|
|
487
503
|
* so its long-context rate remains the known lower bound instead of inventing a stacked multiplier.
|
|
488
504
|
*/
|
|
489
|
-
function
|
|
505
|
+
function applyPromptLengthPricing(
|
|
490
506
|
cost4: Cost4,
|
|
491
507
|
provider: string,
|
|
492
508
|
modelId: string,
|
|
493
509
|
rawInputTokens: number | undefined,
|
|
494
510
|
tier?: ServiceTierInput,
|
|
495
|
-
|
|
511
|
+
policy?: PromptLengthPricing,
|
|
512
|
+
): [Cost4, ContextTierName | undefined, boolean, true?] {
|
|
513
|
+
// A user policy replaces the catalog rule on BOTH sides of its threshold.
|
|
514
|
+
// Keep custom provenance separate while honoring the published Priority relation.
|
|
515
|
+
if (policy?.mode === "flat") return [cost4, undefined, false];
|
|
516
|
+
if (policy?.mode === "custom") {
|
|
517
|
+
const crossed = rawInputTokens !== undefined && Number.isFinite(rawInputTokens)
|
|
518
|
+
&& (policy.comparison === "gte"
|
|
519
|
+
? rawInputTokens >= policy.thresholdTokens : rawInputTokens > policy.thresholdTokens);
|
|
520
|
+
if (!crossed) return [cost4, undefined, false];
|
|
521
|
+
const relation = findContextTier(provider, modelId)?.confirmedPriorityRelation;
|
|
522
|
+
const confirmedFast = isConfirmedFast(tier);
|
|
523
|
+
if (confirmedFast && relation === "exclusive") return [cost4, undefined, false];
|
|
524
|
+
return [policy.rates, undefined, confirmedFast && relation === "lower-bound", true];
|
|
525
|
+
}
|
|
496
526
|
if (rawInputTokens === undefined) return [cost4, undefined, false];
|
|
497
527
|
const rule = findContextTier(provider, modelId);
|
|
498
528
|
if (!rule || !isLongContext(rule, rawInputTokens)) return [cost4, undefined, false];
|
|
@@ -520,9 +550,12 @@ function applyPriorityMultiplier(
|
|
|
520
550
|
modelId: string,
|
|
521
551
|
serviceTier?: ServiceTierInput,
|
|
522
552
|
contextTier?: ContextTierName,
|
|
553
|
+
customThresholdApplied?: true,
|
|
523
554
|
): [Cost4, number] {
|
|
524
555
|
if (canonicalFastTierMarker(tierScalar(serviceTier)) !== "priority") return [cost4, 1];
|
|
525
556
|
if (contextTier && findContextTier(provider, modelId)?.confirmedPriorityRelation !== "stack") return [cost4, 1];
|
|
557
|
+
if (customThresholdApplied && isConfirmedFast(serviceTier)
|
|
558
|
+
&& findContextTier(provider, modelId)?.confirmedPriorityRelation === "lower-bound") return [cost4, 1];
|
|
526
559
|
const rule = findPriorityPricingRule(provider, modelId);
|
|
527
560
|
if (rule?.requiresResponseConfirmation && !isConfirmedFast(serviceTier)) return [cost4, 1];
|
|
528
561
|
const multiplier = rule?.multiplier ?? 1;
|
|
@@ -568,7 +601,7 @@ export function estimateAttemptCost(
|
|
|
568
601
|
serviceTier?: ServiceTierInput,
|
|
569
602
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
570
603
|
): AttemptCostEstimate | null {
|
|
571
|
-
if (!attempt.usage) return null;
|
|
604
|
+
if (!isMeasuredStatus(attempt.usageStatus) || !attempt.usage) return null;
|
|
572
605
|
const tokens = normalizeCostTokens(attempt.usage);
|
|
573
606
|
if (!tokens) return null;
|
|
574
607
|
const price = resolveMatchedPrice(attempt.provider, attempt.model, overlays, userOverlays, attempt);
|
|
@@ -576,11 +609,11 @@ export function estimateAttemptCost(
|
|
|
576
609
|
const attemptServiceTier = attempt.tierOutcome
|
|
577
610
|
? serviceTierContextFromOutcome(attempt.tierOutcome)
|
|
578
611
|
: serviceTier;
|
|
579
|
-
const [tieredCost4, contextTier, contextPriorityLowerBound] =
|
|
580
|
-
price.cost4, price.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier,
|
|
612
|
+
const [tieredCost4, contextTier, contextPriorityLowerBound, customThresholdApplied] = applyPromptLengthPricing(
|
|
613
|
+
price.cost4, price.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier, price.promptLengthPricing,
|
|
581
614
|
);
|
|
582
615
|
const [effectiveCost4, multiplier] = applyPriorityMultiplier(
|
|
583
|
-
tieredCost4, price.provider, attempt.model, attemptServiceTier, contextTier,
|
|
616
|
+
tieredCost4, price.provider, attempt.model, attemptServiceTier, contextTier, customThresholdApplied,
|
|
584
617
|
);
|
|
585
618
|
const priorityLowerBound = contextPriorityLowerBound
|
|
586
619
|
|| isOpenRouterPriorityLowerBound(price.provider, attempt.tierOutcome);
|
|
@@ -594,6 +627,7 @@ export function estimateAttemptCost(
|
|
|
594
627
|
estimated: isEstimated(attempt.usage, attempt.usageStatus, price.status),
|
|
595
628
|
...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
|
|
596
629
|
...(contextTier ? { contextTier } : {}),
|
|
630
|
+
...(customThresholdApplied ? { customThresholdApplied } : {}),
|
|
597
631
|
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
598
632
|
};
|
|
599
633
|
}
|
|
@@ -637,6 +671,7 @@ export function estimateComboCost(
|
|
|
637
671
|
? { priorityMultiplier: estimates.find(est => est.priorityMultiplier)?.priorityMultiplier }
|
|
638
672
|
: {}),
|
|
639
673
|
...(estimates.some(est => est.contextTier) ? { contextTier: "long" as const } : {}),
|
|
674
|
+
...(estimates.some(est => est.customThresholdApplied) ? { customThresholdApplied: true as const } : {}),
|
|
640
675
|
...(estimates.every(est => est.priorityLowerBound === true)
|
|
641
676
|
? { priorityLowerBound: true as const }
|
|
642
677
|
: {}),
|
|
@@ -656,16 +691,16 @@ export function estimateRequestCost(
|
|
|
656
691
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
657
692
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
658
693
|
): CostEstimate | null {
|
|
659
|
-
if (!input.usage) return null;
|
|
694
|
+
if (!isMeasuredStatus(input.usageStatus) || !input.usage) return null;
|
|
660
695
|
const tokens = normalizeCostTokens(input.usage);
|
|
661
696
|
if (!tokens) return null;
|
|
662
697
|
const price = resolveMatchedPrice(input.provider, input.model, overlays, userOverlays, input);
|
|
663
698
|
if (!price) return null;
|
|
664
|
-
const [tieredCost4, contextTier, contextPriorityLowerBound] =
|
|
665
|
-
price.cost4, price.provider, input.model, input.usage.inputTokens, input.serviceTier,
|
|
699
|
+
const [tieredCost4, contextTier, contextPriorityLowerBound, customThresholdApplied] = applyPromptLengthPricing(
|
|
700
|
+
price.cost4, price.provider, input.model, input.usage.inputTokens, input.serviceTier, price.promptLengthPricing,
|
|
666
701
|
);
|
|
667
702
|
const [effectiveCost4, multiplier] = applyPriorityMultiplier(
|
|
668
|
-
tieredCost4, price.provider, input.model, input.serviceTier, contextTier,
|
|
703
|
+
tieredCost4, price.provider, input.model, input.serviceTier, contextTier, customThresholdApplied,
|
|
669
704
|
);
|
|
670
705
|
const priorityLowerBound = contextPriorityLowerBound || isOpenRouterPriorityLowerBound(
|
|
671
706
|
price.provider,
|
|
@@ -678,6 +713,7 @@ export function estimateRequestCost(
|
|
|
678
713
|
estimated: isEstimated(input.usage, input.usageStatus, price.status),
|
|
679
714
|
...(multiplier !== 1 ? { priorityMultiplier: multiplier } : {}),
|
|
680
715
|
...(contextTier ? { contextTier } : {}),
|
|
716
|
+
...(customThresholdApplied ? { customThresholdApplied } : {}),
|
|
681
717
|
...(priorityLowerBound ? { priorityLowerBound: true } : {}),
|
|
682
718
|
};
|
|
683
719
|
}
|
|
@@ -10,6 +10,7 @@ import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
|
10
10
|
export function pricingFamilyProvider(provider: string): string {
|
|
11
11
|
return isAnthropicInstanceId(provider) ? "anthropic" : provider;
|
|
12
12
|
}
|
|
13
|
+
import type { PromptLengthPricing } from "../types/provider";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Expected-price overlay for models whose jawcode cost rows are missing or all-zero
|
|
@@ -46,6 +47,8 @@ export interface ExpectedPriceOverlay {
|
|
|
46
47
|
provider: string;
|
|
47
48
|
modelId: string;
|
|
48
49
|
cost4: Cost4;
|
|
50
|
+
/** User-owned context policy; catalog rows leave this absent. */
|
|
51
|
+
promptLengthPricing?: PromptLengthPricing;
|
|
49
52
|
source: string;
|
|
50
53
|
verifiedAt: string;
|
|
51
54
|
status: ExpectedPriceStatus;
|
|
@@ -88,6 +91,10 @@ const GEMINI_37_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, ca
|
|
|
88
91
|
// rising to $1.50 / $7.50 on 2027-01-01. A SEPARATE constant on purpose: equal today, but
|
|
89
92
|
// aliasing them would silently drag 3.8 along if 3.7's row is ever re-verified differently.
|
|
90
93
|
const GEMINI_38_FLASH: Cost4 = { input: 0.75, output: 3.75, cacheRead: 0.075, cacheWrite: 0 };
|
|
94
|
+
// Gemini 4 Argon introductory paid-tier rate: $2 in / $10 out (thinking included) / $0.10 cached
|
|
95
|
+
// per 1M through 2026-11-30, stepping up to $4 / $20 / $0.20 on 2026-12-01. Revisit this row
|
|
96
|
+
// then. No free tier. Implicit caching only, so there is no per-token cache-write price.
|
|
97
|
+
const GEMINI_4_ARGON: Cost4 = { input: 2, output: 10, cacheRead: 0.1, cacheWrite: 0 };
|
|
91
98
|
const MINIMAX_M21_HIGHSPEED: Cost4 = { input: 0.6, output: 2.4, cacheRead: 0.03, cacheWrite: 0.375 };
|
|
92
99
|
const KIMI_K3: Cost4 = { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3 };
|
|
93
100
|
const KIMI_K27_CODE: Cost4 = { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite: 0.95 };
|
|
@@ -200,6 +207,7 @@ export function cursorFastPriceSource(provider: string, modelId: string): string
|
|
|
200
207
|
|
|
201
208
|
const GEMINI_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-07-22); cacheWrite=0: storage is billed per-hour, not per-token";
|
|
202
209
|
const GEMINI_37_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-08-14); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
|
|
210
|
+
const GEMINI_4_ARGON_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-10-14); introductory rate through 2026-11-30, rises to 4/20 (0.20 cached) on 2026-12-01; cacheWrite=0: implicit caching only";
|
|
203
211
|
const GEMINI_38_PRICING = "https://ai.google.dev/gemini-api/docs/pricing (2026-09-03); promotional rate through 2026-12-31, rises to 1.50/7.50 on 2027-01-01; cacheWrite=0: storage is billed per-hour, not per-token";
|
|
204
212
|
const MINIMAX_PRICING = "https://platform.minimax.io/docs/guides/pricing-paygo";
|
|
205
213
|
const OPENAI_GPT56_PRICING = "https://developers.openai.com/api/docs/pricing";
|
|
@@ -346,6 +354,12 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
346
354
|
// 3.7 Flash rides CCA, whose billing equivalence to the Developer API list price is
|
|
347
355
|
// not published, so this is `verified-derived` rather than `verified`: the number is
|
|
348
356
|
// proven, the claim that Antigravity charges it is inferred.
|
|
357
|
+
// Gemini 4 Argon on Antigravity (AI Ultra / Enterprise only): the picker id plus its three
|
|
358
|
+
// verified CCA wire ids. Billing equivalence to the API price is inferred.
|
|
359
|
+
{ provider: "google-antigravity", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: `derived: Gemini 4 Argon introductory rate through 2026-11-30 ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
|
|
360
|
+
{ provider: "google-antigravity", modelId: "gemini-4-argon-low", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
|
|
361
|
+
{ provider: "google-antigravity", modelId: "gemini-4-argon-medium", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
|
|
362
|
+
{ provider: "google-antigravity", modelId: "gemini-4-argon-high", cost4: GEMINI_4_ARGON, source: `derived: gemini-4-argon ${GEMINI_4_ARGON_PRICING}`, verifiedAt: "2026-10-14", status: "verified-derived" },
|
|
349
363
|
{ provider: "google-antigravity", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: `derived: Gemini 3.8 Flash promotional rate through 2026-12-31 ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
350
364
|
{ provider: "google-antigravity", modelId: "gemini-3.8-flash-low", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
351
365
|
{ provider: "google-antigravity", modelId: "gemini-3.8-flash-medium", cost4: GEMINI_38_FLASH, source: `derived: gemini-3.8-flash ${GEMINI_38_PRICING}`, verifiedAt: "2026-09-03", status: "verified-derived" },
|
|
@@ -403,6 +417,7 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
403
417
|
// Developer API row: the price IS published for this surface, so `verified`.
|
|
404
418
|
{ provider: "google", modelId: "gemini-3.7-flash", cost4: GEMINI_37_FLASH, source: GEMINI_37_PRICING, verifiedAt: "2026-08-14", status: "verified" },
|
|
405
419
|
{ provider: "google", modelId: "gemini-3.8-flash", cost4: GEMINI_38_FLASH, source: GEMINI_38_PRICING, verifiedAt: "2026-09-03", status: "verified" },
|
|
420
|
+
{ provider: "google", modelId: "gemini-4-argon", cost4: GEMINI_4_ARGON, source: GEMINI_4_ARGON_PRICING, verifiedAt: "2026-10-14", status: "verified" },
|
|
406
421
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-preview", cost4: GEMINI_31_PRO, source: GEMINI_PRICING, verifiedAt: "2026-07-20", status: "verified" },
|
|
407
422
|
// Antigravity-bundled third-party models — derived from the underlying vendor's
|
|
408
423
|
// official API price (Antigravity itself bills via subscription quota).
|
|
@@ -22,6 +22,7 @@ import { closeSync, fstatSync, openSync, readSync, writeSync } from "node:fs";
|
|
|
22
22
|
import { atomicWriteFileStreamed } from "../config/atomic-write";
|
|
23
23
|
import {
|
|
24
24
|
currentUsageLogRevision,
|
|
25
|
+
exactFileIdentity,
|
|
25
26
|
usageLogIdentityKey,
|
|
26
27
|
usageLogPath,
|
|
27
28
|
usageLogRevisionKey,
|
|
@@ -127,7 +128,7 @@ export function enforceUsageLedgerSizeLimit(
|
|
|
127
128
|
let sourceClosed = false;
|
|
128
129
|
try {
|
|
129
130
|
const opened = fstatSync(sourceFd);
|
|
130
|
-
if (Number(opened.size) !== captured.size ||
|
|
131
|
+
if (Number(opened.size) !== captured.size || exactFileIdentity(sourceFd).ino !== captured.ino) {
|
|
131
132
|
return { kind: "deferred", currentBytes: captured.size, reason: "revision-changed" };
|
|
132
133
|
}
|
|
133
134
|
const start = firstRowBoundary(sourceFd, Math.max(0, captured.size - target), captured.size);
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { closeSync, fstatSync, openSync, readSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
currentUsageLogRevision,
|
|
5
|
+
exactFileIdentity,
|
|
5
6
|
normalizePersistedUsageRow,
|
|
6
7
|
usageLogIdentityKey,
|
|
7
8
|
usageLogPath,
|
|
@@ -63,13 +64,13 @@ export class UsageLedgerRebuildRequiredError extends Error {
|
|
|
63
64
|
|
|
64
65
|
function revisionFromStat(
|
|
65
66
|
path: string,
|
|
67
|
+
fd: number,
|
|
66
68
|
stat: ReturnType<typeof fstatSync>,
|
|
67
69
|
): UsageLogRevision {
|
|
68
70
|
if (!stat.isFile()) throw new Error("usage log is not a regular file");
|
|
69
71
|
return {
|
|
70
72
|
path,
|
|
71
|
-
|
|
72
|
-
ino: Number(stat.ino),
|
|
73
|
+
...exactFileIdentity(fd),
|
|
73
74
|
birthtimeMs: Number(stat.birthtimeMs),
|
|
74
75
|
size: Number(stat.size),
|
|
75
76
|
mtimeMs: Number(stat.mtimeMs),
|
|
@@ -276,7 +277,7 @@ export async function scanUsageLedgerCooperatively(
|
|
|
276
277
|
let pendingLineBytes = 0;
|
|
277
278
|
|
|
278
279
|
try {
|
|
279
|
-
const openedRevision = revisionFromStat(path, fstatSync(fd));
|
|
280
|
+
const openedRevision = revisionFromStat(path, fd, fstatSync(fd));
|
|
280
281
|
const scanEnd = openedRevision.size;
|
|
281
282
|
const openedIdentityKey = usageLogIdentityKey(openedRevision);
|
|
282
283
|
if (options.expectedIdentityKey && options.expectedIdentityKey !== openedIdentityKey) {
|
|
@@ -391,7 +392,7 @@ export async function scanUsageLedgerCooperatively(
|
|
|
391
392
|
}
|
|
392
393
|
|
|
393
394
|
throwIfAborted(options.signal);
|
|
394
|
-
const endingRevision = revisionFromStat(path, fstatSync(fd));
|
|
395
|
+
const endingRevision = revisionFromStat(path, fd, fstatSync(fd));
|
|
395
396
|
if (usageLogIdentityKey(endingRevision) !== openedIdentityKey) {
|
|
396
397
|
throw rebuildRequired("identity_mismatch");
|
|
397
398
|
}
|
|
@@ -417,7 +418,7 @@ export async function scanUsageLedgerCooperatively(
|
|
|
417
418
|
line,
|
|
418
419
|
options.signal,
|
|
419
420
|
);
|
|
420
|
-
const verifiedFdRevision = revisionFromStat(path, fstatSync(fd));
|
|
421
|
+
const verifiedFdRevision = revisionFromStat(path, fd, fstatSync(fd));
|
|
421
422
|
const verifiedPathRevision = currentUsageLogRevision();
|
|
422
423
|
if (capturedDigest !== verifiedDigest) throw rebuildRequired("content_changed");
|
|
423
424
|
if (!verifiedPathRevision
|
package/src/usage/log.ts
CHANGED
|
@@ -1163,8 +1163,12 @@ export function appendUsageEntry(entry: PersistedUsageEntry): void {
|
|
|
1163
1163
|
|
|
1164
1164
|
export type UsageLogRevision = {
|
|
1165
1165
|
path: string;
|
|
1166
|
-
|
|
1167
|
-
|
|
1166
|
+
/**
|
|
1167
|
+
* Exact decimal device and file id. NTFS file ids carry a sequence number in their
|
|
1168
|
+
* high bits and routinely exceed 2^53, so a Number would let two files compare equal.
|
|
1169
|
+
*/
|
|
1170
|
+
dev: string;
|
|
1171
|
+
ino: string;
|
|
1168
1172
|
birthtimeMs: number;
|
|
1169
1173
|
size: number;
|
|
1170
1174
|
mtimeMs: number;
|
|
@@ -1367,12 +1371,17 @@ function readExactly(fd: number, length: number, position: number): Buffer | nul
|
|
|
1367
1371
|
return output;
|
|
1368
1372
|
}
|
|
1369
1373
|
|
|
1370
|
-
|
|
1374
|
+
/** Exact dev/ino of an open file, read through bigint stats so large ids keep every bit. */
|
|
1375
|
+
export function exactFileIdentity(fd: number): { dev: string; ino: string } {
|
|
1376
|
+
const exact = fstatSync(fd, { bigint: true });
|
|
1377
|
+
return { dev: exact.dev.toString(), ino: exact.ino.toString() };
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function usageLogRevision(path: string, fd: number, stat: ReturnType<typeof fstatSync>): UsageLogRevision {
|
|
1371
1381
|
if (!stat.isFile()) throw new Error("usage log is not a regular file");
|
|
1372
1382
|
return {
|
|
1373
1383
|
path,
|
|
1374
|
-
|
|
1375
|
-
ino: Number(stat.ino),
|
|
1384
|
+
...exactFileIdentity(fd),
|
|
1376
1385
|
birthtimeMs: Number(stat.birthtimeMs),
|
|
1377
1386
|
size: Number(stat.size),
|
|
1378
1387
|
mtimeMs: Number(stat.mtimeMs),
|
|
@@ -1405,7 +1414,7 @@ export function currentUsageLogRevision(): UsageLogRevision | null {
|
|
|
1405
1414
|
let fd: number | undefined;
|
|
1406
1415
|
try {
|
|
1407
1416
|
fd = openSync(path, "r");
|
|
1408
|
-
return usageLogRevision(path, fstatSync(fd));
|
|
1417
|
+
return usageLogRevision(path, fd, fstatSync(fd));
|
|
1409
1418
|
} finally {
|
|
1410
1419
|
if (fd !== undefined) closeSync(fd);
|
|
1411
1420
|
}
|
|
@@ -1530,7 +1539,7 @@ async function readUsageEntriesFullCooperatively(
|
|
|
1530
1539
|
if (prefixDigest === null) throw new Error("usage log changed while it was being read");
|
|
1531
1540
|
return {
|
|
1532
1541
|
entries: parsed.entries,
|
|
1533
|
-
revision: usageLogRevision(path, stat),
|
|
1542
|
+
revision: usageLogRevision(path, fd, stat),
|
|
1534
1543
|
truncatedPrefixBytes,
|
|
1535
1544
|
entriesTruncated: parsed.entriesDropped > 0,
|
|
1536
1545
|
entriesDropped: parsed.entriesDropped,
|
|
@@ -1562,7 +1571,7 @@ async function readUsageEntriesIncrementally(
|
|
|
1562
1571
|
try {
|
|
1563
1572
|
fd = openSync(path, "r");
|
|
1564
1573
|
const stat = fstatSync(fd);
|
|
1565
|
-
const revision = usageLogRevision(path, stat);
|
|
1574
|
+
const revision = usageLogRevision(path, fd, stat);
|
|
1566
1575
|
if (usageLogIdentityKey(revision) !== retained.identityKey) return null;
|
|
1567
1576
|
const size = Number(stat.size);
|
|
1568
1577
|
// A shrink means truncation or replacement-in-place; the retained rows may no
|
package/src/usage/summary.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type PersistedUsageEntry,
|
|
11
11
|
type UsageStatus,
|
|
12
12
|
} from "./log";
|
|
13
|
-
import { type AttemptCostEstimate, type CostEstimate, estimateAttemptCost, estimateRequestCost, serviceTierContext, type ServiceTierContext, tokensPerSecond } from "./cost";
|
|
13
|
+
import { type AttemptCostEstimate, type CostEstimate, estimateAttemptCost, estimateRequestCost, isMeasuredStatus, serviceTierContext, type ServiceTierContext, tokensPerSecond } from "./cost";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Canonical range members. The warm-up loop in the management usage route
|
|
@@ -303,7 +303,10 @@ export function computeEntryCost(entry: PersistedUsageEntry): EntryCostInfo {
|
|
|
303
303
|
isPriced = true;
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
-
|
|
306
|
+
// An unmeasured leg that may have generated billed tokens leaves the total unknown, not short.
|
|
307
|
+
const unknownCost = entry.attempts.some((attempt, index) =>
|
|
308
|
+
!attemptEstimates[index] && (!attempt.usage || !isMeasuredStatus(attempt.usageStatus)) && !spentNothing(attempt));
|
|
309
|
+
return { tier, estimate: null, attemptEstimates, costTotal, isPriced: isPriced && !unknownCost };
|
|
307
310
|
}
|
|
308
311
|
const estimate = estimateRequestCost({
|
|
309
312
|
...usageModelPriceOptions(entry, entry),
|
|
@@ -428,10 +431,6 @@ function blankTotals(): UsageSummaryTotals {
|
|
|
428
431
|
};
|
|
429
432
|
}
|
|
430
433
|
|
|
431
|
-
function isMeasuredStatus(status: UsageStatus): boolean {
|
|
432
|
-
return status === "reported" || status === "estimated";
|
|
433
|
-
}
|
|
434
|
-
|
|
435
434
|
export interface UsageAttribution {
|
|
436
435
|
requestId: string;
|
|
437
436
|
provider: string;
|
|
@@ -814,14 +813,28 @@ function blankRequestCounts(): UsageRequestCounts {
|
|
|
814
813
|
};
|
|
815
814
|
}
|
|
816
815
|
|
|
816
|
+
/**
|
|
817
|
+
* A usage-less attempt that provably cost nothing: it never reached upstream, or upstream refused it
|
|
818
|
+
* with an HTTP error before any output (providers do not bill a pre-generation 429/401/5xx). One
|
|
819
|
+
* whose stream began, aborted, or ended without a status may have generated billed tokens, so it
|
|
820
|
+
* stays an unknown cost. A request made only of such zeros carries neither fact and counts unpriced.
|
|
821
|
+
*/
|
|
822
|
+
function spentNothing(attempt: NonNullable<PersistedUsageEntry["attempts"]>[number] | undefined): boolean {
|
|
823
|
+
if (!attempt || attempt.usage) return false;
|
|
824
|
+
if (attempt.sendCount === 0 || attempt.locallyAnswered === true) return true;
|
|
825
|
+
return attempt.status >= 400 && attempt.streamAborted !== true && attempt.firstOutputMs === undefined;
|
|
826
|
+
}
|
|
827
|
+
|
|
817
828
|
function bumpRequestCounts(counts: UsageRequestCounts, facts: number, amount = 1): void {
|
|
818
829
|
counts.requests += amount;
|
|
819
830
|
const status = statusFromRequestFacts(facts);
|
|
820
831
|
if (isMeasuredStatus(status)) counts.measuredRequests += amount;
|
|
821
832
|
if (status === "reported") counts.reportedRequests += amount;
|
|
822
833
|
else if (status === "estimated") counts.estimatedRequests += amount;
|
|
823
|
-
|
|
824
|
-
|
|
834
|
+
// Unpriced dominates: one unpriceable part makes the request's cost unknown, and counting it
|
|
835
|
+
// on both sides overstated price coverage.
|
|
836
|
+
if ((facts & REQUEST_PRICED) !== 0 && (facts & REQUEST_UNPRICED) === 0) counts.pricedRequests += amount;
|
|
837
|
+
else counts.unpricedRequests += amount;
|
|
825
838
|
}
|
|
826
839
|
|
|
827
840
|
function mergeRequestCounts(target: UsageRequestCounts, source: UsageRequestCounts): void {
|
|
@@ -1557,7 +1570,7 @@ class StreamingUsageSummaryAccumulator implements UsageSummaryAccumulator {
|
|
|
1557
1570
|
: costInfo.estimate;
|
|
1558
1571
|
const ordinal = this.nextOrdinal++;
|
|
1559
1572
|
const facts = requestStatusFact(attribution.usageStatus)
|
|
1560
|
-
| (estimate ? REQUEST_PRICED : REQUEST_UNPRICED);
|
|
1573
|
+
| (estimate ? REQUEST_PRICED : spentNothing(entry.attempts?.[index]) ? 0 : REQUEST_UNPRICED);
|
|
1561
1574
|
const modelKey = this.addModelAttribution(partition, attribution, estimate, ordinal);
|
|
1562
1575
|
modelFacts.set(modelKey, (modelFacts.get(modelKey) ?? 0) | facts);
|
|
1563
1576
|
if (this.mode === "row-unique") {
|