feat: support richer token cost tracking for ask#1353
Conversation
Estimate the input-token footprint of each tool call's output (the cost the result imposes when fed back to the model on subsequent steps) using a local length-based estimator, persist it per tool call in the chat message metadata, and surface it inline in each tool call row next to the Details toggle. Estimates are ~-prefixed to keep them distinct from the authoritative billed token totals.
Record the provider-reported input/output token usage of each agent step in the chat message metadata and display it per step group in the thinking steps view (joined to UI step groups via the step index now tagged on each tool token usage entry). Also fix the tool output estimator to measure the model-visible payload: tools with a toModelOutput mapping (all builtins) send only their output text to the model, so estimating the raw ToolResult object was counting UI-only metadata the model never sees. The bytes-per-token ratio is now a uniform ~2 chars/token, calibrated against provider-reported per-step usage of code-heavy tool results.
Collect usage from researchStream.steps and response.messages after the stream completes (covers approval-gated and failed tool calls, off the hot path), nest tool estimates under their step in a single stepTokenUsage array, and join UI steps to entries by stepIndex.
This comment has been minimized.
This comment has been minimized.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughAdds an offline token estimation module ( ChangesPer-step tool token usage estimation and display
Sequence Diagram(s)sequenceDiagram
participant chatThreadListItem
participant createMessageStream
participant estimateModelToolOutputTokens
participant DetailsCard
participant ToolTokenBadge
createMessageStream->>estimateModelToolOutputTokens: estimate tokens per tool-result part
estimateModelToolOutputTokens-->>createMessageStream: estimated token counts
createMessageStream->>createMessageStream: build stepTokenUsage aligned to steps
createMessageStream-->>chatThreadListItem: message-metadata (stepTokenUsage)
chatThreadListItem->>chatThreadListItem: derive uiVisibleThinkingSteps + answerStepIndex
chatThreadListItem->>DetailsCard: thinkingSteps, answerStepIndex, metadata
DetailsCard->>DetailsCard: build toolTokenUsageMap from stepTokenUsage
DetailsCard->>ToolTokenBadge: estimatedOutputTokens per tool call
ToolTokenBadge-->>DetailsCard: rendered token badge with tooltip
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/web/src/ee/features/chat/agent.ts (1)
257-263:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPrevent computed token metadata from being overwritten by caller metadata
On Line 262, spreading
...metadatalast lets externalmetadatareplace derived fields (stepTokenUsage, totals,modelName,traceId), which can break the step-index join contract used by the UI.Suggested fix
writer.write({ type: 'message-metadata', messageMetadata: { + ...metadata, totalTokens: (priorMetadata?.totalTokens ?? 0) + (totalUsage.totalTokens ?? 0), totalInputTokens: (priorMetadata?.totalInputTokens ?? 0) + (totalUsage.inputTokens ?? 0), totalOutputTokens: (priorMetadata?.totalOutputTokens ?? 0) + (totalUsage.outputTokens ?? 0), totalCacheReadTokens: (priorMetadata?.totalCacheReadTokens ?? 0) + (totalUsage.inputTokenDetails?.cacheReadTokens ?? 0), totalCacheWriteTokens: (priorMetadata?.totalCacheWriteTokens ?? 0) + (totalUsage.inputTokenDetails?.cacheWriteTokens ?? 0), totalResponseTimeMs: (priorMetadata?.totalResponseTimeMs ?? 0) + (new Date().getTime() - startTime.getTime()), // Concatenated (not summed) across approval-continuation // phases so earlier phases' steps are preserved in order. stepTokenUsage: [...(priorMetadata?.stepTokenUsage ?? []), ...stepTokenUsage], modelName, traceId, - ...metadata, } });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/src/ee/features/chat/agent.ts` around lines 257 - 263, The spread operator `...metadata` is placed last in the object literal, which allows caller-provided metadata to overwrite the computed derived fields (stepTokenUsage, modelName, traceId, and totals). Reorder the object properties by moving `...metadata` to the beginning of the object literal before the computed fields, so that the carefully derived values (stepTokenUsage, modelName, traceId, and any totals fields) are spread after the external metadata and cannot be accidentally overwritten by caller data.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@packages/web/src/ee/features/chat/agent.ts`:
- Around line 257-263: The spread operator `...metadata` is placed last in the
object literal, which allows caller-provided metadata to overwrite the computed
derived fields (stepTokenUsage, modelName, traceId, and totals). Reorder the
object properties by moving `...metadata` to the beginning of the object literal
before the computed fields, so that the carefully derived values
(stepTokenUsage, modelName, traceId, and any totals fields) are spread after the
external metadata and cannot be accidentally overwritten by caller data.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 181d5be9-92d5-4715-9a94-791f21b19000
📒 Files selected for processing (12)
packages/web/src/ee/features/chat/agent.test.tspackages/web/src/ee/features/chat/agent.tspackages/web/src/ee/features/chat/components/chatThread/chatThreadListItem.tsxpackages/web/src/ee/features/chat/components/chatThread/detailsCard.test.tsxpackages/web/src/ee/features/chat/components/chatThread/detailsCard.tsxpackages/web/src/ee/features/chat/components/chatThread/tools/mcpToolComponent.tsxpackages/web/src/ee/features/chat/components/chatThread/tools/toolOutputGuard.tsxpackages/web/src/ee/features/chat/components/chatThread/tools/toolSearchToolComponent.tsxpackages/web/src/ee/features/chat/components/chatThread/tools/toolTokenBadge.tsxpackages/web/src/features/chat/tokenEstimation.test.tspackages/web/src/features/chat/tokenEstimation.tspackages/web/src/features/chat/types.ts
Spread caller-supplied metadata before the derived token fields so stepTokenUsage and the totals can't be clobbered, which would desync the UI's index-based step join.
| @@ -0,0 +1,63 @@ | |||
| import { ToolResultOutput } from "@ai-sdk/provider-utils"; | |||
There was a problem hiding this comment.
should this be in /ee/features?
Adds tracking of token costs per step, also adds estimates of tool call token usage. This information is embedded in the chat history. Tool call token usages are estimates because a single step can run multiple tool calls and there is no mechanism to discern which part of the input token cost came from which tool call.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests