Show raw input token counts in json output. (#15021)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
Jacob Richman
2025-12-15 18:47:39 -08:00
committed by GitHub
parent bb0c0d8ee3
commit 79f664d593
17 changed files with 189 additions and 129 deletions

View File

@@ -4,7 +4,7 @@ exports[`runNonInteractive > should emit appropriate error event in streaming JS
"{"type":"init","timestamp":"<TIMESTAMP>","session_id":"test-session-id","model":"test-model"}
{"type":"message","timestamp":"<TIMESTAMP>","role":"user","content":"Loop test"}
{"type":"error","timestamp":"<TIMESTAMP>","severity":"warning","message":"Loop detected, stopping execution"}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"duration_ms":<DURATION>,"tool_calls":0}}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
"
`;
@@ -12,7 +12,7 @@ exports[`runNonInteractive > should emit appropriate error event in streaming JS
"{"type":"init","timestamp":"<TIMESTAMP>","session_id":"test-session-id","model":"test-model"}
{"type":"message","timestamp":"<TIMESTAMP>","role":"user","content":"Max turns test"}
{"type":"error","timestamp":"<TIMESTAMP>","severity":"error","message":"Maximum session turns exceeded"}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"duration_ms":<DURATION>,"tool_calls":0}}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
"
`;
@@ -23,7 +23,7 @@ exports[`runNonInteractive > should emit appropriate events for streaming JSON o
{"type":"tool_use","timestamp":"<TIMESTAMP>","tool_name":"testTool","tool_id":"tool-1","parameters":{"arg1":"value1"}}
{"type":"tool_result","timestamp":"<TIMESTAMP>","tool_id":"tool-1","status":"success","output":"Tool executed successfully"}
{"type":"message","timestamp":"<TIMESTAMP>","role":"assistant","content":"Final answer","delta":true}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"duration_ms":<DURATION>,"tool_calls":0}}
{"type":"result","timestamp":"<TIMESTAMP>","status":"success","stats":{"total_tokens":0,"input_tokens":0,"output_tokens":0,"cached":0,"input":0,"duration_ms":<DURATION>,"tool_calls":0}}
"
`;

View File

@@ -87,6 +87,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
@@ -128,6 +129,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 5,
prompt: 10,
candidates: 20,
total: 30,
@@ -139,6 +141,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-flash': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 50 },
tokens: {
input: 5,
prompt: 5,
candidates: 10,
total: 15,
@@ -180,6 +183,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 10, totalErrors: 1, totalLatencyMs: 1000 },
tokens: {
input: 50,
prompt: 100,
candidates: 200,
total: 300,
@@ -191,6 +195,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-flash': {
api: { totalRequests: 20, totalErrors: 2, totalLatencyMs: 500 },
tokens: {
input: 100,
prompt: 200,
candidates: 400,
total: 600,
@@ -235,6 +240,7 @@ describe('<ModelStatsDisplay />', () => {
totalLatencyMs: 9876,
},
tokens: {
input: 987654321 - 123456789,
prompt: 987654321,
candidates: 123456789,
total: 999999999,
@@ -272,6 +278,7 @@ describe('<ModelStatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 5,
prompt: 10,
candidates: 20,
total: 30,

View File

@@ -170,7 +170,7 @@ export const ModelStatsDisplay: React.FC = () => {
isSubtle
values={getModelValues((m) => (
<Text color={theme.text.primary}>
{Math.max(0, m.tokens.prompt - m.tokens.cached).toLocaleString()}
{m.tokens.input.toLocaleString()}
</Text>
))}
/>

View File

@@ -45,6 +45,7 @@ describe('<SessionSummaryDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 10, totalErrors: 1, totalLatencyMs: 50234 },
tokens: {
input: 500,
prompt: 1000,
candidates: 2000,
total: 3500,

View File

@@ -85,6 +85,7 @@ describe('<StatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 3, totalErrors: 0, totalLatencyMs: 15000 },
tokens: {
input: 500,
prompt: 1000,
candidates: 2000,
total: 43234,
@@ -96,6 +97,7 @@ describe('<StatsDisplay />', () => {
'gemini-2.5-flash': {
api: { totalRequests: 5, totalErrors: 1, totalLatencyMs: 4500 },
tokens: {
input: 15000,
prompt: 25000,
candidates: 15000,
total: 150000000,
@@ -123,6 +125,7 @@ describe('<StatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 50,
prompt: 100,
candidates: 100,
total: 250,
@@ -216,6 +219,7 @@ describe('<StatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 100,
prompt: 100,
candidates: 100,
total: 200,
@@ -398,6 +402,7 @@ describe('<StatsDisplay />', () => {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 50,
prompt: 100,
candidates: 100,
total: 250,

View File

@@ -85,15 +85,13 @@ const buildModelRows = (
const activeRows = Object.entries(models).map(([name, metrics]) => {
const modelName = getBaseModelName(name);
const cachedTokens = metrics.tokens.cached;
const totalInputTokens = metrics.tokens.prompt;
const uncachedTokens = Math.max(0, totalInputTokens - cachedTokens);
const inputTokens = metrics.tokens.input;
return {
key: name,
modelName,
requests: metrics.api.totalRequests,
cachedTokens: cachedTokens.toLocaleString(),
uncachedTokens: uncachedTokens.toLocaleString(),
totalInputTokens: totalInputTokens.toLocaleString(),
inputTokens: inputTokens.toLocaleString(),
outputTokens: metrics.tokens.candidates.toLocaleString(),
bucket: quotas?.buckets?.find((b) => b.modelId === modelName),
isActive: true,
@@ -114,8 +112,7 @@ const buildModelRows = (
modelName: bucket.modelId!,
requests: '-',
cachedTokens: '-',
uncachedTokens: '-',
totalInputTokens: '-',
inputTokens: '-',
outputTokens: '-',
bucket,
isActive: false,
@@ -290,7 +287,7 @@ const ModelUsageTable: React.FC<{
row.isActive ? theme.text.primary : theme.text.secondary
}
>
{row.uncachedTokens}
{row.inputTokens}
</Text>
</Box>
<Box

View File

@@ -92,6 +92,7 @@ describe('SessionStatsContext', () => {
totalLatencyMs: 123,
},
tokens: {
input: 50,
prompt: 100,
candidates: 200,
total: 300,
@@ -171,6 +172,7 @@ describe('SessionStatsContext', () => {
'gemini-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
@@ -212,6 +214,7 @@ describe('SessionStatsContext', () => {
'gemini-pro': {
api: { totalRequests: 2, totalErrors: 0, totalLatencyMs: 200 },
tokens: {
input: 20,
prompt: 20,
candidates: 40,
total: 60,

View File

@@ -37,6 +37,7 @@ function areModelMetricsEqual(a: ModelMetrics, b: ModelMetrics): boolean {
return false;
}
if (
a.tokens.input !== b.tokens.input ||
a.tokens.prompt !== b.tokens.prompt ||
a.tokens.candidates !== b.tokens.candidates ||
a.tokens.total !== b.tokens.total ||
@@ -159,6 +160,7 @@ export interface ComputedSessionStats {
successRate: number;
agreementRate: number;
totalCachedTokens: number;
totalInputTokens: number;
totalPromptTokens: number;
totalLinesAdded: number;
totalLinesRemoved: number;

View File

@@ -21,6 +21,7 @@ describe('calculateErrorRate', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 0, totalErrors: 0, totalLatencyMs: 0 },
tokens: {
input: 0,
prompt: 0,
candidates: 0,
total: 0,
@@ -36,6 +37,7 @@ describe('calculateErrorRate', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 10, totalErrors: 2, totalLatencyMs: 0 },
tokens: {
input: 0,
prompt: 0,
candidates: 0,
total: 0,
@@ -53,6 +55,7 @@ describe('calculateAverageLatency', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 0, totalErrors: 0, totalLatencyMs: 1000 },
tokens: {
input: 0,
prompt: 0,
candidates: 0,
total: 0,
@@ -68,6 +71,7 @@ describe('calculateAverageLatency', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 10, totalErrors: 0, totalLatencyMs: 1500 },
tokens: {
input: 0,
prompt: 0,
candidates: 0,
total: 0,
@@ -85,6 +89,7 @@ describe('calculateCacheHitRate', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 0, totalErrors: 0, totalLatencyMs: 0 },
tokens: {
input: 0,
prompt: 0,
candidates: 0,
total: 0,
@@ -100,6 +105,7 @@ describe('calculateCacheHitRate', () => {
const metrics: ModelMetrics = {
api: { totalRequests: 0, totalErrors: 0, totalLatencyMs: 0 },
tokens: {
input: 150,
prompt: 200,
candidates: 0,
total: 0,
@@ -143,6 +149,7 @@ describe('computeSessionStats', () => {
successRate: 0,
agreementRate: 0,
totalPromptTokens: 0,
totalInputTokens: 0,
totalCachedTokens: 0,
totalLinesAdded: 0,
totalLinesRemoved: 0,
@@ -155,6 +162,7 @@ describe('computeSessionStats', () => {
'gemini-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 750 },
tokens: {
input: 10,
prompt: 10,
candidates: 10,
total: 20,
@@ -193,6 +201,7 @@ describe('computeSessionStats', () => {
'gemini-pro': {
api: { totalRequests: 2, totalErrors: 0, totalLatencyMs: 1000 },
tokens: {
input: 100,
prompt: 150,
candidates: 10,
total: 160,

View File

@@ -50,6 +50,10 @@ export const computeSessionStats = (
(acc, model) => acc + model.tokens.cached,
0,
);
const totalInputTokens = Object.values(models).reduce(
(acc, model) => acc + model.tokens.input,
0,
);
const totalPromptTokens = Object.values(models).reduce(
(acc, model) => acc + model.tokens.prompt,
0,
@@ -82,6 +86,7 @@ export const computeSessionStats = (
successRate,
agreementRate,
totalCachedTokens,
totalInputTokens,
totalPromptTokens,
totalLinesAdded: files.totalLinesAdded,
totalLinesRemoved: files.totalLinesRemoved,

View File

@@ -47,7 +47,15 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
})),
StreamJsonFormatter: vi.fn().mockImplementation(() => ({
emitEvent: vi.fn(),
convertToStreamStats: vi.fn().mockReturnValue({}),
convertToStreamStats: vi.fn().mockReturnValue({
total_tokens: 0,
input_tokens: 0,
output_tokens: 0,
cached: 0,
input: 0,
duration_ms: 0,
tool_calls: 0,
}),
})),
uiTelemetryService: {
getMetrics: vi.fn().mockReturnValue({}),