Add input + output tokens for anthropic message type

This commit is contained in:
Sameer Kankute 2026-04-15 21:08:34 +05:30
parent 84c507bc14
commit 277be4c50e
No known key found for this signature in database
3 changed files with 65 additions and 8 deletions

View File

@ -113,6 +113,35 @@ describe("LogDetailContent", () => {
expect(screen.getAllByText("$0.00200000").length).toBeGreaterThanOrEqual(1);
});
it("should show Input Tokens and Output Tokens for anthropic_messages when uncached text_tokens exist", () => {
render(
<LogDetailContent
logEntry={createLogEntry({
call_type: "anthropic_messages",
prompt_tokens: 34548,
completion_tokens: 28,
total_tokens: 34576,
spend: 0.01107885,
metadata: {
status: "success",
additional_usage_values: {
prompt_tokens_details: { text_tokens: 3 },
cache_read_input_tokens: 34462,
cache_creation_input_tokens: 83,
},
},
})}
/>,
);
expect(screen.getByText("Input Tokens")).toBeInTheDocument();
expect(screen.getByText("Output Tokens")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
expect(screen.getByText("28")).toBeInTheDocument();
// Combined TokenFlow line should not appear (would include "prompt tokens")
expect(screen.queryByText(/prompt tokens \+ .* completion tokens/)).not.toBeInTheDocument();
});
it("should display ConfigInfoMessage when no messages, response, or error and not loading", () => {
render(
<LogDetailContent

View File

@ -260,6 +260,19 @@ function GuardrailLabel({ label, maskedCount }: { label: string; maskedCount: nu
);
}
/**
* Uncached input token count (billable non-cache prompt text), aligned with Cost Breakdown "Input".
* Same sources as CostBreakdownViewer rawInputTokens.
*/
function getUncachedInputTextTokens(metadata: Record<string, any>): number | undefined {
const raw =
metadata?.additional_usage_values?.prompt_tokens_details?.text_tokens ??
metadata?.usage_object?.prompt_tokens_details?.text_tokens;
if (raw === undefined || raw === null) return undefined;
const n = Number(raw);
return Number.isFinite(n) ? n : undefined;
}
function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata: Record<string, any> }) {
const completionStartTime = logEntry.completionStartTime;
const ttftMs =
@ -280,17 +293,32 @@ function MetricsSection({ logEntry, metadata }: { logEntry: LogEntry; metadata:
? "red"
: "default";
const uncachedInputTokens = getUncachedInputTextTokens(metadata);
const showAnthropicMessagesInputOutput =
logEntry.call_type === "anthropic_messages" && uncachedInputTokens !== undefined;
return (
<div className="bg-white rounded-lg shadow w-full max-w-full overflow-hidden mb-6">
<Card title="Metrics" size="small" style={{ marginBottom: 0 }}>
<Descriptions column={2} size="small">
<Descriptions.Item label="Tokens">
<TokenFlow
prompt={logEntry.prompt_tokens}
completion={logEntry.completion_tokens}
total={logEntry.total_tokens}
/>
</Descriptions.Item>
{showAnthropicMessagesInputOutput ? (
<>
<Descriptions.Item label="Input Tokens">
{formatNumberWithCommas(uncachedInputTokens)}
</Descriptions.Item>
<Descriptions.Item label="Output Tokens">
{formatNumberWithCommas(logEntry.completion_tokens)}
</Descriptions.Item>
</>
) : (
<Descriptions.Item label="Tokens">
<TokenFlow
prompt={logEntry.prompt_tokens}
completion={logEntry.completion_tokens}
total={logEntry.total_tokens}
/>
</Descriptions.Item>
)}
<Descriptions.Item label="Cost">${formatNumberWithCommas(logEntry.spend || 0, 8)}</Descriptions.Item>
<Descriptions.Item label="Duration">{logEntry.request_duration_ms != null ? (logEntry.request_duration_ms / 1000).toFixed(3) : "-"} s</Descriptions.Item>
{ttftMs != null && ttftMs > 0 && (

View File

@ -14,7 +14,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"jsx": "preserve",
"incremental": true,
"plugins": [
{