@bitkyc08/opencodex 2.42.0 → 2.43.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/AGENTS_INSTALL.md +2 -2
- package/README.md +31 -0
- package/bin/ocx.mjs +10 -1
- package/gui/dist/assets/index-DS1NE4Jn.css +1 -0
- package/gui/dist/assets/index-Djowl68T.js +112 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-codec.ts +304 -0
- package/src/adapters/anthropic-image-normalize.ts +8 -298
- package/src/adapters/anthropic.ts +30 -7
- package/src/adapters/command-code.ts +7 -5
- package/src/adapters/cursor/desktop-executor-contract.ts +15 -0
- package/src/adapters/cursor/images.ts +36 -6
- package/src/adapters/cursor/live-transport.ts +7 -2
- package/src/adapters/cursor/native-exec-desktop.ts +2 -15
- package/src/adapters/cursor/protobuf-request.ts +13 -3
- package/src/adapters/cursor/tool-definitions.ts +5 -670
- package/src/adapters/cursor/tool-guidance.ts +236 -0
- package/src/adapters/cursor/tool-naming.ts +252 -0
- package/src/adapters/cursor/tool-schemas.ts +195 -0
- package/src/adapters/cursor/types.ts +6 -3
- package/src/adapters/exec-tool-result-normalize.ts +1 -1
- package/src/adapters/google-errors.ts +9 -1
- package/src/adapters/google.ts +1 -0
- package/src/adapters/kiro-calibration.ts +181 -0
- package/src/adapters/kiro.ts +135 -3
- package/src/adapters/openai-responses.ts +216 -26
- package/src/adapters/responses-code-mode.ts +59 -0
- package/src/adapters/tool-catalog-nudge.ts +1 -1
- package/src/adapters/xai-schema-analysis.ts +86 -0
- package/src/adapters/xai-tool-schema.ts +2 -87
- package/src/adapters/xai-web-search.ts +1 -1
- package/src/bridge.ts +22 -10
- package/src/chat/inbound.ts +11 -3
- package/src/claude/inbound-content-options.ts +60 -0
- package/src/claude/inbound-model-options.ts +142 -0
- package/src/claude/inbound-records.ts +7 -0
- package/src/claude/inbound.ts +10 -202
- package/src/claude/model-info.ts +45 -0
- package/src/cli/account-auth.ts +21 -6
- package/src/cli/capabilities.ts +13 -2
- package/src/cli/claude.ts +232 -39
- package/src/cli/config-command.ts +9 -1
- package/src/cli/dispatch.ts +5 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/effort.ts +372 -0
- package/src/cli/export-command.ts +3 -9
- package/src/cli/help.ts +1 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/init.ts +4 -0
- package/src/cli/model-selection-guidance.ts +30 -0
- package/src/cli/models-runtime.ts +3 -2
- package/src/cli/models.ts +8 -3
- package/src/cli/opencode.ts +4 -1
- package/src/cli/provider-runtime.ts +65 -0
- package/src/cli/provider.ts +8 -0
- package/src/cli/registry.ts +16 -2
- package/src/cli/runtime-api.ts +3 -1
- package/src/cli/star-prompt.ts +22 -6
- package/src/cli/status-probes.ts +168 -0
- package/src/cli/status.ts +5 -168
- package/src/clients/config-export/constants.ts +69 -0
- package/src/clients/config-export/contracts.ts +154 -0
- package/src/clients/config-export/dsh.ts +132 -0
- package/src/clients/config-export/fast-models.ts +29 -0
- package/src/clients/config-export/mcode.ts +83 -0
- package/src/clients/config-export/model-metadata.ts +108 -0
- package/src/clients/config-export/omp.ts +104 -0
- package/src/clients/config-export/zcode.ts +92 -0
- package/src/clients/config-export.ts +18 -710
- package/src/codex/account-lifecycle.ts +20 -3
- package/src/codex/account-usability.ts +2 -0
- package/src/codex/auth-api.ts +170 -23
- package/src/codex/auth-context.ts +328 -24
- package/src/codex/catalog/effort.ts +30 -4
- package/src/codex/catalog/metadata.ts +39 -9
- package/src/codex/catalog/native-models.ts +53 -12
- package/src/codex/catalog/parsing.ts +119 -5
- package/src/codex/catalog/provider-fetch.ts +150 -20
- package/src/codex/catalog/reserve.ts +52 -0
- package/src/codex/catalog/sync.ts +89 -16
- package/src/codex/convergence-types.ts +1 -0
- package/src/codex/convergence.ts +2 -0
- package/src/codex/data/upstream-models.json +1 -1
- package/src/codex/forward-transport-headers.ts +25 -0
- package/src/codex/inject.ts +3 -28
- package/src/codex/legacy-config-keys.ts +68 -0
- package/src/codex/log-guard/inspect-schema.ts +137 -0
- package/src/codex/log-guard/inspect.ts +2 -134
- package/src/codex/loopback-target.ts +54 -0
- package/src/codex/main-account-cache.ts +63 -1
- package/src/codex/main-account-hard-lock.ts +52 -0
- package/src/codex/main-account.ts +3 -1
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/model-entitlements.ts +54 -4
- package/src/codex/project-config-warnings.ts +92 -2
- package/src/codex/prompt-layers/encoding.ts +80 -0
- package/src/codex/prompt-layers/paths.ts +54 -0
- package/src/codex/prompt-layers/revision.ts +55 -0
- package/src/codex/prompt-layers/toml-edit.ts +163 -0
- package/src/codex/prompt-layers/toml-read.ts +181 -0
- package/src/codex/prompt-layers.ts +14 -520
- package/src/codex/quota-auto-refresh-state.ts +16 -0
- package/src/codex/quota-auto-refresh.ts +219 -0
- package/src/codex/quota-types.ts +51 -0
- package/src/codex/quota.ts +252 -93
- package/src/codex/reserve-availability.ts +177 -0
- package/src/codex/routing.ts +28 -9
- package/src/codex/shim.ts +53 -11
- package/src/codex/subagent-model-fallback.ts +23 -3
- package/src/combos/failover.ts +125 -7
- package/src/combos/identifiers.ts +89 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/resolve.ts +80 -9
- package/src/combos/types.ts +20 -93
- package/src/config/subagent-models.ts +24 -0
- package/src/config.ts +156 -13
- package/src/generated/compatibility-version.json +454 -166
- package/src/generated/model-metadata.ts +1 -1
- package/src/integrations/journal.ts +65 -4
- package/src/integrations/store.ts +5 -0
- package/src/lab/events/limits.ts +4 -0
- package/src/lib/destination-policy.ts +31 -2
- package/src/lib/errors.ts +39 -0
- package/src/lib/provider-outbound.ts +69 -3
- package/src/lib/proxy-env.ts +22 -0
- package/src/lib/redact-folding.ts +176 -0
- package/src/lib/redact.ts +2 -175
- package/src/lib/state-store-sweeper.ts +20 -6
- package/src/lib/token-estimate.ts +94 -27
- package/src/lib/windows-user-principal.ts +53 -5
- package/src/oauth/anthropic-routing.ts +99 -3
- package/src/oauth/generic-account-failover.ts +36 -13
- package/src/oauth/index.ts +127 -26
- package/src/oauth/login-cli.ts +5 -0
- package/src/oauth/meta-muse.ts +117 -15
- package/src/oauth/pool-settings-capability.ts +15 -4
- package/src/providers/api-keys.ts +8 -10
- package/src/providers/default-aliases.ts +39 -0
- package/src/providers/derive.ts +10 -2
- package/src/providers/fastwire.ts +36 -7
- package/src/providers/initial-model-selection-runtime.ts +90 -0
- package/src/providers/initial-model-selection.ts +120 -0
- package/src/providers/key-failover.ts +134 -54
- package/src/providers/key-store.ts +11 -1
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery.ts +76 -0
- package/src/providers/model-rename-startup.ts +72 -8
- package/src/providers/openai-sidecar.ts +17 -5
- package/src/providers/openai-tiers-destination.ts +102 -0
- package/src/providers/openai-tiers.ts +2 -99
- package/src/providers/opencode-go-transport.ts +41 -0
- package/src/providers/quota-key-accounts.ts +141 -0
- package/src/providers/quota-types.ts +9 -0
- package/src/providers/quota.ts +535 -104
- package/src/providers/registry.ts +60 -17
- package/src/providers/xai-responses-opt-in.ts +31 -5
- package/src/quota/reset-activation.ts +81 -0
- package/src/quota/reset-detector.ts +305 -0
- package/src/quota/reset-notify-config.ts +162 -0
- package/src/quota/reset-observer.ts +125 -0
- package/src/quota/reset-poller.ts +160 -0
- package/src/quota/reset-seen-store.ts +385 -0
- package/src/quota/reset-sinks.ts +199 -0
- package/src/quota/window-mapping.ts +106 -0
- package/src/responses/apply-patch-envelope.ts +46 -0
- package/src/responses/code-mode-helper-compat.ts +39 -1
- package/src/responses/custom-tool-compat.ts +10 -4
- package/src/responses/hosted-tool-policy.ts +12 -4
- package/src/responses/parser-content.ts +133 -0
- package/src/responses/parser-text-format.ts +24 -0
- package/src/responses/parser-tools.ts +188 -0
- package/src/responses/parser.ts +3 -326
- package/src/responses/state.ts +124 -28
- package/src/router.ts +48 -13
- package/src/routing/analytics.ts +1 -0
- package/src/routing/capability.ts +17 -4
- package/src/server/auth-cors.ts +7 -1
- package/src/server/background-lifecycle.ts +23 -1
- package/src/server/chat-completions.ts +25 -3
- package/src/server/claude-messages.ts +62 -5
- package/src/server/effort-row.ts +1 -1
- package/src/server/fast-row.ts +295 -0
- package/src/server/gui-static.ts +30 -4
- package/src/server/index.ts +107 -21
- package/src/server/management/agent-settings-routes.ts +2 -2
- package/src/server/management/combo-routes.ts +37 -9
- package/src/server/management/config-routes.ts +93 -2
- package/src/server/management/integration-routes.ts +108 -0
- package/src/server/management/model-routes.ts +13 -3
- package/src/server/management/model-rows.ts +20 -1
- package/src/server/management/native-integration-routes.ts +4 -1
- package/src/server/management/oauth-account-routes.ts +38 -10
- package/src/server/management/provider-routes.ts +34 -3
- package/src/server/management/quota-reset-routes.ts +57 -0
- package/src/server/management/route-registry.ts +7 -4
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/system-routes.ts +3 -2
- package/src/server/management-api.ts +14 -2
- package/src/server/ports.ts +12 -2
- package/src/server/relay-eager.ts +38 -23
- package/src/server/relay.ts +4 -0
- package/src/server/request-log.ts +6 -0
- package/src/server/responses/codex-ws-correlation.ts +65 -0
- package/src/server/responses/codex-ws-exchange.ts +261 -0
- package/src/server/responses/codex-ws-metadata.ts +134 -0
- package/src/server/responses/codex-ws-pool.ts +162 -0
- package/src/server/responses/codex-ws-request.ts +87 -0
- package/src/server/responses/codex-ws-session.ts +93 -0
- package/src/server/responses/codex-ws-wire.ts +144 -0
- package/src/server/responses/collaboration.ts +41 -1
- package/src/server/responses/compact.ts +105 -12
- package/src/server/responses/core.ts +476 -57
- package/src/server/responses/fetch-helpers.ts +10 -3
- package/src/server/responses/input-admission.ts +16 -9
- package/src/server/responses/responses-field-backfill.ts +1 -1
- package/src/server/responses/ws-upstream.ts +34 -318
- package/src/server/responses-custom-tool-repair.ts +20 -4
- package/src/server/responses-undeclared-tool-guard.ts +100 -8
- package/src/server/safe-response-headers.ts +23 -0
- package/src/server/search.ts +9 -0
- package/src/server/subagent-models-startup.ts +27 -0
- package/src/server/system-env-shell.ts +238 -0
- package/src/server/system-env.ts +7 -234
- package/src/server/ws-bridge.ts +3 -25
- package/src/server/xai-responses-startup.ts +21 -0
- package/src/service-manager-probe.ts +1 -1
- package/src/service.ts +55 -16
- package/src/types/config.ts +108 -12
- package/src/types/provider.ts +36 -7
- package/src/types/tools.ts +26 -1
- package/src/types.ts +2 -0
- package/src/update/notify.ts +8 -2
- package/src/usage/cost.ts +38 -28
- package/src/usage/expected-prices.ts +34 -15
- package/src/usage/log.ts +2 -0
- package/src/usage/model-identity.ts +26 -0
- package/src/usage/summary.ts +15 -1
- package/src/vision/describe.ts +6 -0
- package/src/vision/image-rewrite.ts +108 -0
- package/src/vision/index.ts +19 -306
- package/src/vision/plan.ts +205 -0
- package/src/web-search/executor.ts +6 -0
- package/src/web-search/index.ts +8 -1
- package/gui/dist/assets/index-BU1tE0sr.js +0 -112
- package/gui/dist/assets/index-DL9-iS6J.css +0 -1
package/src/types/provider.ts
CHANGED
|
@@ -270,6 +270,12 @@ export interface OcxProviderConfig {
|
|
|
270
270
|
* compaction blobs. Absent or false degrades foreign blobs to an opaque note.
|
|
271
271
|
*/
|
|
272
272
|
decodesNativeCompactionBlobs?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* Trust this direct key-auth Responses provider to consume or relay opaque encrypted
|
|
275
|
+
* V2 agent tasks. OpenCodex does not decrypt, translate, or recover an eligible task.
|
|
276
|
+
* Absent or false keeps the existing recovery/fail-closed behavior.
|
|
277
|
+
*/
|
|
278
|
+
allowEncryptedV2AgentTasks?: boolean;
|
|
273
279
|
/**
|
|
274
280
|
* Explicit opt-in for non-registry private-network destinations such as localhost, RFC1918,
|
|
275
281
|
* link-local, or unique-local upstreams. Metadata endpoints remain blocked.
|
|
@@ -339,6 +345,13 @@ export interface OcxProviderConfig {
|
|
|
339
345
|
* full set so the user can pick). See devlog issue_052_provider-model-allowlist.
|
|
340
346
|
*/
|
|
341
347
|
selectedModels?: string[];
|
|
348
|
+
/** Registration-owned state. Absent means legacy or OAuth-exempt, not uninitialized. */
|
|
349
|
+
initialModelSelection?: {
|
|
350
|
+
version: 1;
|
|
351
|
+
registrationId: string;
|
|
352
|
+
status: "pending" | "ready" | "all-off";
|
|
353
|
+
modelCount?: number;
|
|
354
|
+
};
|
|
342
355
|
/**
|
|
343
356
|
* Per-provider retention allowlist for authoritative live discovery. When non-empty, any
|
|
344
357
|
* model id in this list is preserved in the routed catalog even if the live `/models`
|
|
@@ -424,11 +437,13 @@ export interface OcxProviderConfig {
|
|
|
424
437
|
*/
|
|
425
438
|
authMode?: "key" | "forward" | "oauth" | "local";
|
|
426
439
|
/**
|
|
427
|
-
* Per-provider override for generic OAuth
|
|
440
|
+
* Per-provider override for the generic OAuth PROACTIVE account preference (#2568, #695).
|
|
428
441
|
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
442
|
+
* Reactive 429 rotation is presence-driven and cannot be refused here — 2+ logged-in accounts
|
|
443
|
+
* activate it, and a 429 with an idle second account is a defect rather than a preference.
|
|
444
|
+
* What an explicit `false` still refuses is the pre-dispatch preference that steers a HEALTHY
|
|
445
|
+
* request toward the account with more known headroom. It beats the global
|
|
446
|
+
* `oauthAccountFailover` in either direction; reactive 429 rotation remains presence-driven.
|
|
432
447
|
*/
|
|
433
448
|
oauthAccountFailover?: {
|
|
434
449
|
enabled?: boolean;
|
|
@@ -506,6 +521,8 @@ export interface OcxProviderConfig {
|
|
|
506
521
|
* from the web-search sidecar's `search.xSearch` options and never widens caller tool selectors.
|
|
507
522
|
*/
|
|
508
523
|
xaiResponsesXSearch?: boolean;
|
|
524
|
+
/** One-time Grok subscription wire upgrade; later explicit Chat choices remain authoritative. */
|
|
525
|
+
xaiResponsesDefaultVersion?: number;
|
|
509
526
|
/**
|
|
510
527
|
* Whether the Responses upstream accepts native custom tools and custom_tool_call items.
|
|
511
528
|
* Set false only for a provider whose native contract rejects them; absence preserves
|
|
@@ -518,9 +535,21 @@ export interface OcxProviderConfig {
|
|
|
518
535
|
* SSE/JSON; raw inspection state remains authoritative.
|
|
519
536
|
*/
|
|
520
537
|
responsesSnapshotRepair?: boolean;
|
|
521
|
-
/**
|
|
538
|
+
/**
|
|
539
|
+
* Provider-wide mapping from Codex effort labels to upstream `reasoning_effort` values.
|
|
540
|
+
* Map a label to the reserved value `"__omit__"` to send no reasoning field at all for that
|
|
541
|
+
* effort, so the upstream model's own default applies. The sentinel is
|
|
542
|
+
* `REASONING_EFFORT_OMIT_SENTINEL` in `src/reasoning-effort.ts`; it suppresses
|
|
543
|
+
* `reasoning_effort` on an OpenAI-compatible wire and Ollama's native `think` field on the
|
|
544
|
+
* Ollama native adapter (#2356).
|
|
545
|
+
*/
|
|
522
546
|
reasoningEffortMap?: Record<string, string>;
|
|
523
|
-
/**
|
|
547
|
+
/**
|
|
548
|
+
* Model-specific mapping from Codex effort labels to upstream `reasoning_effort` values.
|
|
549
|
+
* Map a label to the reserved value `"__omit__"` to send no reasoning field at all for that
|
|
550
|
+
* effort, so the upstream model's own default applies. Same sentinel as
|
|
551
|
+
* `reasoningEffortMap`, resolved per model first.
|
|
552
|
+
*/
|
|
524
553
|
modelReasoningEffortMap?: Record<string, Record<string, string>>;
|
|
525
554
|
/** OpenAI-compatible gateway reasoning wire shape. Default sends `reasoning_effort`. */
|
|
526
555
|
reasoningWireFormat?: "gateway-object";
|
|
@@ -696,7 +725,7 @@ export interface OcxProviderConfig {
|
|
|
696
725
|
* headless and cannot control a screen itself; provide commands here only when running on a host
|
|
697
726
|
* that can. With no executor, these tools honestly report "not supported".
|
|
698
727
|
*/
|
|
699
|
-
desktopExecutor?: import("../adapters/cursor/
|
|
728
|
+
desktopExecutor?: import("../adapters/cursor/desktop-executor-contract").DesktopExecutorConfig;
|
|
700
729
|
/**
|
|
701
730
|
* Cursor adapter only: unsafe opt-in escape hatch for Cursor server-driven built-in local
|
|
702
731
|
* read/write/delete/ls/grep/shell/fetch execution. Prefer `nativeLocalExec: "on"` for new
|
package/src/types/tools.ts
CHANGED
|
@@ -31,6 +31,17 @@ export function namespacedToolName(namespace: string | undefined, name: string):
|
|
|
31
31
|
return namespace ? `${namespace}__${name}` : name;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Dotted alias of a namespaced tool's wire name. Some routed providers (observed: muse-spark
|
|
36
|
+
* via opencode-go) echo a namespaced tool call as "<namespace>.<name>" instead of the flattened
|
|
37
|
+
* "<namespace>__<name>" form. It names the same tool identity+�u���T never a new grant"��y��y� so the
|
|
38
|
+
* undeclared-tool guard and the tool bridge maps accept it wherever the wire name is accepted
|
|
39
|
+
* (mirroring the second entry of `toolChoiceAliases`). See #3402.
|
|
40
|
+
*/
|
|
41
|
+
export function dottedToolName(namespace: string | undefined, name: string): string {
|
|
42
|
+
return namespace ? `${namespace}.${name}` : name;
|
|
43
|
+
}
|
|
44
|
+
|
|
34
45
|
/**
|
|
35
46
|
* Codex unified-exec name normalization.
|
|
36
47
|
*
|
|
@@ -73,9 +84,23 @@ export function normalizeDeclaredToolName(
|
|
|
73
84
|
: name;
|
|
74
85
|
}
|
|
75
86
|
|
|
87
|
+
/**
|
|
88
|
+
* True when a declared catalog is the genuine Codex code-mode shape.
|
|
89
|
+
*
|
|
90
|
+
* `exec` is a name, not a guarantee. A catalog that lists `exec` NEXT TO a bare
|
|
91
|
+
* `exec_command` or `shell_command` is the flat-bridge shape: there `exec` may be an
|
|
92
|
+
* ordinary caller-defined tool, and nested `tools.*` helpers are not what it runs.
|
|
93
|
+
* `normalizeDeclaredToolName` already refuses to reinterpret helper names in that shape,
|
|
94
|
+
* and anything inferring code mode from the bare name owes the same check.
|
|
95
|
+
*/
|
|
96
|
+
export function declaresCodeModeExec(declared: ReadonlySet<string> | undefined): boolean {
|
|
97
|
+
if (!declared || !declared.has(CODE_MODE_EXEC_TOOL_NAME)) return false;
|
|
98
|
+
return !(LEGACY_SHELL_BRIDGE_TOOL_NAMES as readonly string[]).some(legacy => declared.has(legacy));
|
|
99
|
+
}
|
|
100
|
+
|
|
76
101
|
export function toolChoiceAliases(tool: Pick<OcxTool, "namespace" | "name">): string[] {
|
|
77
102
|
const wireName = namespacedToolName(tool.namespace, tool.name);
|
|
78
|
-
return tool.namespace ? [wireName,
|
|
103
|
+
return tool.namespace ? [wireName, dottedToolName(tool.namespace, tool.name)] : [wireName];
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
function sameToolIdentity(
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export type { OcxTool, OcxToolChoice } from "./types/tools";
|
|
5
5
|
export {
|
|
6
6
|
CODE_MODE_EXEC_TOOL_NAME,
|
|
7
|
+
dottedToolName,
|
|
7
8
|
namespacedToolName,
|
|
8
9
|
normalizeDeclaredToolName,
|
|
9
10
|
toolChoiceAliases,
|
|
@@ -14,6 +15,7 @@ export {
|
|
|
14
15
|
modelInList,
|
|
15
16
|
isAllowedToolChoice,
|
|
16
17
|
toolChoiceToolPredicate,
|
|
18
|
+
declaresCodeModeExec,
|
|
17
19
|
} from "./types/tools";
|
|
18
20
|
|
|
19
21
|
export type { UpstreamHttpVersion, ReasoningSummaryDelivery, CodexAccountMode } from "./types/wire";
|
package/src/update/notify.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { isatty } from "node:tty";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import { atomicWriteFile, getConfigDir } from "../config";
|
|
6
7
|
import { hasStarPromptRun } from "../cli/star-prompt";
|
|
@@ -122,8 +123,13 @@ export function isSourceBuildVersion(v: string): boolean {
|
|
|
122
123
|
}
|
|
123
124
|
|
|
124
125
|
/** The interactive/TTY + install-method gate shared with the star prompt. */
|
|
125
|
-
function interactiveGuardOk(): boolean {
|
|
126
|
-
|
|
126
|
+
export function interactiveGuardOk(): boolean {
|
|
127
|
+
try {
|
|
128
|
+
return !(process.env.OCX_SERVICE || !isatty(0) || !isatty(1));
|
|
129
|
+
} catch {
|
|
130
|
+
/* best-effort */
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
127
133
|
}
|
|
128
134
|
|
|
129
135
|
/**
|
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 { canonicalFastTierMarker } from "../providers/fastwire";
|
|
18
19
|
import { baseProviderLabel, canonicalUsageProviderLabel } from "../providers/label";
|
|
19
20
|
import type { PersistedUsageAttempt, UsageStatus } from "./log";
|
|
20
21
|
import { canonicalAntigravityUsageModel } from "../providers/antigravity-models";
|
|
@@ -34,12 +35,18 @@ import {
|
|
|
34
35
|
/** Published long-context pricing band (#908). */
|
|
35
36
|
export type ContextTierName = "long";
|
|
36
37
|
|
|
38
|
+
export interface PriceResolutionOptions {
|
|
39
|
+
/** False for unresolved slash selectors; exact provider/user rates still apply. */
|
|
40
|
+
allowModelLevelFallback?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type AttemptCostInput = Pick<PersistedUsageAttempt, "ordinal" | "provider" | "model" | "usage" | "usageStatus" | "tierOutcome"> & PriceResolutionOptions;
|
|
44
|
+
|
|
37
45
|
/**
|
|
38
46
|
* Service-tier provenance. `effectiveServiceTier()` collapses these with `??`,
|
|
39
|
-
* but long-context
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* the long rate. Only a response-confirmed Fast tier suppresses the context tier.
|
|
47
|
+
* but provider-specific long-context rules need to know WHICH source supplied Fast.
|
|
48
|
+
* Current API rows stack Fast and long rates; legacy exclusive rows and unknown
|
|
49
|
+
* vendor combinations still require response-confirmed provenance.
|
|
43
50
|
*/
|
|
44
51
|
export interface ServiceTierContext {
|
|
45
52
|
responseServiceTier?: string;
|
|
@@ -178,6 +185,7 @@ export function resolveMatchedPrice(
|
|
|
178
185
|
modelId: string,
|
|
179
186
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
180
187
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
188
|
+
options: PriceResolutionOptions = {},
|
|
181
189
|
): MatchedPrice | null {
|
|
182
190
|
// User-configured overlays are keyed by the EXACT configured provider name.
|
|
183
191
|
// A provider that literally exists in config.providers keeps its own pricing
|
|
@@ -201,14 +209,14 @@ export function resolveMatchedPrice(
|
|
|
201
209
|
// user overlays get a NEW array identity + version bump on every config refresh,
|
|
202
210
|
// so memoized rows never go stale.
|
|
203
211
|
if (overlays === EXPECTED_PRICE_OVERLAYS && userOverlays === activeUserCostOverlays()) {
|
|
204
|
-
const cacheKey = `${userCostOverlayVersion()} ${provider} ${modelId}`;
|
|
212
|
+
const cacheKey = `${userCostOverlayVersion()} ${options.allowModelLevelFallback !== false} ${provider} ${modelId}`;
|
|
205
213
|
if (!priceMemo.has(cacheKey)) {
|
|
206
214
|
if (priceMemo.size >= 512) priceMemo.clear();
|
|
207
|
-
priceMemo.set(cacheKey, resolveMatchedPriceInner(provider, modelId, overlays, userOverlays));
|
|
215
|
+
priceMemo.set(cacheKey, resolveMatchedPriceInner(provider, modelId, overlays, userOverlays, options));
|
|
208
216
|
}
|
|
209
217
|
return priceMemo.get(cacheKey)!;
|
|
210
218
|
}
|
|
211
|
-
return resolveMatchedPriceInner(provider, modelId, overlays, userOverlays);
|
|
219
|
+
return resolveMatchedPriceInner(provider, modelId, overlays, userOverlays, options);
|
|
212
220
|
}
|
|
213
221
|
|
|
214
222
|
const priceMemo = new Map<string, MatchedPrice | null>();
|
|
@@ -223,14 +231,15 @@ function resolveMatchedPriceInner(
|
|
|
223
231
|
modelId: string,
|
|
224
232
|
overlays: readonly ExpectedPriceOverlay[],
|
|
225
233
|
userOverlays: readonly ExpectedPriceOverlay[],
|
|
234
|
+
options: PriceResolutionOptions,
|
|
226
235
|
): MatchedPrice | null {
|
|
227
|
-
const direct = resolveMatchedPriceExact(provider, modelId, overlays, userOverlays);
|
|
236
|
+
const direct = resolveMatchedPriceExact(provider, modelId, overlays, userOverlays, options);
|
|
228
237
|
if (direct) return direct;
|
|
229
238
|
// Antigravity historical/wire ids often lack an exact overlay; fall back to the
|
|
230
239
|
// picker/call base model so collapsed usage rows still get a price.
|
|
231
240
|
if (provider === "google-antigravity" || provider.startsWith("google-antigravity")) {
|
|
232
241
|
const base = canonicalAntigravityUsageModel(modelId);
|
|
233
|
-
if (base !== modelId) return resolveMatchedPriceExact(provider, base, overlays, userOverlays);
|
|
242
|
+
if (base !== modelId) return resolveMatchedPriceExact(provider, base, overlays, userOverlays, options);
|
|
234
243
|
}
|
|
235
244
|
return null;
|
|
236
245
|
}
|
|
@@ -245,6 +254,7 @@ function resolveMatchedPriceExact(
|
|
|
245
254
|
modelId: string,
|
|
246
255
|
overlays: readonly ExpectedPriceOverlay[],
|
|
247
256
|
userOverlays: readonly ExpectedPriceOverlay[],
|
|
257
|
+
options: PriceResolutionOptions,
|
|
248
258
|
): MatchedPrice | null {
|
|
249
259
|
// User-configured provider overlay wins over every compiled catalog: the
|
|
250
260
|
// operator's explicit price is authoritative for the ~$ estimate.
|
|
@@ -253,7 +263,7 @@ function resolveMatchedPriceExact(
|
|
|
253
263
|
const verifiedOverride = overlays === EXPECTED_PRICE_OVERLAYS
|
|
254
264
|
? findVerifiedPriceOverride(provider, modelId)
|
|
255
265
|
: undefined;
|
|
256
|
-
if (verifiedOverride && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
|
|
266
|
+
if (verifiedOverride && verifiedOverride.status !== "unverified" && validCost4(verifiedOverride.cost4) && hasNonZeroCost(verifiedOverride.cost4)) {
|
|
257
267
|
return {
|
|
258
268
|
provider,
|
|
259
269
|
modelId,
|
|
@@ -261,7 +271,7 @@ function resolveMatchedPriceExact(
|
|
|
261
271
|
source: "expected",
|
|
262
272
|
sourceRef: verifiedOverride.source,
|
|
263
273
|
verifiedAt: verifiedOverride.verifiedAt,
|
|
264
|
-
status:
|
|
274
|
+
status: verifiedOverride.status,
|
|
265
275
|
};
|
|
266
276
|
}
|
|
267
277
|
const metadataProvider = resolveMetadataProvider(provider);
|
|
@@ -280,7 +290,7 @@ function resolveMatchedPriceExact(
|
|
|
280
290
|
}
|
|
281
291
|
const overlay = findExpectedPriceOverlay(provider, modelId, overlays);
|
|
282
292
|
if (!overlay || !validCost4(overlay.cost4) || !hasNonZeroCost(overlay.cost4)) {
|
|
283
|
-
return resolveModelLevelPrice(provider, modelId);
|
|
293
|
+
return options.allowModelLevelFallback === false ? null : resolveModelLevelPrice(provider, modelId);
|
|
284
294
|
}
|
|
285
295
|
if (overlay.status === "unverified") return null;
|
|
286
296
|
return {
|
|
@@ -433,7 +443,7 @@ function tierScalar(tier?: ServiceTierInput): string | undefined {
|
|
|
433
443
|
|
|
434
444
|
/** True only when the UPSTREAM RESPONSE confirmed the Fast tier (see ServiceTierContext). */
|
|
435
445
|
function isConfirmedFast(tier?: ServiceTierInput): boolean {
|
|
436
|
-
return typeof tier === "object" && tier.responseServiceTier === "priority";
|
|
446
|
+
return typeof tier === "object" && canonicalFastTierMarker(tier.responseServiceTier) === "priority";
|
|
437
447
|
}
|
|
438
448
|
|
|
439
449
|
/**
|
|
@@ -445,7 +455,7 @@ function isConfirmedFast(tier?: ServiceTierInput): boolean {
|
|
|
445
455
|
* cache-heavy long prompt would fall below the boundary and under-bill.
|
|
446
456
|
*
|
|
447
457
|
* A provider's declaration decides how a response-confirmed priority tier relates to this band.
|
|
448
|
-
*
|
|
458
|
+
* API rows can stack the bands. xAI publishes neither a combined rate nor an exclusion,
|
|
449
459
|
* so its long-context rate remains the known lower bound instead of inventing a stacked multiplier.
|
|
450
460
|
*/
|
|
451
461
|
function applyContextTier(
|
|
@@ -481,9 +491,11 @@ function applyPriorityMultiplier(
|
|
|
481
491
|
provider: string,
|
|
482
492
|
modelId: string,
|
|
483
493
|
serviceTier?: ServiceTierInput,
|
|
494
|
+
contextTier?: ContextTierName,
|
|
484
495
|
): [Cost4, number] {
|
|
485
|
-
if (tierScalar(serviceTier) !== "priority") return [cost4, 1];
|
|
496
|
+
if (canonicalFastTierMarker(tierScalar(serviceTier)) !== "priority") return [cost4, 1];
|
|
486
497
|
const base = baseProviderLabel(provider);
|
|
498
|
+
if (contextTier && findContextTier(base, modelId)?.confirmedPriorityRelation !== "stack") return [cost4, 1];
|
|
487
499
|
const rule = findPriorityPricingRule(base, modelId);
|
|
488
500
|
if (rule?.requiresResponseConfirmation && !isConfirmedFast(serviceTier)) return [cost4, 1];
|
|
489
501
|
const multiplier = rule?.multiplier ?? 1;
|
|
@@ -524,7 +536,7 @@ function isOpenRouterPriorityLowerBound(
|
|
|
524
536
|
* missing so combos can fail closed.
|
|
525
537
|
*/
|
|
526
538
|
export function estimateAttemptCost(
|
|
527
|
-
attempt:
|
|
539
|
+
attempt: AttemptCostInput,
|
|
528
540
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
529
541
|
serviceTier?: ServiceTierInput,
|
|
530
542
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
@@ -532,7 +544,7 @@ export function estimateAttemptCost(
|
|
|
532
544
|
if (!attempt.usage) return null;
|
|
533
545
|
const tokens = normalizeCostTokens(attempt.usage);
|
|
534
546
|
if (!tokens) return null;
|
|
535
|
-
const price = resolveMatchedPrice(attempt.provider, attempt.model, overlays, userOverlays);
|
|
547
|
+
const price = resolveMatchedPrice(attempt.provider, attempt.model, overlays, userOverlays, attempt);
|
|
536
548
|
if (!price) return null;
|
|
537
549
|
const attemptServiceTier = attempt.tierOutcome
|
|
538
550
|
? serviceTierContextFromOutcome(attempt.tierOutcome)
|
|
@@ -540,12 +552,9 @@ export function estimateAttemptCost(
|
|
|
540
552
|
const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
|
|
541
553
|
price.cost4, attempt.provider, attempt.model, attempt.usage.inputTokens, attemptServiceTier,
|
|
542
554
|
);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
const [effectiveCost4, multiplier] = contextTier
|
|
547
|
-
? [tieredCost4, 1] as const
|
|
548
|
-
: applyPriorityMultiplier(tieredCost4, attempt.provider, attempt.model, attemptServiceTier);
|
|
555
|
+
const [effectiveCost4, multiplier] = applyPriorityMultiplier(
|
|
556
|
+
tieredCost4, attempt.provider, attempt.model, attemptServiceTier, contextTier,
|
|
557
|
+
);
|
|
549
558
|
const priorityLowerBound = contextPriorityLowerBound
|
|
550
559
|
|| isOpenRouterPriorityLowerBound(attempt.provider, attempt.tierOutcome);
|
|
551
560
|
return {
|
|
@@ -567,7 +576,7 @@ export function estimateAttemptCost(
|
|
|
567
576
|
* attempt is unpriced or unnormalizable, return null rather than a partial sum.
|
|
568
577
|
*/
|
|
569
578
|
export function estimateComboCost(
|
|
570
|
-
attempts: readonly
|
|
579
|
+
attempts: readonly AttemptCostInput[],
|
|
571
580
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
572
581
|
serviceTier?: ServiceTierInput,
|
|
573
582
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
@@ -615,6 +624,7 @@ export function estimateRequestCost(
|
|
|
615
624
|
usage?: OcxUsage;
|
|
616
625
|
usageStatus: UsageStatus;
|
|
617
626
|
serviceTier?: ServiceTierInput;
|
|
627
|
+
allowModelLevelFallback?: boolean;
|
|
618
628
|
},
|
|
619
629
|
overlays: readonly ExpectedPriceOverlay[] = EXPECTED_PRICE_OVERLAYS,
|
|
620
630
|
userOverlays: readonly ExpectedPriceOverlay[] = activeUserCostOverlays(),
|
|
@@ -622,14 +632,14 @@ export function estimateRequestCost(
|
|
|
622
632
|
if (!input.usage) return null;
|
|
623
633
|
const tokens = normalizeCostTokens(input.usage);
|
|
624
634
|
if (!tokens) return null;
|
|
625
|
-
const price = resolveMatchedPrice(input.provider, input.model, overlays, userOverlays);
|
|
635
|
+
const price = resolveMatchedPrice(input.provider, input.model, overlays, userOverlays, input);
|
|
626
636
|
if (!price) return null;
|
|
627
637
|
const [tieredCost4, contextTier, contextPriorityLowerBound] = applyContextTier(
|
|
628
638
|
price.cost4, input.provider, input.model, input.usage.inputTokens, input.serviceTier,
|
|
629
639
|
);
|
|
630
|
-
const [effectiveCost4, multiplier] =
|
|
631
|
-
|
|
632
|
-
|
|
640
|
+
const [effectiveCost4, multiplier] = applyPriorityMultiplier(
|
|
641
|
+
tieredCost4, input.provider, input.model, input.serviceTier, contextTier,
|
|
642
|
+
);
|
|
633
643
|
const priorityLowerBound = contextPriorityLowerBound || isOpenRouterPriorityLowerBound(
|
|
634
644
|
input.provider,
|
|
635
645
|
typeof input.serviceTier === "object" ? input.serviceTier.tierOutcome : undefined,
|
|
@@ -42,6 +42,8 @@ export interface ExpectedPriceOverlay {
|
|
|
42
42
|
|
|
43
43
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
44
44
|
const GPT56_SOL: Cost4 = { input: 4, output: 20, cacheRead: 0.4, cacheWrite: 5 };
|
|
45
|
+
const GPT6_ASTRA: Cost4 = { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 };
|
|
46
|
+
const ASTRA_API_PRICING = "https://developers.openai.com/api/docs/models/gpt-6-astra";
|
|
45
47
|
/**
|
|
46
48
|
* Daybreak aliases. `daybreak-*-latest` never appears in the pricing table itself — only its
|
|
47
49
|
* current snapshot does — so these tuples are the snapshot's published rates and carry
|
|
@@ -112,9 +114,14 @@ const KIMI_PRICING = "https://platform.kimi.ai/docs/pricing (official table; cac
|
|
|
112
114
|
const QWEN38_MAX_PRICING = "https://qwen.ai/blog?id=qwen3.8 (Qwen release announcement; no Model Studio billing row yet; cache rates unpublished -> 0)";
|
|
113
115
|
|
|
114
116
|
export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
117
|
+
{ provider: "openai-apikey", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: ASTRA_API_PRICING, verifiedAt: "2026-09-05", status: "verified" },
|
|
118
|
+
// Display estimates use API prices for both login and API-key routes, including cache writes.
|
|
119
|
+
{ provider: "openai", modelId: "gpt-6-astra", cost4: GPT6_ASTRA, source: `API-reference comparison estimate: ${ASTRA_API_PRICING}`, verifiedAt: "2026-09-05", status: "verified-derived" },
|
|
120
|
+
// claude-fable-5-1 now HAS a generated jawcode row, so the two Anthropic surfaces resolve
|
|
121
|
+
// from it and these overlays are the fallback rather than the primary source. They stay:
|
|
122
|
+
// the overlay lookup is keyed by the configured provider id, so an account-pool log label
|
|
123
|
+
// like anthropic-pb51d9b still needs them, and only the jawcode bundle collapses
|
|
124
|
+
// anthropic-apikey onto anthropic.
|
|
118
125
|
{ provider: "anthropic", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
|
|
119
126
|
{ provider: "anthropic-apikey", modelId: "claude-fable-5-1", cost4: CLAUDE_FABLE_51, source: `anthropic official Claude Fable 5.1 ${ANTHROPIC_PRICING}; cache hit = 0.025x base input`, verifiedAt: "2026-09-02", status: "verified" },
|
|
120
127
|
// Cursor canonicalizes every Fable 5.1 spelling onto this sole overlay row.
|
|
@@ -237,6 +244,11 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
237
244
|
* therefore cannot reprice routed resellers that reuse the same model slug.
|
|
238
245
|
*/
|
|
239
246
|
export const VERIFIED_PRICE_OVERRIDES: readonly ExpectedPriceOverlay[] = [
|
|
247
|
+
...["openai", "openai-apikey"].map((provider): ExpectedPriceOverlay => ({
|
|
248
|
+
provider, modelId: "gpt-5.6-sol", cost4: GPT56_SOL,
|
|
249
|
+
source: provider === "openai" ? `API-reference comparison estimate: ${OPENAI_GPT56_PRICING}` : OPENAI_GPT56_PRICING,
|
|
250
|
+
verifiedAt: "2026-09-05", status: provider === "openai" ? "verified-derived" : "verified",
|
|
251
|
+
})),
|
|
240
252
|
{
|
|
241
253
|
provider: "xai",
|
|
242
254
|
modelId: "grok-4.6",
|
|
@@ -278,6 +290,7 @@ export function findExpectedPriceOverlay(
|
|
|
278
290
|
|
|
279
291
|
/** OpenAI Fast price multipliers retained as a compatibility export. */
|
|
280
292
|
export const PRIORITY_MULTIPLIERS: Readonly<Record<string, number>> = {
|
|
293
|
+
"gpt-6-astra": 2,
|
|
281
294
|
"gpt-5.6-sol": 2,
|
|
282
295
|
"gpt-daybreak-blue-latest": 2,
|
|
283
296
|
"daybreak-blue-latest": 2,
|
|
@@ -306,7 +319,6 @@ export interface PriorityPricingRule {
|
|
|
306
319
|
verifiedAt: string;
|
|
307
320
|
}
|
|
308
321
|
|
|
309
|
-
const OPENAI_FAST_PRICING = "https://openai.com/api-fast-mode/";
|
|
310
322
|
const XAI_PRIORITY_PRICING = "https://docs.x.ai/developers/advanced-api-usage/priority-processing";
|
|
311
323
|
|
|
312
324
|
/**
|
|
@@ -325,10 +337,15 @@ export const PRIORITY_PRICING_RULES: readonly PriorityPricingRule[] = [
|
|
|
325
337
|
provider,
|
|
326
338
|
modelId,
|
|
327
339
|
multiplier,
|
|
328
|
-
source:
|
|
329
|
-
verifiedAt: "2026-08-05",
|
|
340
|
+
source: modelId === "gpt-6-astra" ? ASTRA_API_PRICING : "https://openai.com/api-fast-mode/",
|
|
341
|
+
verifiedAt: modelId === "gpt-6-astra" ? "2026-09-05" : "2026-08-05",
|
|
330
342
|
})),
|
|
331
343
|
),
|
|
344
|
+
...["gpt-5.6-sol-pro", "gpt-5.6-terra-pro", "gpt-5.6-luna-pro"].map((modelId): PriorityPricingRule => ({
|
|
345
|
+
provider: "openai-apikey", modelId, multiplier: 2,
|
|
346
|
+
source: "https://developers.openai.com/api/docs/pricing (derived from the virtual selection's base wire model)",
|
|
347
|
+
verifiedAt: "2026-09-05",
|
|
348
|
+
})),
|
|
332
349
|
...["grok-4.5", "grok-4.6"].map((modelId): PriorityPricingRule => ({
|
|
333
350
|
provider: "xai",
|
|
334
351
|
modelId,
|
|
@@ -374,7 +391,7 @@ export interface ContextTier {
|
|
|
374
391
|
/** Per-field factor from the short rate to the published long rate. */
|
|
375
392
|
multiplier: Cost4;
|
|
376
393
|
/** Published relationship between confirmed priority and long-context bands. */
|
|
377
|
-
confirmedPriorityRelation?: "exclusive" | "lower-bound";
|
|
394
|
+
confirmedPriorityRelation?: "exclusive" | "lower-bound" | "stack";
|
|
378
395
|
source: string;
|
|
379
396
|
verifiedAt: string;
|
|
380
397
|
}
|
|
@@ -389,7 +406,8 @@ const OPENAI_LONG_CONTEXT: Cost4 = { input: 2, output: 1.5, cacheRead: 2, cacheW
|
|
|
389
406
|
const UNIFORM_DOUBLE: Cost4 = { input: 2, output: 2, cacheRead: 2, cacheWrite: 2 };
|
|
390
407
|
|
|
391
408
|
const OPENAI_PRICING_DOC = "https://developers.openai.com/api/docs/pricing";
|
|
392
|
-
const
|
|
409
|
+
const OPENAI_CONTEXT_MODELS = [
|
|
410
|
+
"gpt-6-astra",
|
|
393
411
|
"gpt-5.6-sol",
|
|
394
412
|
"gpt-5.6-terra",
|
|
395
413
|
"gpt-5.6-luna",
|
|
@@ -401,16 +419,17 @@ const OPENAI_GPT56_CONTEXT_MODELS = [
|
|
|
401
419
|
];
|
|
402
420
|
|
|
403
421
|
export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
422
|
+
// API-reference estimates do not apply subscription-only exemptions or multipliers.
|
|
404
423
|
...["openai", "openai-apikey"].flatMap(provider =>
|
|
405
|
-
|
|
424
|
+
OPENAI_CONTEXT_MODELS.map((modelId): ContextTier => ({
|
|
406
425
|
provider,
|
|
407
426
|
modelId,
|
|
408
427
|
thresholdInputTokens: 272_000,
|
|
409
428
|
inclusive: false,
|
|
410
429
|
multiplier: OPENAI_LONG_CONTEXT,
|
|
411
|
-
confirmedPriorityRelation: "
|
|
430
|
+
confirmedPriorityRelation: "stack",
|
|
412
431
|
source: OPENAI_PRICING_DOC,
|
|
413
|
-
verifiedAt: "2026-
|
|
432
|
+
verifiedAt: "2026-09-05",
|
|
414
433
|
})),
|
|
415
434
|
),
|
|
416
435
|
{
|
|
@@ -420,9 +439,9 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
420
439
|
thresholdInputTokens: 272_000,
|
|
421
440
|
inclusive: false,
|
|
422
441
|
multiplier: OPENAI_LONG_CONTEXT,
|
|
423
|
-
confirmedPriorityRelation: "
|
|
442
|
+
confirmedPriorityRelation: "stack",
|
|
424
443
|
source: OPENAI_PRICING_DOC,
|
|
425
|
-
verifiedAt: "2026-
|
|
444
|
+
verifiedAt: "2026-09-05",
|
|
426
445
|
},
|
|
427
446
|
{
|
|
428
447
|
// The bare selector is the separately billed API-key alias. Daybreak Red has no tier row:
|
|
@@ -432,9 +451,9 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
432
451
|
thresholdInputTokens: 272_000,
|
|
433
452
|
inclusive: false,
|
|
434
453
|
multiplier: OPENAI_LONG_CONTEXT,
|
|
435
|
-
confirmedPriorityRelation: "
|
|
454
|
+
confirmedPriorityRelation: "stack",
|
|
436
455
|
source: OPENAI_PRICING_DOC,
|
|
437
|
-
verifiedAt: "2026-
|
|
456
|
+
verifiedAt: "2026-09-05",
|
|
438
457
|
},
|
|
439
458
|
{
|
|
440
459
|
provider: "xai",
|
package/src/usage/log.ts
CHANGED
|
@@ -46,6 +46,7 @@ export type AttemptRecoveryKind =
|
|
|
46
46
|
| "transient-5xx"
|
|
47
47
|
| "connection-reset"
|
|
48
48
|
| "oauth-401"
|
|
49
|
+
| "key-401"
|
|
49
50
|
| "key-429"
|
|
50
51
|
| "rate-limit-429"
|
|
51
52
|
| "anthropic-oauth-429"
|
|
@@ -257,6 +258,7 @@ const ATTEMPT_RECOVERY_KINDS = new Set<AttemptRecoveryKind>([
|
|
|
257
258
|
"transient-5xx",
|
|
258
259
|
"connection-reset",
|
|
259
260
|
"oauth-401",
|
|
261
|
+
"key-401",
|
|
260
262
|
"key-429",
|
|
261
263
|
"rate-limit-429",
|
|
262
264
|
"anthropic-oauth-429",
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { baseProviderLabel } from "../providers/label";
|
|
2
|
+
import type { PriceResolutionOptions } from "./cost";
|
|
3
|
+
import type { PersistedUsageEntry } from "./log";
|
|
4
|
+
|
|
5
|
+
type TraceSource = Pick<PersistedUsageEntry, "routeDecision">;
|
|
6
|
+
type ModelTarget = Pick<PersistedUsageEntry, "provider" | "model">;
|
|
7
|
+
|
|
8
|
+
/** Saved routing provenance, not a claim about the model a remote endpoint ran. */
|
|
9
|
+
export function isUnresolvedRequestedModel(source: TraceSource, target: ModelTarget): boolean {
|
|
10
|
+
const trace = source.routeDecision;
|
|
11
|
+
return trace !== undefined
|
|
12
|
+
&& trace.routeKind === "default-provider"
|
|
13
|
+
&& trace.selected.reason === "default-provider"
|
|
14
|
+
&& trace.truncated?.strings !== true
|
|
15
|
+
&& trace.selected.model.length > 0
|
|
16
|
+
&& trace.requestedModel === trace.selected.model
|
|
17
|
+
&& target.model === trace.selected.model
|
|
18
|
+
&& baseProviderLabel(target.provider) === baseProviderLabel(trace.selected.provider);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Bare fallback rates remain eligible; unresolved slash IDs need a provider-specific rate. */
|
|
22
|
+
export function usageModelPriceOptions(source: TraceSource, target: ModelTarget): PriceResolutionOptions {
|
|
23
|
+
return {
|
|
24
|
+
allowModelLevelFallback: !target.model.includes("/") || !isUnresolvedRequestedModel(source, target),
|
|
25
|
+
};
|
|
26
|
+
}
|
package/src/usage/summary.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { baseProviderLabel } from "../providers/label";
|
|
2
2
|
import { canonicalAntigravityUsageModel } from "../providers/antigravity-models";
|
|
3
3
|
import { usageDisplayTotalTokens } from "./totals";
|
|
4
|
+
import { isUnresolvedRequestedModel, usageModelPriceOptions } from "./model-identity";
|
|
4
5
|
import { isCodexUsageAccountLogLabel, type PersistedUsageEntry, type UsageStatus } from "./log";
|
|
5
6
|
import { type AttemptCostEstimate, type CostEstimate, estimateAttemptCost, estimateRequestCost, serviceTierContext, type ServiceTierContext } from "./cost";
|
|
6
7
|
|
|
@@ -59,6 +60,8 @@ export interface UsageDay {
|
|
|
59
60
|
export interface UsageDayModel {
|
|
60
61
|
model: string;
|
|
61
62
|
provider: string;
|
|
63
|
+
/** Includes trace-proven unresolved requested selectors; absence is not confirmation. */
|
|
64
|
+
hasUnresolvedRequestedModel?: true;
|
|
62
65
|
requests: number;
|
|
63
66
|
attemptCount: number;
|
|
64
67
|
totalTokens: number;
|
|
@@ -73,6 +76,8 @@ export interface UsageDayModel {
|
|
|
73
76
|
export interface UsageModel {
|
|
74
77
|
provider: string;
|
|
75
78
|
model: string;
|
|
79
|
+
/** Includes trace-proven unresolved requested selectors; absence is not confirmation. */
|
|
80
|
+
hasUnresolvedRequestedModel?: true;
|
|
76
81
|
resolvedModel?: string;
|
|
77
82
|
requests: number;
|
|
78
83
|
attemptCount: number;
|
|
@@ -208,7 +213,7 @@ export function computeEntryCost(entry: PersistedUsageEntry): EntryCostInfo {
|
|
|
208
213
|
const tier = serviceTierContext(entry);
|
|
209
214
|
if (entry.attempts?.length) {
|
|
210
215
|
const attemptEstimates = entry.attempts.map(attempt =>
|
|
211
|
-
estimateAttemptCost(attempt, undefined, tier)
|
|
216
|
+
estimateAttemptCost({ ...attempt, ...usageModelPriceOptions(entry, attempt) }, undefined, tier)
|
|
212
217
|
);
|
|
213
218
|
let costTotal = 0;
|
|
214
219
|
let isPriced = false;
|
|
@@ -221,6 +226,7 @@ export function computeEntryCost(entry: PersistedUsageEntry): EntryCostInfo {
|
|
|
221
226
|
return { tier, estimate: null, attemptEstimates, costTotal, isPriced };
|
|
222
227
|
}
|
|
223
228
|
const estimate = estimateRequestCost({
|
|
229
|
+
...usageModelPriceOptions(entry, entry),
|
|
224
230
|
provider: entry.provider,
|
|
225
231
|
model: entry.model,
|
|
226
232
|
usage: entry.usage,
|
|
@@ -324,6 +330,7 @@ interface UsageAttribution {
|
|
|
324
330
|
provider: string;
|
|
325
331
|
model: string;
|
|
326
332
|
resolvedModel?: string;
|
|
333
|
+
hasUnresolvedRequestedModel?: true;
|
|
327
334
|
accountLogLabel?: string;
|
|
328
335
|
usageStatus: UsageStatus;
|
|
329
336
|
usage?: PersistedUsageEntry["usage"];
|
|
@@ -366,6 +373,7 @@ function usageAttributions(entry: PersistedUsageEntry): UsageAttribution[] {
|
|
|
366
373
|
requestId: entry.requestId,
|
|
367
374
|
provider: entry.provider,
|
|
368
375
|
...usageModelIdentity(entry.provider, entry.model, entry.resolvedModel),
|
|
376
|
+
...(isUnresolvedRequestedModel(entry, entry) ? { hasUnresolvedRequestedModel: true as const } : {}),
|
|
369
377
|
...(entry.accountLogLabel ? { accountLogLabel: entry.accountLogLabel } : {}),
|
|
370
378
|
usageStatus: entry.usageStatus,
|
|
371
379
|
...(entry.usage ? { usage: entry.usage } : {}),
|
|
@@ -376,6 +384,7 @@ function usageAttributions(entry: PersistedUsageEntry): UsageAttribution[] {
|
|
|
376
384
|
requestId: entry.requestId,
|
|
377
385
|
provider: attempt.provider,
|
|
378
386
|
...usageModelIdentity(attempt.provider, attempt.model),
|
|
387
|
+
...(isUnresolvedRequestedModel(entry, attempt) ? { hasUnresolvedRequestedModel: true as const } : {}),
|
|
379
388
|
...(attempt.accountLogLabel ? { accountLogLabel: attempt.accountLogLabel } : {}),
|
|
380
389
|
usageStatus: attempt.usageStatus,
|
|
381
390
|
...(attempt.usage ? { usage: attempt.usage } : {}),
|
|
@@ -517,6 +526,7 @@ interface UsageModelAccumulator {
|
|
|
517
526
|
provider: string;
|
|
518
527
|
model: string;
|
|
519
528
|
resolvedModel?: string;
|
|
529
|
+
hasUnresolvedRequestedModel?: true;
|
|
520
530
|
firstSeen: number;
|
|
521
531
|
attemptCount: number;
|
|
522
532
|
dayTotalTokens: number;
|
|
@@ -704,6 +714,7 @@ function cloneModelAccumulator(source: UsageModelAccumulator): UsageModelAccumul
|
|
|
704
714
|
}
|
|
705
715
|
|
|
706
716
|
function mergeModelAccumulator(target: UsageModelAccumulator, source: UsageModelAccumulator): void {
|
|
717
|
+
if (source.hasUnresolvedRequestedModel) target.hasUnresolvedRequestedModel = true;
|
|
707
718
|
if (source.firstSeen < target.firstSeen) {
|
|
708
719
|
target.firstSeen = source.firstSeen;
|
|
709
720
|
target.resolvedModel = source.resolvedModel;
|
|
@@ -874,6 +885,7 @@ function buildDayModels(
|
|
|
874
885
|
return retainedModelAccumulators(sorted, overlaps).map(model => ({
|
|
875
886
|
model: model.model,
|
|
876
887
|
provider: model.provider,
|
|
888
|
+
...(model.hasUnresolvedRequestedModel ? { hasUnresolvedRequestedModel: true as const } : {}),
|
|
877
889
|
requests: requestCountsFor(model).requests,
|
|
878
890
|
attemptCount: model.attemptCount,
|
|
879
891
|
totalTokens: model.dayTotalTokens,
|
|
@@ -900,6 +912,7 @@ function buildUsageModels(
|
|
|
900
912
|
return {
|
|
901
913
|
provider: model.provider,
|
|
902
914
|
model: model.model,
|
|
915
|
+
...(model.hasUnresolvedRequestedModel ? { hasUnresolvedRequestedModel: true as const } : {}),
|
|
903
916
|
...(model.resolvedModel ? { resolvedModel: model.resolvedModel } : {}),
|
|
904
917
|
requests,
|
|
905
918
|
attemptCount: model.attemptCount,
|
|
@@ -1115,6 +1128,7 @@ class StreamingUsageSummaryAccumulator implements UsageSummaryAccumulator {
|
|
|
1115
1128
|
attribution: UsageAttribution,
|
|
1116
1129
|
estimate: AttemptCostEstimate | CostEstimate | null,
|
|
1117
1130
|
): void {
|
|
1131
|
+
if (attribution.hasUnresolvedRequestedModel) breakdown.hasUnresolvedRequestedModel = true;
|
|
1118
1132
|
breakdown.attemptCount += 1;
|
|
1119
1133
|
if (attribution.usage) {
|
|
1120
1134
|
breakdown.inputTokens += attribution.usage.inputTokens;
|