Skip to content

feat: track and display input/output tokens separately#1251

Open
shivamashtikar wants to merge 1 commit intomainfrom
test-input-tokens
Open

feat: track and display input/output tokens separately#1251
shivamashtikar wants to merge 1 commit intomainfrom
test-input-tokens

Conversation

@shivamashtikar
Copy link
Contributor

@shivamashtikar shivamashtikar commented Feb 11, 2026

Description

  • Add inputTokens and outputTokens columns to messages table
  • Update all insertMessage calls to store tokens separately
  • Update aggregation queries in sharedAgentUsage, admin, and message modules
  • Add input/output token display to Dashboard, AdminChatsTable components
  • Maintain backward compatibility with existing totalTokens field

Testing

  • in agent summary view, you should be able to see input and output tokens separately
  • for older messages, it should only show total tokens

Additional Notes

Summary by CodeRabbit

  • New Features

    • Token metrics now display input and output token counts separately across all dashboards, leaderboards, chat details, and analytics views—replacing the previous total token display with a granular in/out breakdown.
  • Chores

    • Database schema and backend APIs updated to independently track and aggregate input and output tokens for all operations.

- Add inputTokens and outputTokens columns to messages table
- Update all insertMessage calls to store tokens separately
- Update aggregation queries in sharedAgentUsage, admin, and message modules
- Add input/output token display to Dashboard, AdminChatsTable components
- Maintain backward compatibility with existing totalTokens field
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

📝 Walkthrough

Walkthrough

This pull request extends token tracking across the system to separately capture input and output tokens. Changes include database schema updates, SQL aggregation logic, backend API responses, and frontend UI displays to surface per-direction token breakdowns alongside existing total token metrics.

Changes

Cohort / File(s) Summary
Database Schema
server/db/schema/messages.ts
Added input_tokens and output_tokens integer columns (default 0) to the messages table for granular token tracking per message.
Database Query Layer
server/db/message.ts, server/db/sharedAgentUsage.ts
Updated aggregation queries across message feedback stats, agent usage, and leaderboard endpoints to sum and expose inputTokens and outputTokens fields. Extended five public interfaces (SharedAgentUsageData, AgentUserUsage, UserAgentLeaderboard, AgentAnalysisData, AgentUserLeaderboard) with these fields.
Backend Message Handling
server/api/chat/agents.ts, server/api/chat/chat.ts, server/api/chat/message-agents.ts
Added inputTokens and outputTokens extraction from token usage tracking and inclusion in message payloads during persistence. Refactored token accumulation in agents.ts to use an object structure for in/out tracking.
Backend Admin API
server/api/admin.ts
Extended admin query results (GetAdminChats, GetAdminUsers) to aggregate and expose inputTokens and outputTokens in response objects.
Frontend Components
frontend/src/components/AdminChatsTable.tsx, frontend/src/components/Dashboard.tsx
Added inputTokens and outputTokens fields to AdminChat and AdminUser interfaces. Updated UI displays to show token breakdowns in "( in / out)" format across multiple metrics and dashboard panels.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PR #646: Both PRs extend message persistence layer; the main PR adds inputTokens/outputTokens fields to schema and insertion logic while the related PR switches to DB-backed message storage.
  • PR #636: Both PRs modify the same chat message handling code paths (server/api/chat/agents.ts, server/api/chat/chat.ts) and alter message payload structures; this PR adds token fields while the related PR adds intent classification.

Suggested reviewers

  • zereraz
  • junaid-shirur
  • kalpadhwaryu
  • devesh-juspay

Poem

🐰 Token tales now split in two,
Input flows and outputs too,
From schema deep to UI bright,
We parse the breakdown, left and right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: track and display input/output tokens separately' directly and clearly summarizes the main change: separating input and output token tracking across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test-input-tokens

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @shivamashtikar, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant enhancement to how token usage is tracked and displayed within the application. By separating input and output tokens at the database level and propagating this change through the API and frontend, users and administrators can now gain a more precise understanding of LLM interaction costs and resource consumption. This provides greater transparency and improved analytics without disrupting existing data.

