feat: add role-specific statistics to telemetry and UI (cont. #15234) (#18824)

Co-authored-by: Yuna Seol <yunaseol@google.com>
This commit is contained in:
Yuna Seol
2026-02-17 12:32:30 -05:00
committed by GitHub
parent 14aabbbe8b
commit 8aca3068cf
51 changed files with 826 additions and 20 deletions

View File

@@ -11,7 +11,7 @@ import * as SessionContext from '../contexts/SessionContext.js';
import * as SettingsContext from '../contexts/SettingsContext.js';
import type { LoadedSettings } from '../../config/settings.js';
import type { SessionMetrics } from '../contexts/SessionContext.js';
import { ToolCallDecision } from '@google/gemini-cli-core';
import { ToolCallDecision, LlmRole } from '@google/gemini-cli-core';
// Mock the context to provide controlled data for testing
vi.mock('../contexts/SessionContext.js', async (importOriginal) => {
@@ -118,6 +118,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 0,
tool: 0,
},
roles: {},
},
},
tools: {
@@ -160,6 +161,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 2,
tool: 0,
},
roles: {},
},
'gemini-2.5-flash': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 50 },
@@ -172,6 +174,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 0,
tool: 3,
},
roles: {},
},
},
tools: {
@@ -214,6 +217,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 10,
tool: 5,
},
roles: {},
},
'gemini-2.5-flash': {
api: { totalRequests: 20, totalErrors: 2, totalLatencyMs: 500 },
@@ -226,6 +230,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 20,
tool: 10,
},
roles: {},
},
},
tools: {
@@ -271,6 +276,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 111111111,
tool: 222222222,
},
roles: {},
},
},
tools: {
@@ -309,6 +315,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 2,
tool: 1,
},
roles: {},
},
},
tools: {
@@ -351,6 +358,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 100,
tool: 50,
},
roles: {},
},
'gemini-3-flash-preview': {
api: { totalRequests: 20, totalErrors: 0, totalLatencyMs: 1000 },
@@ -363,6 +371,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 200,
tool: 100,
},
roles: {},
},
},
tools: {
@@ -390,6 +399,64 @@ describe('<ModelStatsDisplay />', () => {
const output = lastFrame();
expect(output).toContain('gemini-3-pro-');
expect(output).toContain('gemini-3-flash-');
});
it('should display role breakdown correctly', () => {
const { lastFrame } = renderWithMockedStats({
models: {
'gemini-2.5-pro': {
api: { totalRequests: 2, totalErrors: 0, totalLatencyMs: 200 },
tokens: {
input: 20,
prompt: 30,
candidates: 40,
total: 70,
cached: 10,
thoughts: 0,
tool: 0,
},
roles: {
[LlmRole.MAIN]: {
totalRequests: 1,
totalErrors: 0,
totalLatencyMs: 100,
tokens: {
input: 10,
prompt: 15,
candidates: 20,
total: 35,
cached: 5,
thoughts: 0,
tool: 0,
},
},
},
},
},
tools: {
totalCalls: 0,
totalSuccess: 0,
totalFail: 0,
totalDurationMs: 0,
totalDecisions: {
accept: 0,
reject: 0,
modify: 0,
[ToolCallDecision.AUTO_ACCEPT]: 0,
},
byName: {},
},
files: {
totalLinesAdded: 0,
totalLinesRemoved: 0,
},
});
const output = lastFrame();
expect(output).toContain('main');
expect(output).toContain('Input');
expect(output).toContain('Output');
expect(output).toContain('Cache Reads');
expect(output).toMatchSnapshot();
});
@@ -427,6 +494,7 @@ describe('<ModelStatsDisplay />', () => {
thoughts: 0,
tool: 0,
},
roles: {},
},
},
tools: {
@@ -462,4 +530,121 @@ describe('<ModelStatsDisplay />', () => {
expect(output).toContain('Tier:');
expect(output).toContain('Pro');
});
it('should handle long role name layout', () => {
// Use the longest valid role name to test layout
const longRoleName = LlmRole.UTILITY_LOOP_DETECTOR;
const { lastFrame } = renderWithMockedStats({
models: {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
cached: 0,
thoughts: 0,
tool: 0,
},
roles: {
[longRoleName]: {
totalRequests: 1,
totalErrors: 0,
totalLatencyMs: 100,
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
cached: 0,
thoughts: 0,
tool: 0,
},
},
},
},
},
tools: {
totalCalls: 0,
totalSuccess: 0,
totalFail: 0,
totalDurationMs: 0,
totalDecisions: {
accept: 0,
reject: 0,
modify: 0,
[ToolCallDecision.AUTO_ACCEPT]: 0,
},
byName: {},
},
files: {
totalLinesAdded: 0,
totalLinesRemoved: 0,
},
});
const output = lastFrame();
expect(output).toContain(longRoleName);
expect(output).toMatchSnapshot();
});
it('should filter out invalid role names', () => {
const invalidRoleName =
'this_is_a_very_long_role_name_that_should_be_wrapped' as LlmRole;
const { lastFrame } = renderWithMockedStats({
models: {
'gemini-2.5-pro': {
api: { totalRequests: 1, totalErrors: 0, totalLatencyMs: 100 },
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
cached: 0,
thoughts: 0,
tool: 0,
},
roles: {
[invalidRoleName]: {
totalRequests: 1,
totalErrors: 0,
totalLatencyMs: 100,
tokens: {
input: 10,
prompt: 10,
candidates: 20,
total: 30,
cached: 0,
thoughts: 0,
tool: 0,
},
},
},
},
},
tools: {
totalCalls: 0,
totalSuccess: 0,
totalFail: 0,
totalDurationMs: 0,
totalDecisions: {
accept: 0,
reject: 0,
modify: 0,
[ToolCallDecision.AUTO_ACCEPT]: 0,
},
byName: {},
},
files: {
totalLinesAdded: 0,
totalLinesRemoved: 0,
},
});
const output = lastFrame();
expect(output).not.toContain(invalidRoleName);
expect(output).toMatchSnapshot();
});
});