Skip to content

(draft, not ready for review) feat: add conversation cost tracking - #8531

Closed
ConstantTime wants to merge 1 commit into
mainfrom
feat/conversation-cost-tracking
Closed

ConstantTime wants to merge 1 commit into
mainfrom
feat/conversation-cost-tracking

Conversation

@ConstantTime

@ConstantTime ConstantTime commented Jul 18, 2025 •

Copy link
Copy Markdown
Collaborator

Summary

Adds real-time cost tracking for conversations. Users can now see how much their conversations cost as messages stream, with automatic updates and color-coded indicators.

Features

  • Dynamic cost calculation from message history
  • 100+ model support across OpenAI, Anthropic, Google, and AWS Bedrock
  • Historical pricing ensures accurate costs based on when messages were sent
  • Color-coded display for quick cost assessment:
    • 🟢 Green: < $0.01
    • 🟡 Yellow: < $0.10
    • 🟠 Orange: < $1.00
    • 🔴 Red: > $1.00

Implementation

The cost display appears in the conversation header and updates automatically as new messages arrive. Hover over the cost to see detailed breakdown including model used, token count, and last update time.

Testing

  • 56 unit tests covering all pricing logic and edge cases
  • Tested with multiple models in single conversations
  • Verified historical pricing calculations

Change Type

  • New feature (non-breaking change which adds functionality)

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • Local unit tests pass with my changes (56 tests)
  • I have written tests demonstrating that my changes are effective

Comment thread api/server/services/ConversationCost.js Fixed
Comment thread api/server/services/ConversationCost.js Fixed
Comment thread api/server/services/ConversationCost.js Fixed
Comment thread api/server/services/ConversationCost.js Fixed
Comment thread api/server/services/ConversationCost.js Fixed
Comment thread client/src/components/Chat/ConversationCost.tsx Fixed
Comment thread client/src/components/Chat/ConversationCost.tsx Fixed
Comment thread client/src/components/Chat/ConversationCost.tsx Fixed
Comment thread client/src/components/Chat/ConversationCost.tsx Fixed
Comment thread client/src/data-provider/conversations.ts Fixed

@github-advanced-security github-advanced-security AI 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.

ESLint found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@ConstantTime ConstantTime changed the title feat: add conversation cost tracking (draft, not ready for review) feat: add conversation cost tracking Jul 24, 2025
@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch 3 times, most recently from e1e9fd3 to c5e7993 Compare July 30, 2025 09:26
@michnovka

Copy link
Copy Markdown
Contributor

@ConstantTime hi, how is it going? Are you stuck on ESLint, or are there some more issues you need help resolving?

@ConstantTime

Copy link
Copy Markdown
Collaborator Author

@ConstantTime hi, how is it going? Are you stuck on ESLint, or are there some more issues you need help resolving?

Hey @michnovka, sorry for the late response. Planning to wrap this up during the weekend and open up for review.

@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch from 5998d33 to e364282 Compare August 17, 2025 12:16
@github-actions

Copy link
Copy Markdown
Contributor

🚨 Unused i18next Keys Detected

The following translation keys are defined in translation.json but are not used in the codebase:

  • com_ui_conversation_cost
  • com_ui_last_updated
  • com_ui_primary_model
  • com_ui_total_tokens

⚠️ Please remove these unused keys to keep the translation files clean.

@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch 2 times, most recently from 78fa244 to a199930 Compare August 17, 2025 12:34
Comment thread api/server/services/__tests__/ConversationCostDynamic.test.js Fixed
@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch 2 times, most recently from a52c997 to 068e1ad Compare August 17, 2025 14:44
@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch from 068e1ad to 2c8df88 Compare August 24, 2025 17:46
…ting