Highlights

  • Database Schema Update: Added new inputTokens and outputTokens columns to the messages table to store token usage more granularly.
  • Token Tracking Logic: Updated all insertMessage calls across various API endpoints (agents.ts, chat.ts, message-agents.ts) to correctly capture and store input and output tokens separately.
  • Data Aggregation Enhancements: Modified aggregation queries in admin, message, and sharedAgentUsage modules to include the new inputTokens and outputTokens for more detailed reporting.
  • Frontend Display Updates: Integrated the display of separate input and output token counts into the Dashboard and AdminChatsTable components, providing users with a clearer breakdown of token consumption.
  • Backward Compatibility: Ensured that the existing totalTokens field remains functional for historical data while new messages will leverage the granular input/output token tracking.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • frontend/src/components/AdminChatsTable.tsx
    • Added inputTokens and outputTokens to AdminChat and AdminUser interfaces.
    • Updated the display logic to show separate input and output token counts alongside total tokens.
  • frontend/src/components/Dashboard.tsx
    • Added inputTokens and outputTokens to BaseStatsData, BaseUserData, and BaseAgentData interfaces.
    • Modified MetricCard descriptions and agent usage displays to present the input/output token breakdown.
  • server/api/admin.ts
    • Updated GetAdminChats and GetAdminUsers aggregation queries to include inputTokens and outputTokens.
    • Converted aggregated inputTokens and outputTokens from bigint to number for API responses.
  • server/api/chat/agents.ts
    • Modified token aggregation logic to separately sum inputTokens and outputTokens.
    • Updated insertMessage calls to store inputTokens and outputTokens explicitly.
  • server/api/chat/chat.ts
    • Updated insertMessage calls to explicitly store inputTokens and outputTokens.
  • server/api/chat/message-agents.ts
    • Modified insertMessage call to include inputTokens and outputTokens from agentContext.tokenUsage.
  • server/db/message.ts
    • Added inputTokens and outputTokens to the getMessageFeedbackStats function's return type and aggregation.
    • Updated the feedbackStats object and summation loops to include the new token types.
  • server/db/schema/messages.ts
    • Added input_tokens and output_tokens integer columns to the messages table schema with default values.
    • Updated the comment for tokensUsed to clarify its legacy status.
  • server/db/sharedAgentUsage.ts
    • Added inputTokens and outputTokens properties to relevant interfaces (SharedAgentUsageData, AgentUserUsage, UserAgentLeaderboard, AgentAnalysisData).
    • Updated aggregation queries in getAgentUsageByUsers, getUserAgentLeaderboard, and getAgentAnalysis to include inputTokens and outputTokens.
    • Modified getAgentAnalysis to sum inputTokens and outputTokens across user leaderboards.
Activity
  • The author has performed testing to ensure that the agent summary view correctly displays separate input and output tokens.
  • Testing also confirmed that older messages, which predate this change, continue to display only total tokens, ensuring backward compatibility.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively implements the tracking and separate display of input and output tokens, which is a great enhancement for usage monitoring. The changes are consistently applied across the database, backend APIs, and frontend components. I've found one issue in the Dashboard.tsx component where a copy-paste error has led to duplicated token information and the removal of the message count display. My review includes a specific comment with a suggested fix for this.

Comment on lines +976 to +986
<div className="flex flex-col items-center">
<span className="text-sm font-medium">
{(agent.totalTokens || 0).toLocaleString()}
</span>
<span className="text-xs text-muted-foreground">
total tokens
</span>
<span className="text-xs text-muted-foreground">
({(agent.inputTokens || 0).toLocaleString()} in / {(agent.outputTokens || 0).toLocaleString()} out)
</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This block incorrectly replaces the 'total messages' display with a 'total tokens' display. This seems to be a copy-paste error, as there's another block that correctly updates the token display. This results in duplicated token information and the message count being removed from the SharedAgentUsageCard.

