OpenRouter actual model attribution, error-state cost reliability, and pre-send pricing UI#31
Conversation
…ty, and pre-send pricing UI Three features combined in one commit: 1. OpenRouter actual model + cost attribution - Add actual_model_id column to messages table (migration 145) - Persist actual routed model from OpenRouter generation API - Display actual model name with "via auto" badge in ToolsMessageView - Full data path: API -> DB -> ChatState -> MultiChat UI 2. Error-state cost reliability - Recompute and persist chat/project cost totals on error paths - Add cost invalidation in legacy onError and useStopMessageStreaming - Harden sidebar cost display with Number() coercion and Number.isFinite checks 3. Pre-send model pricing UI - Add formatPricePerMillion() utility to CostAPI - New ModelPricingDisplay component showing per-1M token pricing - Supports single-model and range display for multi-model compare - Integrated into ChatInput, respects showCost setting Made-with: Cursor
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Apply Prettier formatting fixes for ModelPricingDisplay and ToolsMessageView so lint/style checks pass. Made-with: Cursor
There was a problem hiding this comment.
Pull request overview
This PR improves cost tracking accuracy and model attribution (especially for OpenRouter auto-routing) and adds a pre-send UI affordance to show model pricing.
Changes:
- Persist and display OpenRouter “actual model” attribution via
actual_model_id/actualModelId, including a “via auto” indicator in message headers. - Harden cost reliability in error/stop streaming paths by recomputing chat/project totals and invalidating relevant queries; sidebar rendering now guards against NaN/non-numeric values.
- Add a
ModelPricingDisplayUI component to show input/output pricing per 1M tokens (single model or range) before sending, gated byshowCost.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src-tauri/src/migrations.rs |
Adds messages.actual_model_id column migration. |
src/core/chorus/ChatState.ts |
Extends Message with optional actualModelId. |
src/core/chorus/api/CostAPI.ts |
Returns actualModel from OpenRouter cost fetch; adds per-1M pricing formatter. |
src/core/chorus/api/MessageAPI.ts |
Persists actual_model_id during streaming; recomputes costs + invalidates queries on error/stop. |
src/ui/components/AppSidebar.tsx |
Makes sidebar cost display robust via numeric coercion and finite checks. |
src/ui/components/ChatInput.tsx |
Integrates pricing display into the send-row UI. |
src/ui/components/MultiChat.tsx |
Displays the “actual model” name and “via auto” badge in the message header. |
src/ui/components/ModelPricingDisplay.tsx |
New component rendering per-1M token pricing label (single or range). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Migration { | ||
| version: 145, | ||
| description: "add actual_model_id to messages", | ||
| kind: MigrationKind::Up, | ||
| sql: r#" | ||
| ALTER TABLE messages ADD COLUMN actual_model_id TEXT DEFAULT NULL; | ||
| "#, | ||
| }, |
There was a problem hiding this comment.
The migrations vector is not in ascending version order (e.g., version 144 appears before 143). Since tauri_plugin_sql::Builder::add_migrations(DB_URL, migrations) receives this list as-is (src-tauri/src/lib.rs:136-138), an out-of-order list can result in migrations being applied in the wrong sequence or skipped depending on the plugin's migration runner. Please reorder the migrations so they are strictly increasing and place this new version 145 migration after version 144.
There was a problem hiding this comment.
WARNING!!! PR #30 introduces a new migration as well. Fix this based on merge order
Drop the chat-input model pricing display to keep the compose area uncluttered while preserving existing post-send cost behavior. Made-with: Cursor
The OpenRouter generation endpoint (/api/v1/generation) was returning negative total_cost values, and the actual model wasn't being captured. OpenRouter includes both usage.cost and the model field directly in the streaming response. This commit: - Captures chunk.model from the streaming response in ProviderOpenRouter - Reads usage.cost (OpenRouter extension) from the final chunk - Adds model and cost fields to the UsageData type - Prefers response-level cost/model in MessageAPI, falling back to the generation endpoint only when the response doesn't include them - Guards against negative cost values Made-with: Cursor
…ames Made-with: Cursor
Summary
Three integrated features for cost tracking and model attribution:
actual_model_id) in the messages table, display the real model name in the message header with a "via auto" badge, and returnactualModelfrom the OpenRouter generation API.onError,useStopMessageStreaming) now recompute and persist chat/project cost totals and invalidate relevant queries. Sidebar cost display is hardened withNumber()coercion andNumber.isFinite()checks.ModelPricingDisplaycomponent shows per-1M token input/output pricing before sending. Supports single-model display and range display for multi-model compare mode. Respects theshowCostuser setting.Files changed
src-tauri/src/migrations.rsactual_model_idcolumnsrc/core/chorus/ChatState.tsactualModelIdfieldsrc/core/chorus/api/CostAPI.tsactualModelreturn + F3:formatPricePerMillion()src/core/chorus/api/MessageAPI.tssrc/ui/components/AppSidebar.tsxsrc/ui/components/ChatInput.tsxModelPricingDisplayintegrationsrc/ui/components/MultiChat.tsxsrc/ui/components/ModelPricingDisplay.tsxTest plan
openrouter/auto). Verify the message header shows the actual model name with a "via auto" badge instead of the generic model name.showCostenabled, open a chat with one model selected. Verify the input/output pricing per 1M tokens appears near the send button.showCostenabled and multiple models in compare mode, verify a price range is shown.showCostdisabled in settings, verify no pricing label appears.Made with Cursor