- Add comprehensive ModelPricing service with 100+ models and historical pricing
- Create real-time ConversationCost component that displays in chat header
- Use actual token counts from model APIs instead of client-side estimation
- Fix BaseClient.js to preserve tokenCount in response messages
- Add tokenCount, usage, and tokens fields to message schema
- Update Header component to include ConversationCost display
- Support OpenAI, Anthropic, Google, and other major model providers
- Include color-coded cost display based on amount
- Add 32 unit tests for pricing calculation logic

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
@ConstantTime
ConstantTime force-pushed the feat/conversation-cost-tracking branch from 2c8df88 to 3edf6fd Compare August 24, 2025 17:46
// Persist usage metadata on the assistant message if available for accurate costing
if (this.getStreamUsage != null) {
const streamUsage = this.getStreamUsage();
if (streamUsage && (Number(streamUsage[this.inputTokensKey]) > 0 || Number(streamUsage[this.outputTokensKey]) > 0)) {

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Replace `streamUsage·&&·(Number(streamUsage[this.inputTokensKey])·>·0·||·Number(streamUsage[this.outputTokensKey])·>·0)` with `⏎········streamUsage·&&⏎········(Number(streamUsage[this.inputTokensKey])·>·0·||⏎··········Number(streamUsage[this.outputTokensKey])·>·0)⏎······`
};

if (message.usage) {
currentTokenUsage.promptTokens = message.usage.prompt_tokens || message.usage.input_tokens || 0;

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Insert `⏎·········`

if (message.usage) {
currentTokenUsage.promptTokens = message.usage.prompt_tokens || message.usage.input_tokens || 0;
currentTokenUsage.completionTokens = message.usage.completion_tokens || message.usage.output_tokens || 0;

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Insert `⏎·········`
currentTokenUsage.cacheReadTokens = read;
} else if (message.tokens) {
currentTokenUsage.promptTokens = message.tokens.prompt || message.tokens.input || 0;
currentTokenUsage.completionTokens = message.tokens.completion || message.tokens.output || 0;

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Insert `⏎·········`
getMultipleConversationCosts,
};

const { calculateTokenCost, getModelProvider } = require('./ModelPricing');

Check failure

Code scanning / ESLint

Disallow variable redeclaration

'calculateTokenCost' is already defined.
* @returns {Array} returns.modelBreakdown - Per-model cost and usage
* @returns {Date} returns.lastUpdated - Timestamp of the last message
*/
function calculateConversationCostFromMessages(messages) {

Check failure

Code scanning / ESLint

Disallow variable redeclaration

'calculateConversationCostFromMessages' is already defined.
* @returns {number} returns.totalTokens - Total token count across all messages
* @returns {Date} returns.lastUpdated - Timestamp of the last message
*/
function getConversationCostDisplayFromMessages(messages) {

Check failure

Code scanning / ESLint

Disallow variable redeclaration

'getConversationCostDisplayFromMessages' is already defined.
* @param {string} userId - User ID
* @returns {Object} Map of conversationId to cost display data
*/
async function getMultipleConversationCosts(conversationIds, userId) {

Check failure

Code scanning / ESLint

Disallow variable redeclaration

'getMultipleConversationCosts' is already defined.

if (!data || data.totalCostRaw === 0) {
return (
<div className="flex items-center gap-1 rounded-md px-2 py-1 text-xs text-gray-400" title={t('com_ui_conversation_cost')}>

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Replace `·className="flex·items-center·gap-1·rounded-md·px-2·py-1·text-xs·text-gray-400"·title={t('com_ui_conversation_cost')}` with `⏎········className="flex·items-center·gap-1·rounded-md·px-2·py-1·text-xs·text-gray-400"⏎········title={t('com_ui_conversation_cost')}⏎······`
const tooltipText = `${t('com_ui_conversation_cost')}: ${data.totalCost} | ${t('com_ui_primary_model')}: ${data.primaryModel} | ${t('com_ui_total_tokens')}: ${data.totalTokens.toLocaleString()} | ${t('com_ui_last_updated')}: ${new Date(data.lastUpdated).toLocaleTimeString()}`;

return (
<div className="flex items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors hover:bg-surface-hover" title={tooltipText}>

Check failure

Code scanning / ESLint

Ensure code is properly formatted, use insertion, deletion, or replacement to obtain desired formatting.

Replace `·className="flex·items-center·gap-1·rounded-md·px-2·py-1·text-xs·transition-colors·hover:bg-surface-hover"·title={tooltipText}` with `⏎······className="flex·items-center·gap-1·rounded-md·px-2·py-1·text-xs·transition-colors·hover:bg-surface-hover"⏎······title={tooltipText}⏎····`
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.

4 participants