From 3d8f56a207fbd83120a7ce8478b8bb7f40eb1d58 Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Tue, 31 Mar 2026 09:15:51 +0000 Subject: [PATCH] fix: prevent subagent telemetry from overwriting main agent footer context Root cause: PR #1835 accidentally overwrote PR #1912's correct telemetry isolation during a merge conflict resolution. This restores the original guard logic so subagent GeminiChat instances (which don't receive a telemetryService param) no longer write to the global uiTelemetryService. - Remove unused uiTelemetryService import from geminiChat.ts - Guard telemetry calls with this.telemetryService checks - Add test verifying subagent isolation Co-authored-by: Qwen-Coder --- packages/core/src/core/geminiChat.test.ts | 42 +++++++++++++++++++++++ packages/core/src/core/geminiChat.ts | 13 +++---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/packages/core/src/core/geminiChat.test.ts b/packages/core/src/core/geminiChat.test.ts index 2f9e2d1076..4a47813881 100644 --- a/packages/core/src/core/geminiChat.test.ts +++ b/packages/core/src/core/geminiChat.test.ts @@ -788,6 +788,48 @@ describe('GeminiChat', async () => { ); }); + it('should not update global telemetry when no telemetryService is provided (subagent isolation)', async () => { + // Simulate a subagent GeminiChat: created without a telemetryService + const subagentChat = new GeminiChat(mockConfig, config, []); + + const response = (async function* () { + yield { + candidates: [ + { + content: { + parts: [{ text: 'subagent response' }], + role: 'model', + }, + finishReason: 'STOP', + index: 0, + safetyRatings: [], + }, + ], + text: () => 'subagent response', + usageMetadata: { + promptTokenCount: 12000, + candidatesTokenCount: 500, + totalTokenCount: 12500, + }, + } as unknown as GenerateContentResponse; + })(); + vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue( + response, + ); + + const stream = await subagentChat.sendMessageStream( + 'test-model', + { message: 'subagent task' }, + 'prompt-id-subagent', + ); + for await (const _ of stream) { + // consume stream + } + + // The global uiTelemetryService must NOT be called by subagent chats + expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled(); + }); + it('should keep parts with thoughtSignature when consolidating history', async () => { const stream = (async function* () { yield { diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index db2d0b8033..522deb0397 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -35,7 +35,6 @@ import { ContentRetryFailureEvent, } from '../telemetry/types.js'; import type { UiTelemetryService } from '../telemetry/uiTelemetry.js'; -import { uiTelemetryService } from '../telemetry/uiTelemetry.js'; const debugLogger = createDebugLogger('QWEN_CODE_CHAT'); @@ -659,15 +658,11 @@ export class GeminiChat { // Some providers omit total_tokens or return 0 in streaming usage chunks. const lastPromptTokenCount = usageMetadata.totalTokenCount || usageMetadata.promptTokenCount; - if (lastPromptTokenCount) { - (this.telemetryService ?? uiTelemetryService).setLastPromptTokenCount( - lastPromptTokenCount, - ); + if (lastPromptTokenCount && this.telemetryService) { + this.telemetryService.setLastPromptTokenCount(lastPromptTokenCount); } - if (usageMetadata.cachedContentTokenCount) { - ( - this.telemetryService ?? uiTelemetryService - ).setLastCachedContentTokenCount( + if (usageMetadata.cachedContentTokenCount && this.telemetryService) { + this.telemetryService.setLastCachedContentTokenCount( usageMetadata.cachedContentTokenCount, ); }