feat: add file change tracking to session metrics (#6094)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Arya Gummadi
2025-08-18 22:57:53 -07:00
committed by GitHub
parent da396bd566
commit 8f8082fe3d
10 changed files with 291 additions and 35 deletions

View File

@@ -108,6 +108,10 @@ describe('UiTelemetryService', () => {
},
byName: {},
},
files: {
totalLinesAdded: 0,
totalLinesRemoved: 0,
},
});
expect(service.getLastPromptTokenCount()).toBe(0);
});
@@ -342,9 +346,9 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.ProceedOnce,
);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall))),
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -376,9 +380,9 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.Cancel,
);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall))),
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -410,9 +414,9 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.ModifyWithEditor,
);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall))),
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -426,9 +430,9 @@ describe('UiTelemetryService', () => {
it('should process a ToolCallEvent without a decision', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall))),
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -462,13 +466,13 @@ describe('UiTelemetryService', () => {
);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall1))),
...structuredClone(new ToolCallEvent(toolCall1)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall2))),
...structuredClone(new ToolCallEvent(toolCall2)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -497,13 +501,13 @@ describe('UiTelemetryService', () => {
const toolCall1 = createFakeCompletedToolCall('tool_A', true, 100);
const toolCall2 = createFakeCompletedToolCall('tool_B', false, 200);
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall1))),
...structuredClone(new ToolCallEvent(toolCall1)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
service.addEvent({
...JSON.parse(JSON.stringify(new ToolCallEvent(toolCall2))),
...structuredClone(new ToolCallEvent(toolCall2)),
'event.name': EVENT_TOOL_CALL,
});
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
const metrics = service.getMetrics();
const { tools } = metrics;
@@ -629,4 +633,42 @@ describe('UiTelemetryService', () => {
expect(spy).toHaveBeenCalledOnce();
});
});
describe('Tool Call Event with Line Count Metadata', () => {
it('should aggregate valid line count metadata', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
const event = {
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
metadata: {
ai_added_lines: 10,
ai_removed_lines: 5,
},
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL };
service.addEvent(event);
const metrics = service.getMetrics();
expect(metrics.files.totalLinesAdded).toBe(10);
expect(metrics.files.totalLinesRemoved).toBe(5);
});
it('should ignore null/undefined values in line count metadata', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
const event = {
...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL,
metadata: {
ai_added_lines: null,
ai_removed_lines: undefined,
},
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL };
service.addEvent(event);
const metrics = service.getMetrics();
expect(metrics.files.totalLinesAdded).toBe(0);
expect(metrics.files.totalLinesRemoved).toBe(0);
});
});
});

View File

@@ -63,6 +63,10 @@ export interface SessionMetrics {
};
byName: Record<string, ToolCallStats>;
};
files: {
totalLinesAdded: number;
totalLinesRemoved: number;
};
}
const createInitialModelMetrics = (): ModelMetrics => ({
@@ -96,6 +100,10 @@ const createInitialMetrics = (): SessionMetrics => ({
},
byName: {},
},
files: {
totalLinesAdded: 0,
totalLinesRemoved: 0,
},
});
export class UiTelemetryService extends EventEmitter {
@@ -171,7 +179,7 @@ export class UiTelemetryService extends EventEmitter {
}
private processToolCall(event: ToolCallEvent) {
const { tools } = this.#metrics;
const { tools, files } = this.#metrics;
tools.totalCalls++;
tools.totalDurationMs += event.duration_ms;
@@ -209,6 +217,16 @@ export class UiTelemetryService extends EventEmitter {
tools.totalDecisions[event.decision]++;
toolStats.decisions[event.decision]++;
}
// Aggregate line count data from metadata
if (event.metadata) {
if (event.metadata['ai_added_lines'] !== undefined) {
files.totalLinesAdded += event.metadata['ai_added_lines'];
}
if (event.metadata['ai_removed_lines'] !== undefined) {
files.totalLinesRemoved += event.metadata['ai_removed_lines'];
}
}
}
}