To fix this, this block should be reverted to display the agent.totalMessages as it did before.

                  <div className="flex flex-col items-center">
                    <span className="text-sm font-medium">
                      {agent.totalMessages}
                    </span>
                    <span className="text-xs text-muted-foreground">
                      messages
                    </span>
                  </div>

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
server/db/message.ts (1)

166-200: ⚠️ Potential issue | 🟡 Minor

Return type doesn’t include inputTokens / outputTokens.

getMessageFeedbackStats now returns these fields, but the Promise return type omits them, so callers can’t access them without type assertions. Please align the signature with the new payload.

✅ Suggested type update
export async function getMessageFeedbackStats({ ... }): Promise<{
  totalLikes: number
  totalDislikes: number
  totalCost: number
  totalTokens: number
+ inputTokens: number
+ outputTokens: number
  feedbackByChat: Record<
    string,
    { likes: number; dislikes: number; cost: number; tokens: number }
  >
  feedbackMessages: Array<{
    messageId: string
    chatExternalId: string
    type: "like" | "dislike"
    feedbackText: string[]
    timestamp: string
  }>
}> {
server/api/chat/message-agents.ts (1)

4985-5001: ⚠️ Potential issue | 🟠 Major

Update agentContext.tokenUsage in the token_usage event handler to populate token counts.

The token_usage event (lines 4846–4855) only logs telemetry and does not update agentContext.tokenUsage. Since inputTokens and outputTokens (lines 4999–5000) are persisted directly from the unmodified context, they remain 0 for all MessageAgents runs. Similarly, totalTokens (line 4982) and all aggregated token counts will be 0. Update the event handler to accumulate token values into the context, confirming first whether JAF's evt.data values are deltas or cumulative totals.

Suggested fix
case "token_usage": {
  const tokenUsageSpan = jafStreamingSpan.startSpan("token_usage")
  tokenUsageSpan.setAttribute("prompt_tokens", evt.data.prompt ?? 0)
  tokenUsageSpan.setAttribute(
    "completion_tokens",
    evt.data.completion ?? 0
  )
  tokenUsageSpan.setAttribute("total_tokens", evt.data.total ?? 0)
+  // TODO: confirm whether evt.data.* are deltas or totals from JAF
+  agentContext.tokenUsage.input += evt.data.prompt ?? 0
+  agentContext.tokenUsage.output += evt.data.completion ?? 0
  tokenUsageSpan.end()
  break
}
server/api/chat/agents.ts (1)

705-731: ⚠️ Potential issue | 🟠 Major

Prevent NaN token totals by defaulting missing usage values to 0.

If any chunk’s usage omits inputTokens or outputTokens, the reduction yields NaN, which then propagates to tokensUsed/inputTokens/outputTokens and can break inserts or analytics. Default to 0 during aggregation (or normalize when pushing).

🛠️ Suggested defensive aggregation (apply to all token reductions)
-            const totalTokens = tokenArr.reduce(
-              (acc, tokens) => ({
-                inputTokens: acc.inputTokens + tokens.inputTokens,
-                outputTokens: acc.outputTokens + tokens.outputTokens,
-              }),
-              { inputTokens: 0, outputTokens: 0 },
-            )
+            const totalTokens = tokenArr.reduce(
+              (acc, tokens) => ({
+                inputTokens: acc.inputTokens + (tokens.inputTokens ?? 0),
+                outputTokens: acc.outputTokens + (tokens.outputTokens ?? 0),
+              }),
+              { inputTokens: 0, outputTokens: 0 },
+            )

Also applies to: 742-768, 774-800, 925-951, 1676-1703, 1926-1954, 2561-2588, 2843-2870

frontend/src/components/AdminChatsTable.tsx (1)

619-627: ⚠️ Potential issue | 🟡 Minor

Hide input/output breakdown when legacy data has zeros.

For older chats, inputTokens/outputTokens will be 0 while totalTokens is non-zero, and the UI will misleadingly show “0 in / 0 out.” Render the breakdown only when either value is present.

✅ Conditional display to avoid misleading zeros
-                          <span className="text-xs text-muted-foreground">
-                            ({(chat.inputTokens || 0).toLocaleString()} in / {(chat.outputTokens || 0).toLocaleString()} out)
-                          </span>
+                          {(chat.inputTokens || 0) + (chat.outputTokens || 0) > 0 && (
+                            <span className="text-xs text-muted-foreground">
+                              ({(chat.inputTokens || 0).toLocaleString()} in / {(chat.outputTokens || 0).toLocaleString()} out)
+                            </span>
+                          )}
🤖 Fix all issues with AI agents
In `@frontend/src/components/Dashboard.tsx`:
- Around line 4631-4635: processAdminChatsData currently returns an
AdminDashboardStats and per-user AdminUserUsage objects without populating
inputTokens/outputTokens, causing "0 in / 0 out" everywhere; update
processAdminChatsData to accumulate chat-level token counts (inputTokens and
outputTokens) into both the top-level AdminDashboardStats return object and into
each per-user AdminUserUsage entry as you iterate chats (ensure you sum
chat.inputTokens/chat.outputTokens or equivalent fields when building totals and
user aggregates), and return those populated fields so MetricCard and
AdminUsersLeaderboard consume the real token values.
- Around line 3178-3182: processUserChatsData currently returns totalTokens but
never computes inputTokens or outputTokens, causing MetricCard to always show "0
in / 0 out"; update processUserChatsData to accumulate inputTokens and
outputTokens from each chat/message (e.g., sum request prompt tokens into
inputTokens and model response tokens into outputTokens) and include these
fields on the returned userStats object, or alternatively remove the in/out
breakdown from where MetricCard renders (reference MetricCard props using
userStats.inputTokens and userStats.outputTokens) until those fields are
populated; ensure the returned object from processUserChatsData contains numeric
inputTokens and outputTokens to avoid relying on the || 0 fallback.
- Around line 976-986: There are two identical JSX blocks rendering token info
(a div with className "flex flex-col items-center" that displays
(agent.totalTokens || 0).toLocaleString() and the in/out tokens using
agent.inputTokens and agent.outputTokens) inside the agent row; remove the
duplicate block so each agent card only renders this token column once, leaving
the single remaining token-info div and adjusting any surrounding layout/spacing
if needed.

Comment on lines +976 to +986
<div className="flex flex-col items-center">
<span className="text-sm font-medium">
{(agent.totalTokens || 0).toLocaleString()}
</span>
<span className="text-xs text-muted-foreground">
total tokens
</span>
<span className="text-xs text-muted-foreground">
({(agent.inputTokens || 0).toLocaleString()} in / {(agent.outputTokens || 0).toLocaleString()} out)
</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: Duplicate token column in the shared agent list row.

Lines 976–986 and 1011–1021 render identical "total tokens" + "(in / out)" blocks within the same agent card. This results in the token information appearing twice per row. One of these blocks should be removed.

🐛 Remove the duplicate block (lines 1011–1021)
                      </div>
-                  <div className="flex flex-col items-center">
-                    <span className="text-sm font-medium">
-                      {(agent.totalTokens || 0).toLocaleString()}
-                    </span>
-                    <span className="text-xs text-muted-foreground">
-                      total tokens
-                    </span>
-                    <span className="text-xs text-muted-foreground">
-                      ({(agent.inputTokens || 0).toLocaleString()} in / {(agent.outputTokens || 0).toLocaleString()} out)
-                    </span>
-                  </div>
                      <div className="flex items-center gap-2 text-xs">

Also applies to: 1011-1021

🤖 Prompt for AI Agents
In `@frontend/src/components/Dashboard.tsx` around lines 976 - 986, There are two
identical JSX blocks rendering token info (a div with className "flex flex-col
items-center" that displays (agent.totalTokens || 0).toLocaleString() and the
in/out tokens using agent.inputTokens and agent.outputTokens) inside the agent
row; remove the duplicate block so each agent card only renders this token
column once, leaving the single remaining token-info div and adjusting any
surrounding layout/spacing if needed.

Comment on lines 3178 to 3182
<MetricCard
title="Total Tokens"
value={(userStats.totalTokens || 0).toLocaleString()}
description="Tokens processed"
description={`${(userStats.inputTokens || 0).toLocaleString()} in / ${(userStats.outputTokens || 0).toLocaleString()} out`}
icon={Activity}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: userStats.inputTokens / outputTokens are always undefined → displays "0 in / 0 out".

processUserChatsData (line 2893) returns an object with totalTokens but never computes inputTokens or outputTokens. The fallback || 0 prevents a crash, but the UI will always show zeros here, which is misleading.

Either accumulate inputTokens/outputTokens from the chat data inside processUserChatsData, or don't display the in/out breakdown on this page until the data is available.

🐛 Sketch: propagate input/output tokens in processUserChatsData
  const processUserChatsData = (chats: any[]) => {
    ...
    let totalTokensCalculated = 0
+   let inputTokensCalculated = 0
+   let outputTokensCalculated = 0

    ...
    chats.forEach((chat) => {
      ...
      totalTokensCalculated += chat.totalTokens || 0
+     inputTokensCalculated += chat.inputTokens || 0
+     outputTokensCalculated += chat.outputTokens || 0
      ...
    })

    return {
      ...
      totalTokens: totalTokensCalculated,
+     inputTokens: inputTokensCalculated,
+     outputTokens: outputTokensCalculated,
      ...
    }
  }
🤖 Prompt for AI Agents
In `@frontend/src/components/Dashboard.tsx` around lines 3178 - 3182,
processUserChatsData currently returns totalTokens but never computes
inputTokens or outputTokens, causing MetricCard to always show "0 in / 0 out";
update processUserChatsData to accumulate inputTokens and outputTokens from each
chat/message (e.g., sum request prompt tokens into inputTokens and model
response tokens into outputTokens) and include these fields on the returned
userStats object, or alternatively remove the in/out breakdown from where
MetricCard renders (reference MetricCard props using userStats.inputTokens and
userStats.outputTokens) until those fields are populated; ensure the returned
object from processUserChatsData contains numeric inputTokens and outputTokens
to avoid relying on the || 0 fallback.

Comment on lines 4631 to 4635
<MetricCard
title="Total Tokens"
value={(adminStats.totalTokens || 0).toLocaleString()}
description="Total tokens processed"
description={`${(adminStats.inputTokens || 0).toLocaleString()} in / ${(adminStats.outputTokens || 0).toLocaleString()} out`}
icon={Activity}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: adminStats.inputTokens / outputTokens are never populated → always "0 in / 0 out".

processAdminChatsData (line 3977) returns an AdminDashboardStats object but never sets inputTokens or outputTokens in the return value (line 4268). The same issue applies to the per-user objects built at line 4112 — AdminUserUsage entries lack these fields, so the AdminUsersLeaderboard at line 2241 will also always show zeros.

You need to accumulate inputTokens and outputTokens in both the top-level stats and per-user objects inside processAdminChatsData, or source these values from a server-side aggregation endpoint instead of computing them client-side from chat-level data that may not carry the breakdown.

🤖 Prompt for AI Agents
In `@frontend/src/components/Dashboard.tsx` around lines 4631 - 4635,
processAdminChatsData currently returns an AdminDashboardStats and per-user
AdminUserUsage objects without populating inputTokens/outputTokens, causing "0
in / 0 out" everywhere; update processAdminChatsData to accumulate chat-level
token counts (inputTokens and outputTokens) into both the top-level
AdminDashboardStats return object and into each per-user AdminUserUsage entry as
you iterate chats (ensure you sum chat.inputTokens/chat.outputTokens or
equivalent fields when building totals and user aggregates), and return those
populated fields so MetricCard and AdminUsersLeaderboard consume the real token
values.

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.

1 participant