Skip to content

fix(usage): price GPT-6 Astra, and declare when the Codex cost total omits a model - #22073

Merged
Jinwoo-H merged 3 commits into
mainfrom
fix/codex-astra-pricing
Sep 21, 2026
Merged

Jinwoo-H merged 3 commits into
mainfrom
fix/codex-astra-pricing

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

Orca's Stats & Usage page shows roughly what your Codex tokens would have cost at API rates. It works from a table of per-model prices. GPT-6 Astra was not in that table, so all of its tokens were quietly thrown away before the total was added up — hundreds of millions of tokens could show as a few dollars, and nothing on screen said anything was missing.

This adds Astra's prices, and makes the card admit when it has dropped something.

What Changed

Before. A user on GPT-6 Astra opened Stats & Usage, saw "Est. API-equivalent cost" with a small, confident dollar figure, and had no way to tell it was wrong.

After. Astra usage is priced at the published rates. And if any model in the range still has no price entry, the Codex card's label reads "Est. API-equivalent cost • excludes unpriced models", so the number no longer claims to be the whole bill. The Overview tab, which prints a combined total across providers, now raises its existing "- some model prices are unavailable" line for the same reason.

Two commits:

  1. Pricing entry (authored by @Alfred212121, cherry-picked from feat(usage): price GPT-6 Astra token usage #22006). Adds gpt-6-astra to MODEL_PRICING and a matching branch in normalizeModelForPricing so reasoning suffixes and dated snapshot IDs (gpt-6-astra-high, gpt-6-astra-2026-09-01) price correctly. Rates are $10 input / $1 cached input / $50 output per million, with the existing above-272K-token tier fields carrying the 2x input, 2x cache, 1.5x output long-context prices.

  2. The caveat. The mechanism behind the silent zero is in buildSummary: for each day-row it asks estimateCostUsd for a cost, and when that returns null it just skips the row. A single flag, hasAnyBillableCost, decides whether the total is shown at all — so one priced model anywhere in the range is enough to print a number that omits every unpriced one. The summary now also carries hasUnpricedModels, set when a row has a model name but no price, and the pane appends the caveat to the cost card's label.

  3. Review fixups. The Overview tab had the same blind spot from the other side: its hasPartialCost only noticed a provider whose entire cost was null, so a Codex range with one unpriced model among priced ones kept a real number and the warning line stayed hidden. Each provider's overview entry now carries its own hasPartialCost, and the reduction ORs it in — no new string. Also, the Codex card could read "n/a • excludes unpriced models" when nothing at all was priced; "excludes" promises a remainder, so the suffix now requires a non-null total. That all-unpriced case is still declared on the Overview, through the null-cost path.

Why

Adding the Astra entry alone fixes today's wrong number and leaves the failure class intact: the next model OpenAI ships lands in exactly the same hole, and again nobody finds out from the UI. The first commit is the fix; the second is the reason it won't need to be found by a user twice.

Alternatives considered:

  • Reuse hasInferredPricing. Rejected. It fires only when a rollout has no model name at all, and "inferred pricing" describes a guess, not an omission — it would mislabel what happened.
  • Suppress the total when anything is unpriced. Rejected. A partial figure is still useful, and hiding it would regress users who have one stray unpriced model among many priced ones.
  • A warning banner or icon. Rejected as more surface than the fact deserves. The bullet-suffix idiom (• excludes unpriced models) is the one the breakdown rows already use for • inferred pricing, in the same muted style, so the caveat reads as part of the existing vocabulary rather than a new alarm.

Scope stayed narrow on purpose: one boolean on CodexUsageSummary, one place that sets it, one place that reads it. Claude, Grok and OpenCode have their own summary types and are untouched.

Linked Issue

Fixes #22005

Visual Proof

The only visual change is the text of one stat card's label, and it is asserted directly rather than screenshotted. src/renderer/src/components/stats/CodexUsagePane.test.tsx renders the real pane against a summary and checks both states:

  • with an unpriced model alongside priced ones: the card reads Est. API-equivalent cost • excludes unpriced models, with the value $12.50 still shown
  • with everything priced: the card reads Est. API-equivalent cost, and no "excludes unpriced models" text is in the document
  • with nothing priced: the card reads n/a under the plain label, with no suffix

The first case fails if the caveat is removed from the pane; the third fails if the non-null-total gate is removed. The second is the control and passes either way. No screenshot was captured, because doing so would have meant launching a visible app window.

Testing

Run from the worktree with the repo's own binaries:

vitest run --config config/vitest.config.ts src/main/codex-usage src/renderer/src/components/stats
   -> 17 files, 93 tests, all passing

tsc --noEmit -p config/tsconfig.node.json --composite false   -> clean
tsc --noEmit -p config/tsconfig.web.json  --composite false   -> clean
oxlint <changed dirs>                                          -> clean
oxlint --config config/oxlint-anti-slop.json <changed dirs> --deny-warnings -> clean
oxfmt --check <changed dirs>                                   -> clean
node config/scripts/check-changed-code-quality.mjs origin/main -> 0 new findings across 11 files
node config/scripts/verify-localization-catalog.mjs            -> clean
node config/scripts/verify-localization-extraction.mjs         -> clean
node config/scripts/audit-localization-coverage.mjs --check    -> clean
node config/scripts/check-max-lines-ratchet.mjs                -> clean
node config/scripts/check-reliability-gates.mjs                -> clean

New tests, each confirmed to fail against the unfixed code:

  • store-model-pricing.test.ts — a named model with no price entry sets hasUnpricedModels and its ten million tokens are absent from the total beside it; a fully priced summary, and one containing a row with no model name at all, both leave the flag off. (The no-model-name case is the boundary that separates this flag from hasInferredPricing.)
  • store-model-pricing.test.ts (from feat(usage): price GPT-6 Astra token usage #22006) — Astra long-context pricing, and normalization of reasoning suffixes and snapshot IDs.
  • CodexUsagePane.test.tsx — the rendered caveat, in all three states described above.
  • usage-overview-model.test.ts — a Codex provider with a real cost and hasUnpricedModels true makes the Overview's hasPartialCost true, and false leaves it false. Confirmed to fail before the Overview fix.

Platform: macOS. The change is pure TypeScript with no platform-dependent behavior, no process spawning, and no filesystem or path handling. The Codex usage summary does not cross the remote or mobile wire — it travels only over local Electron IPC between the main process and the renderer, which ship together — so there is no mixed-version concern.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: none — no new inputs, no I/O. Cross-platform: no platform-dependent code. Remote SSH and mobile: neither consumes CodexUsageSummary; it crosses local Electron IPC only, so no wire contract changes. Backwards compatibility: adding a model to the pricing table changes only estimates for that model, which were previously being dropped entirely. Performance: one boolean assignment inside a loop that already computes the cost.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Alfred212121 and others added 2 commits September 21, 2026 16:32
gpt-6-astra was missing from MODEL_PRICING, so normalizeModelForPricing
returned null and estimateCostUsd dropped every event on that model from
the total. Stats & Usage showed ~$0 for hundreds of millions of tokens
with no unpriced indicator, since hasInferredPricing only covers a
missing model name, not a missing table entry.

Rates are the published ones: $10 input / $1 cached input / $50 output
per 1M, with the >272K long-context tier at 2x input and cache and 1.5x
output, which the existing tier fields already express.

Fixes #22005

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A daily row whose model has no `MODEL_PRICING` entry gets a null cost, and
`buildSummary` simply skips it. As long as one other row is priced,
`hasAnyBillableCost` is true, so the Codex card prints a confident dollar
figure that silently leaves those tokens out. That is how GPT-6 Astra usage
read as near-$0 before the entry landed, and it is how the next unpriced
model will read too.

`hasInferredPricing` does not cover this: it only fires when a rollout has no
model name at all, and its label ("inferred pricing") describes a guess, not
an omission.

So the summary now carries `hasUnpricedModels`, set when a row has a model
name and no price, and the estimated-cost card appends
"• excludes unpriced models" — the same bullet-suffix idiom the breakdown
rows already use for "• inferred pricing". The number stays; it stops
claiming to be the whole bill.
@coderabbitai

coderabbitai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

Understand this PR’s impact

Explore downstream dependencies and potential security impact with Blast Radius.

View blast radius →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 5a5277d0-f2e0-4e54-8328-30c66df72367

📥 Commits

Reviewing files that changed from the base of the PR and between 1d85f1c and 4910fda.

📒 Files selected for processing (6)
  • src/renderer/src/components/stats/CodexUsagePane.test.tsx
  • src/renderer/src/components/stats/CodexUsagePane.tsx
  • src/renderer/src/components/stats/usage-overview-model.test.ts
  • src/renderer/src/components/stats/usage-overview-model.ts
  • src/renderer/src/components/stats/usage-overview-types.ts
  • src/renderer/src/components/stats/usage-provider-normalization.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/components/stats/CodexUsagePane.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The change adds GPT-6 Astra pricing and normalization for suffixed model names. Codex usage summaries now flag named models without pricing entries while excluding their tokens from estimated cost totals. The partial-cost state propagates to provider overviews. The Codex cost card displays an exclusion notice when a cost exists. Tests, translations, and summary fixtures cover the updated behavior.

Priority: ➖ Normal

Severity of issue fixed: Medium

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 11 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies the coding requirements in issue [#22005]. It adds gpt-6-astra rates of $10 input, $1 cached input, and $50 output per million tokens. It adds long-context rates of $20, $2, and $75…
Out of Scope Changes check ✅ Passed The changed source files, types, locale string, tests, and fixtures support issue [#22005]. The Overview propagation is required to display the partial Codex estimate in the combined total. No unrelat…
Title check ✅ Passed The title clearly and concisely describes both primary changes: GPT-6 Astra pricing and disclosure of omitted Codex models.
Description check ✅ Passed The description is detailed and covers the required explanation, user-visible behavior, rationale, linked issues, testing, platform impact, and checklist. The visual proof section explains why screens…
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 17f727ab-6962-4511-a703-ea474997a57f

📥 Commits

Reviewing files that changed from the base of the PR and between d40aac0 and 1d85f1c.

📒 Files selected for processing (9)
  • src/main/codex-usage/codex-model-pricing.ts
  • src/main/codex-usage/codex-usage-rollup-projections.ts
  • src/main/codex-usage/store-model-pricing.test.ts
  • src/renderer/src/components/stats/CodexUsagePane.test.tsx
  • src/renderer/src/components/stats/CodexUsagePane.tsx
  • src/renderer/src/components/stats/usage-overview-model.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/slices/usage-snapshot-refresh.benchmark.test.ts
  • src/shared/codex-usage-types.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment on lines +85 to +87
inputTiers: [{ threshold: LONG_CONTEXT_THRESHOLD_TOKENS, price: 20 }],
cachedInputTiers: [{ threshold: LONG_CONTEXT_THRESHOLD_TOKENS, price: 2 }],
outputTiers: [{ threshold: LONG_CONTEXT_THRESHOLD_TOKENS, price: 75 }]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

sed -n '1,220p' src/main/codex-usage/codex-model-pricing.ts
sed -n '1,120p' src/main/codex-usage/codex-usage-cost-estimate.ts
sed -n '1,110p' src/main/codex-usage/codex-usage-rollup-projections.ts
sed -n '200,280p' src/main/codex-usage/store-model-pricing.test.ts
rg -n 'LONG_CONTEXT_THRESHOLD|inputTiers|cachedInputTiers|outputTiers|estimateCostUsd' src/main/codex-usage

Repository: stablyai/orca

Length of output: 18977


🌐 Web query:

OpenAI GPT-6 Astra pricing long context over 272K input tokens full request rates

💡 Result:

<source_evidence>

<title>GPT-6 Astra Model | OpenAI API</title> https://developers.openai.com/api/docs/models/gpt-6-astra GPT-6 Astra Model | OpenAI API Price $10•$50 Output Text GPT-6 Astra is our most capable model, built for the hardest end-to-end work. Use it for complex reasoning, coding, computer use, research, and document creation. `reasoning.effort` supports `low`, `medium`, `high`, `xhigh`, and `max`. Get started with GPT-6 Astra using the model guide. 1,050,000 context window 128,000 max output tokens Apr 30, 2026 knowledge cutoff Reasoning token support Pricing Pricing is based on the number of tokens used, or other metrics based on the model type. For tool-specific models, like search and computer use, there’s a fee per tool call. See details in the pricing page. Text tokens Per 1M tokens Input $10.00 Cached input $1.00 Cache writes $12.50 Output $50.00 Prompts with more than 272K input tokens are priced at 2x input and cache rates and 1.5x output for the full request. Cache writes are billed at 1.25x the uncached input token rate. Batch and Flex are priced at 50% of Standard rates. Fast mode is priced at 2x the applicable rates. Modalities Text Input and output Image Input only Audio Not supported Video Not supported Endpoints Live v1/live/sessions Chat Completions v1/chat/completions Responses v1/responses Realtime v1/realtime Realtime translation v1/realtime/translations Realtime transcription v1/realtime/transcription_sessions Assistants v1/assistants Batch v1/batch Fine-tuning v1/fine-tuning Embeddings v1/embeddings Image generation v1/images/generations Videos v1/videos Image edit v1/images/edits Speech generation v1/audio/speech Transcription v1/audio/transcriptions Translation v1/audio/translations Moderation v1/moderations Completions (legacy) v1/completions Features Streaming Supported Function calling Supported Structured outputs Supported Fine-tuning Not supported Tools Tools supported by this model when using the Responses API. Web search Supported File search Supported Image generation Supported Code interpreter Supported Hosted shell Supported Apply patch Supported Skills Supported Computer use Supported MCP Supported Tool search Supported Snapshots Snapshots let you lock in a specific version of the model so that performance and behavior remain consistent. Below is a list of all available snapshots and aliases for GPT-6 Astra. gpt-6-astra gpt-6-astra gpt-6-astra Rate limits Rate limits ensure fair and reliable access to the API by placing specific caps on requests, tokens, audio duration, or other usage within a given time period. Your usage tier determines how high these limits are set and automatically increases as you send more requests and spend more on the API. | Tier | RPM | TPM | Batch queue limit | | --- | --- | --- | --- | | Free | Not supported | | | | Tier 1 | 500 | 500,000 | 1,500,000 | | Tier 2 | 5,000 | 1,000,000 | 3,000,000 | | Tier 3 | 5,000 | 2,000,000 | 100,000,000 | | Tier 4 | 10,000 | 4,000,000 | 200,000,000 | | Tier 5 | 15,000 | 40,000,000 | 15,000,000,000 | <title>Pricing | OpenAI API</title> https://developers.openai.com/api/docs/pricing | | Short context | | | | Long context | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Model | Input | Cached input | Cache writes | Output | Input | Cached input | Cache writes | Output | | gpt-6-astra | $10.00 | $1.00 | $12.50 | $50.00 | $20.00 | $2.00 | $25.00 | $75.00 | | gpt-5.6-sol | $4.00 | $0.40 | $5.00 | $20.00 | $8.00 | $0.80 | $10.00 | $30.00 | | gpt-5.6-terra | $2.00 | $0.20 | $2.50 | $12.00 | $4.00 | $0.40 | $5.00 | $18.00 | | gpt-5.6-luna | $0.20 | $0.02 | $0.25 | $1.20 | $0.40 | $0.04 | $0.50 | $1.80 | ... | | Short context | | | | Long context | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Model | Input | Cached input | Cache writes | Output | Input | Cached input | Cache writes | Output | | gpt-6-astra | $5.00 | $0.50 | $6.25 | $25.00 | $10.00 | $1.00 | $12.50 | $37.50 | | gpt-5.6-sol | $2.00 | $0.20 | $2.50 | $10.00 | $4.00 | $0.40 | $5.00 | $15.00 | | gpt-5.6-terra | $1.00 | $0.10 | $1.25 | $6.00 | $2.00 | $0.20 | $2.50 | $9.00 | | gpt-5.6-luna | $0.10 | $0.01 | $0.125 | $0.60 | $0.20 | $0.02 | $0.25 | $0.90 | ... | | Short context | | | | Long context | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Model | Input | Cached input | Cache writes | Output | Input | Cached input | Cache writes | Output | | gpt-6-astra | $5.00 | $0.50 | $6.25 | $25.00 | $10.00 | $1.00 | $12.50 | $37.50 | | gpt-5.6-sol | $2.00 | $0.20 | $2.50 | $10.00 | $4.00 | $0.40 | $5.00 | $15.00 | | gpt-5.6-terra | $1.00 | $0.10 | $1.25 | $6.00 | $2.00 | $0.20 | $2.50 | $9.00 | | gpt-5.6-luna | $0.10 | $0.01 | $0.125 | $0.60 | $0.20 | $0.02 | $0.25 | $0.90 | ... | | Short context | | | | Long context | | | | | --- | --- | --- | --- | --- | --- | --- | --- | --- | | Model | Input | Cached input | Cache writes | Output | Input | Cached input | Cache writes | Output | | gpt-6-astra | $20.00 | $2.00 | $25.00 | $100.00 | $40.00 | $4.00 | $50.00 | $150.00 | | gpt-5.6-sol | $8.00 | $0.80 | $10.00 | $40.00 | $16.00 | $1.60 | $20.00 | $60.00 | | gpt-5.6-terra | $4.00 | $0.40 | $5.00 | $24.00 | $8.00 | $0.80 | $10.00 | $36.00 | | gpt-5.6-luna | $0.40 | $0.04 | $0.50 | $2.40 | $0.80 | $0.08 | $1.00 | $3.60 | ... Fast mode is unavailable for GPT-6 Astra with EU data residency. Use Standard processing for those requests. See Fast mode compatibility. Regional processing (data residency) endpoints are charged a 10% uplift for models released on or after March 5, 2026, that are eligible for data residency. See our Your data guide for supported regions and processing details. <title>ChatGPT Rate Card (Enterprise token-based pricing) | OpenAI Help Center</title> https://help.openai.com/en/articles/20001415-chatgpt-rate-card-enterprise-token-based-pricing | Model | Input | Cached input | Output | | --- | --- | --- | --- | | GPT-6 Astra | $10.00 | $1.00 | $50.00 | | GPT-5.6 Sol | $4.00 | $0.40 | $20.00 | | GPT-5.6 Terra | $2.00 | $0.20 | $12.00 | | GPT-5.6 Luna | $0.20 | $0.02 | $1.20 | | GPT-5.5 | $5.00 | $0.50 | $30.00 | | Daybreak Blue | $4.00 | $0.40 | $20.00 | | Daybreak Red | $12.50 | $1.25 | $75.00 | | GPT-5.4 | $2.50 | $0.25 | $15.00 | | GPT-5.4-Mini | $0.75 | $0.075 | $4.50 | | GPT-5.3-Codex | $1.75 | $0.175 | $14.00 | | GPT-5.2 | $1.75 | $0.175 | $14.00 | | GPT-5.3-Codex-Spark | Research preview | Research preview | Research preview | ... These rates apply to supported ChatGPT Work and Codex activity, including local tasks, cloud tasks, automations, code review, auto review, and delegated workers. Charges are based on the model used and the actual input, cached input, and output tokens consumed. Image generation, Voice, web search, long context, fast mode, and regional processing may create separate or additional charges as described below. ... # Additional fees and multipliers ... The following fees and multipliers may apply in addition to the standard model rate. | Feature | Rate | | --- | --- | | Long context>272K input tokens | Input: 2x Standard rate Cached input: 2x Standard rate Output: 1.5x Standard rate | | Fast mode Codex and Work modes only; see Speed for more details. | GPT-6 Astra: 2.5× Standard rate. GPT-5.6: 2.5x Standard rate GPT-5.5: 2.5x Standard rate GPT-5.4: 2x Standard rate | | Regional processing (data residency) See Your data guide for supported regions and processing details. | 1.1x Standard rate | ... - Long context is only supported in GPT-5.6, GPT-5.5, and GPT-5.4 (Work and Codex tabs). Long context is not available in the Chat tab. ... ## GPT-6 Astra — Codex long-context exception ... GPT-6 Astra usage in Codex does not incur additional long-context multipliers above 272K input tokens. Codex does not charge for cache writes. <title>This page couldn’t load</title> https://openai.com/api/ # This page couldn’t load Reload to try again, or go back. ReloadBack # This page couldn’t load Reload to try again, or go back. ReloadBack

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- candidate files ---'
git ls-files 'src/main/codex-usage/*' | sed -n '1,120p'
printf '%s\n' '--- persisted types and aggregation symbols ---'
rg -n -C 4 'dailyAggregates|CodexUsageDaily|eventCount|inputTokens|cachedInputTokens|outputTokens|usage' src/main/codex-usage \
  -g '*.ts' | sed -n '1,260p'
printf '%s\n' '--- rollup projection cost paths ---'
sed -n '130,215p' src/main/codex-usage/codex-usage-rollup-projections.ts
printf '%s\n' '--- automation attribution cost paths ---'
sed -n '90,155p' src/main/codex-usage/codex-automation-run-attribution.ts

Repository: stablyai/orca

Length of output: 27403


Apply the long-context rate to each complete request.

GPT-6 Astra applies the higher rates to the full request when input exceeds 272K tokens. The current progressive tiers calculate $87.208 for the 2M-input, 1M-cached, 1M-output fixture, but one qualifying request should cost $97.

The rollup and automation paths pass aggregated token counts to estimateCostUsd, so they cannot determine which individual requests exceeded the threshold. Preserve per-request threshold data and sum request costs. If that data is unavailable, do not apply the long-context surcharge to the aggregate.

…exists

Review of #22073 found the Codex caveat stopped at the Codex tab. The
Overview tab prints a combined total across providers and already has a
"- some model prices are unavailable" line, but `hasPartialCost` only
noticed a provider whose whole cost was null. A Codex range with one
unpriced model among priced ones kept a real number, so the line stayed
hidden and the Overview repeated the same confident, incomplete figure.
`UsageProviderOverview` now carries `hasPartialCost` — set from
`hasUnpricedModels` for Codex, false for the providers that cannot yet
report it — and the reduction ORs it in. No new string.

Second, the Codex card could read "n/a • excludes unpriced models" when
nothing at all was priced. "Excludes" promises a remainder, and there was
none. The suffix now also requires a non-null total; that case is still
declared, on the Overview, through the null-cost path.

`unpricedCostLabel` becomes `costCardLabel`, since it holds the plain
label whenever there is nothing to qualify.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — the Astra pricing and the hasUnpricedModels flag are correct. One incomplete-fix gap: the same partial total still renders unqualified on the Overview and share surfaces.

Reviewed changes

  • GPT-6 Astra pricing — adds gpt-6-astra to MODEL_PRICING at $10/$1/$50 per M with 2×/2×/1.5× long-context tiers above 272K, plus a normalizeModelForPricing branch so bare, reasoning-suffixed (-high, (high)) and dated-snapshot IDs resolve to it. The tiered arithmetic and the two new expected values (87.208, 1.8) check out, and no other model's normalization changes.
  • Unpriced-model caveat — buildSummary sets hasUnpricedModels when a daily row carries a model name but estimateCostUsd returns null, and CodexUsagePane appends • excludes unpriced models to the cost card label. Rows with no model name are deliberately excluded, keeping the flag disjoint from hasInferredPricing.

ℹ️ Partial Codex totals stay unqualified on the Overview and share surfaces

The new caveat is read only by CodexUsagePane. The Usage Overview's combined Est. cost and per-provider cost, and the share card/share text, still present a mixed priced/unpriced Codex total as if it were complete. The overview's existing hasPartialCost caveat cannot cover this: it fires only when a provider has no priced model, not when one named model among priced ones was dropped.

Technical details
# Propagate `hasUnpricedModels` to the remaining cost surfaces

## Affected sites
- `src/renderer/src/components/stats/usage-provider-normalization.ts:75` — `createCodexProvider` copies `estimatedCostUsd` but never reads `hasUnpricedModels`; `UsageProviderOverview` (`usage-overview-types.ts:19-39`) has no field for it.
- `src/renderer/src/components/stats/usage-overview-model.ts:36-38` — `hasPartialCost` is `provider.hasData && provider.estimatedCostUsd === null`, so a non-null partial cost never trips it.
- `src/renderer/src/components/stats/UsageOverviewPane.tsx:206` — combined total; caveat at `:111-116` is driven by `hasPartialCost`.
- `src/renderer/src/components/stats/usage-overview-sections.tsx:253` — per-provider row renders `formatUsageCost(provider.estimatedCostUsd)` with no qualifier.
- `src/renderer/src/components/stats/ShareUsageCard.tsx:149` and `src/renderer/src/components/stats/ShareUsageButton.tsx:94-111` — receive the full `CodexUsageSummary` (`share-card-utils.tsx:16`) but do not read the flag.

## Required outcome
A Codex total that omits a named-but-unpriced model is disclosed wherever it is shown, not only on the Codex pane. The overview already has the wording (`UsageOverviewPane` "some model prices are unavailable"); it needs the mixed case to set it.

## Suggested approach (optional)
Add `hasUnpricedModels` (or a provider-level `hasPartialCost`) to `UsageProviderOverview`, set it from the Codex summary in `createCodexProvider`, and OR it into the overview's `hasPartialCost` at `usage-overview-model.ts:36`. For the share surfaces, gate the `Est. cost` value on the same flag.

## Open questions for the human (optional)
Is the Overview/share disclosure in scope for this PR, or tracked as a deliberate follow-up? If follow-up, a comment on the new field noting the untouched surfaces would keep the gap discoverable.

ℹ️ Nitpicks

  • Confirm whether a bare gpt-6 alias exists. gpt-5.6 is mapped to Sol at codex-model-pricing.ts:185-190; if OpenAI routes gpt-6 to Astra the same way, it currently normalizes to null and is flagged unpriced rather than priced.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ The Overview fixup is correct. One surface from the earlier review remains: the share card and share text still render a partial Codex total unqualified.

Reviewed changes (since 1d85f1c)

  • Provider-level partial-cost signal — UsageProviderOverview gains hasPartialCost, set from summary.hasUnpricedModels in createCodexProvider and hardcoded false for Claude/OpenCode; the aggregate hasPartialCost ORs it in (usage-overview-model.ts:36-39), so the Overview's existing "some model prices are unavailable" line now fires when Codex priced some models but not all. A test asserts both directions against a real non-null cost.
  • All-unpriced guard — the pane's • excludes unpriced models suffix now also requires estimatedCostUsd !== null, so an all-unpriced range shows the plain label with n/a; a third pane test pins that state.

ℹ️ Nitpicks

  • The share surfaces still present a partial Codex cost as complete: ShareUsageCard.tsx:149 (StatsGrid receives only estimatedCostUsd) and ShareUsageButton.tsx:94-111 both hold the full CodexUsageSummary but never read hasUnpricedModels. If share disclosure is a deliberate follow-up, a note on the new field would keep the gap discoverable.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@Jinwoo-H
Jinwoo-H merged commit 297cfe0 into main Sep 21, 2026
29 checks passed
sj900211 added a commit to sj900211/orcinus that referenced this pull request Oct 12, 2026
The stage the staged plan existed for. Measured from the post-stage-2 main this
cost 78 commits and 14 conflicts (13 in the PTY delivery subsystem) instead of
881 commits and 32 conflicts from the original base.

**The PTY wall was not what the 월드맵 feared.** It recorded
`pty-project-window-stream-routing` as a ~1,500-line feature-scale
re-implantation and deferred it twice. In fact all 14 conflicts come from three
upstream commits:

- `9700030689` refactor(pty): make renderer delivery optional for headless
  runtimes (stablyai#23117) — `session.mainWindow` becomes optional and every
  `isDestroyed()` guard gains a null check.
- `82412dab8b` Persist profile state in SQLite with background writes (stablyai#22612).
- `d443320af2` refactor(native-chat): remove the unused terminal handoff (stablyai#22783).

On the same lines the fork had already replaced the main-window guard with
owner-window routing (`resolvePtyOwnerWindow`, `sendToPtyOwner`,
`ownerFlowStateForPty`). The fork's guard is strictly finer: in headless mode no
window is registered with the router, the owner resolves to null, and the fork
takes the same early-return upstream's null check adds — while upstream's coarser
guard would drop or misroute project-window delivery when the main window is
gone. So most of the cluster resolves to ours, and nothing had to be reimplanted.

Conflicts resolved (14)
- `pty/delivery/{accept,accounting,exit,flush}.ts`, `pty/provider/bind-listeners.ts`:
  ours. Verified mechanically — resolving every hunk to ours produced a file
  byte-identical to main, so upstream has no other change in them.
- `pty/delivery/payload.ts`, `pty/runtime/operations.ts`: per hunk, never
  whole-file. A whole-file ours on operations.ts would have restored imports of
  two modules `d443320af2` deleted and a 3-arg `writePtyFromRuntimeController`
  while the merged `runtime/controller.ts` calls the 2-arg form.
- `pty/ipc/{resize-visibility,serialize-buffer,spawn-commit-persist,write-input,write}.ts`,
  `pty/register-handlers.ts`: combined, preserving every per-window router
  injection. A previous attempt at this merge failed its gates by dropping them.
- `persistence/loading-store/store.ts`: combined for `82412dab8b`'s SQLite
  authority.

Edits outside the 14 conflicted files (6)
Each one is a defect the conflict list could not show, because the file merged
cleanly while the meaning did not.
1. `pty/session.ts` + `pty/delivery/window-flow-state.ts`: `session.ts` merged
   cleanly into a crash. `mainWindow` is now optional, upstream's
   `register-headless-runtime.ts` passes `undefined` where it used to pass a fake
   destroyed window, and the fork's `createWindowFlowState(session,
   args.mainWindow.webContents)` therefore threw on every `orca serve` and orcad
   registration. Fixed by narrowing a flow state's `webContents` to
   `Pick<WebContents, 'id' | 'isDestroyed' | 'send'>` and passing a destroyed
   stand-in, with no cast. `window-flow-state.ts` stays at 297 of its 300
   permitted code lines.
2. `persistence/loading-store/store-domain-composition.ts`: added
   `SatelliteWindowSessionPersistence &` to `StoreDomainOperations`, without
   which four satellite-session methods disappear from the `Store` type.
3. `pty/register-without-renderer.test.ts`: two `registerPtyHandlers(renderer as
   never, …)` casts, matching the shape 274 existing calls in this repo use.
   This is an upstream-owned file, so the next sync touching it will conflict here.
4. Three usage test fixtures (`usage-custom-range-filters.test.ts`,
   `UsageNumberFormatToggle.test.tsx`, `usage-pane-test-state.ts`): added the
   fields upstream `297cfe0cf3` (stablyai#22073) made required. **These were already
   broken on main**: a fresh worktree at `e0686816e8` reports exactly these 4
   type errors. Stage 2's gate missed them because a warm `composite`
   `.tsbuildinfo` cache under-reported. Typecheck cold before trusting a pass.
5. `pty/register-handlers.ts` + the window-affinity router:
   `setMainWindowForRouting` widened to `BrowserWindow | null` and called
   unconditionally as `setMainWindowForRouting(mainWindow ?? null)`. Skipping it
   when `mainWindow` is undefined left a previously routed live window routed, so
   a desktop → headless re-registration would send `pty:exit`, `pty:spawned`,
   `pty:writeUnavailable`, `pty:clearBuffer:request`,
   `pty:serializeBuffer:request` and `pty:modelRestoreNeeded` to a stale window.
   Current call order prevents it in production; that is not a guarantee worth
   depending on. Restores parity with main.

Gates (each captured from the command's own exit code)
- typecheck, run cold after deleting `config/*.tsbuildinfo`: 0 errors. The same
  command on a main worktree reports its 4 pre-existing errors.
- oxlint: exit 0 on the full tree and on the touched files.
- verify-orcinus-identity: OK, upstreamRefs=0.
- electron-builder smoke: unsigned, update signature checks disabled.
- fork contract tests: 3 files / 24 tests.
- `register-without-renderer.test.ts` (upstream's new 231-line headless test): 4/4.
- router-referencing tests: 3 files / 70 tests.
- Broad conflict-area suite: 122 failures, none attributable. 121 fail
  identically on a main worktree; the remaining one, upstream's own
  `profile-state-crash-recovery.test.ts`, times out identically on a
  `da6d483ab9` worktree. Per-test JSON diff: 0 failures unique to main.

Reviewed by an independent critic: APPROVE, no blocking defects, every gate
re-measured, every fork-only and upstream-only file confirmed equal to its parent
apart from the six edits above, no IPC channel dropped. Non-blocking and
deliberately not addressed: `hasUnpricedModels: false` does not match its own
fixture's data (behaviour-neutral); the upstream-owned test file will re-conflict;
plan-B's satellite-session durability question is still open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Jinwoo-H added a commit that referenced this pull request Oct 18, 2026
…g unpriced models (#30421)

normalizeModelForPricing was an ordered chain of regexes in which a new point
release fell through to its major: claude-sonnet-5-5 was billed as Sonnet 5
(cache reads at $0.20 instead of $0.10), and any future opus-5-x / fable-5-x
would be too. claude-haiku-5-5 matched nothing and silently added $0 to a
total that showed no caveat.

The resolver now parses family + major + optional minor (family-first and
legacy version-first IDs) and only accepts an exact MODEL_PRICING key, so an
unknown version is unpriced rather than borrowing another model's rates. The
deliberate opus-4.x -> 4.8 and sonnet-4.x -> 4.6 fallbacks go with it.

Adds Sonnet 5.5 and Mythos 5/5.1 rows. Haiku 5.5 stays unpriced: it reprices a
whole request above 100K prompt tokens, which daily totals cannot express.

The Claude summary now carries hasUnpricedModels (as Codex has since #22073),
and both panes share one unpricedCostLabel helper for the caveat.

Fixes #30338
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Codex usage cost is ~$0 for gpt-6-astra — model missing from the pricing table

2 participants