Skip to content

Commit 0f6b5b1

Browse files
Mingholyandywinnock
authored andcommitted
fix: add missing trace info and cancellation events (QwenLM#791)
* fix: add missing trace info and cancellation events * fix: re-organize tool/request cancellation logging
1 parent d52a611 commit 0f6b5b1

File tree

14 files changed

+213
-5
lines changed

14 files changed

+213
-5
lines changed

packages/cli/src/ui/hooks/useGeminiStream.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ const MockedGeminiClientClass = vi.hoisted(() =>
5454
const MockedUserPromptEvent = vi.hoisted(() =>
5555
vi.fn().mockImplementation(() => {}),
5656
);
57+
const MockedApiCancelEvent = vi.hoisted(() =>
58+
vi.fn().mockImplementation(() => {}),
59+
);
5760
const mockParseAndFormatApiError = vi.hoisted(() => vi.fn());
61+
const mockLogApiCancel = vi.hoisted(() => vi.fn());
5862

5963
// Vision auto-switch mocks (hoisted)
6064
const mockHandleVisionSwitch = vi.hoisted(() =>
@@ -71,7 +75,9 @@ vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => {
7175
GitService: vi.fn(),
7276
GeminiClient: MockedGeminiClientClass,
7377
UserPromptEvent: MockedUserPromptEvent,
78+
ApiCancelEvent: MockedApiCancelEvent,
7479
parseAndFormatApiError: mockParseAndFormatApiError,
80+
logApiCancel: mockLogApiCancel,
7581
};
7682
});
7783

packages/cli/src/ui/hooks/useGeminiStream.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
ConversationFinishedEvent,
3232
ApprovalMode,
3333
parseAndFormatApiError,
34+
logApiCancel,
35+
ApiCancelEvent,
3436
} from '@qwen-code/qwen-code-core';
3537
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
3638
import type {
@@ -223,6 +225,16 @@ export const useGeminiStream = (
223225
turnCancelledRef.current = true;
224226
isSubmittingQueryRef.current = false;
225227
abortControllerRef.current?.abort();
228+
229+
// Log API cancellation
230+
const prompt_id = config.getSessionId() + '########' + getPromptCount();
231+
const cancellationEvent = new ApiCancelEvent(
232+
config.getModel(),
233+
prompt_id,
234+
config.getContentGeneratorConfig()?.authType,
235+
);
236+
logApiCancel(config, cancellationEvent);
237+
226238
if (pendingHistoryItemRef.current) {
227239
addItem(pendingHistoryItemRef.current, Date.now());
228240
}
@@ -242,6 +254,8 @@ export const useGeminiStream = (
242254
setPendingHistoryItem,
243255
onCancelSubmit,
244256
pendingHistoryItemRef,
257+
config,
258+
getPromptCount,
245259
]);
246260

247261
useKeypress(
@@ -448,6 +462,7 @@ export const useGeminiStream = (
448462
if (turnCancelledRef.current) {
449463
return;
450464
}
465+
451466
if (pendingHistoryItemRef.current) {
452467
if (pendingHistoryItemRef.current.type === 'tool_group') {
453468
const updatedTools = pendingHistoryItemRef.current.tools.map(

packages/cli/src/zed-integration/zedIntegration.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ class Session {
349349
function_name: fc.name ?? '',
350350
function_args: args,
351351
duration_ms: durationMs,
352+
status: 'error',
352353
success: false,
353354
error: error.message,
354355
tool_type:
@@ -467,6 +468,7 @@ class Session {
467468
function_name: fc.name,
468469
function_args: args,
469470
duration_ms: durationMs,
471+
status: 'success',
470472
success: true,
471473
prompt_id: promptId,
472474
tool_type:

packages/core/src/core/coreToolScheduler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ export class CoreToolScheduler {
401401
}
402402
}
403403

404-
return {
404+
const cancelledCall = {
405405
request: currentCall.request,
406406
tool: toolInstance,
407407
invocation,
@@ -426,6 +426,8 @@ export class CoreToolScheduler {
426426
durationMs,
427427
outcome,
428428
} as CancelledToolCall;
429+
430+
return cancelledCall;
429431
}
430432
case 'validating':
431433
return {

packages/core/src/core/openaiContentGenerator/pipeline.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,12 @@ export class ContentGenerationPipeline {
443443
mergedResponse.usageMetadata = lastResponse.usageMetadata;
444444
}
445445

446+
// Copy other essential properties from the current response
447+
mergedResponse.responseId = response.responseId;
448+
mergedResponse.createTime = response.createTime;
449+
mergedResponse.modelVersion = response.modelVersion;
450+
mergedResponse.promptFeedback = response.promptFeedback;
451+
446452
// Update the collected responses with the merged response
447453
collectedGeminiResponses[collectedGeminiResponses.length - 1] =
448454
mergedResponse;

packages/core/src/core/turn.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export interface ToolCallRequestInfo {
8585
args: Record<string, unknown>;
8686
isClientInitiated: boolean;
8787
prompt_id: string;
88+
response_id?: string;
8889
}
8990

9091
export interface ToolCallResponseInfo {
@@ -203,6 +204,7 @@ export class Turn {
203204
readonly pendingToolCalls: ToolCallRequestInfo[];
204205
private debugResponses: GenerateContentResponse[];
205206
finishReason: FinishReason | undefined;
207+
private currentResponseId?: string;
206208

207209
constructor(
208210
private readonly chat: GeminiChat,
@@ -248,6 +250,11 @@ export class Turn {
248250

249251
this.debugResponses.push(resp);
250252

253+
// Track the current response ID for tool call correlation
254+
if (resp.responseId) {
255+
this.currentResponseId = resp.responseId;
256+
}
257+
251258
const thoughtPart = resp.candidates?.[0]?.content?.parts?.[0];
252259
if (thoughtPart?.thought) {
253260
// Thought always has a bold "subject" part enclosed in double asterisks
@@ -347,6 +354,7 @@ export class Turn {
347354
args,
348355
isClientInitiated: false,
349356
prompt_id: this.prompt_id,
357+
response_id: this.currentResponseId,
350358
};
351359

352360
this.pendingToolCalls.push(toolCallRequest);

packages/core/src/subagents/subagent.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,7 @@ export class SubAgentScope {
381381
let roundText = '';
382382
let lastUsage: GenerateContentResponseUsageMetadata | undefined =
383383
undefined;
384+
let currentResponseId: string | undefined = undefined;
384385
for await (const streamEvent of responseStream) {
385386
if (abortController.signal.aborted) {
386387
this.terminateMode = SubagentTerminateMode.CANCELLED;
@@ -395,6 +396,10 @@ export class SubAgentScope {
395396
// Handle chunk events
396397
if (streamEvent.type === 'chunk') {
397398
const resp = streamEvent.value;
399+
// Track the response ID for tool call correlation
400+
if (resp.responseId) {
401+
currentResponseId = resp.responseId;
402+
}
398403
if (resp.functionCalls) functionCalls.push(...resp.functionCalls);
399404
const content = resp.candidates?.[0]?.content;
400405
const parts = content?.parts || [];
@@ -455,6 +460,7 @@ export class SubAgentScope {
455460
abortController,
456461
promptId,
457462
turnCounter,
463+
currentResponseId,
458464
);
459465
} else {
460466
// No tool calls — treat this as the model's final answer.
@@ -543,6 +549,7 @@ export class SubAgentScope {
543549
* @param {FunctionCall[]} functionCalls - An array of `FunctionCall` objects to process.
544550
* @param {ToolRegistry} toolRegistry - The tool registry to look up and execute tools.
545551
* @param {AbortController} abortController - An `AbortController` to signal cancellation of tool executions.
552+
* @param {string} responseId - Optional API response ID for correlation with tool calls.
546553
* @returns {Promise<Content[]>} A promise that resolves to an array of `Content` parts representing the tool responses,
547554
* which are then used to update the chat history.
548555
*/
@@ -551,6 +558,7 @@ export class SubAgentScope {
551558
abortController: AbortController,
552559
promptId: string,
553560
currentRound: number,
561+
responseId?: string,
554562
): Promise<Content[]> {
555563
const toolResponseParts: Part[] = [];
556564

@@ -704,6 +712,7 @@ export class SubAgentScope {
704712
args,
705713
isClientInitiated: true,
706714
prompt_id: promptId,
715+
response_id: responseId,
707716
};
708717

709718
const description = this.getToolDescription(toolName, args);

packages/core/src/telemetry/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const EVENT_USER_PROMPT = 'qwen-code.user_prompt';
1010
export const EVENT_TOOL_CALL = 'qwen-code.tool_call';
1111
export const EVENT_API_REQUEST = 'qwen-code.api_request';
1212
export const EVENT_API_ERROR = 'qwen-code.api_error';
13+
export const EVENT_API_CANCEL = 'qwen-code.api_cancel';
1314
export const EVENT_API_RESPONSE = 'qwen-code.api_response';
1415
export const EVENT_CLI_CONFIG = 'qwen-code.config';
1516
export const EVENT_FLASH_FALLBACK = 'qwen-code.flash_fallback';

packages/core/src/telemetry/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export { SpanStatusCode, ValueType } from '@opentelemetry/api';
1717
export { SemanticAttributes } from '@opentelemetry/semantic-conventions';
1818
export {
1919
logApiError,
20+
logApiCancel,
2021
logApiRequest,
2122
logApiResponse,
2223
logChatCompression,
@@ -35,6 +36,7 @@ export {
3536
} from './sdk.js';
3637
export {
3738
ApiErrorEvent,
39+
ApiCancelEvent,
3840
ApiRequestEvent,
3941
ApiResponseEvent,
4042
ConversationFinishedEvent,
@@ -54,4 +56,5 @@ export type {
5456
TelemetryEvent,
5557
} from './types.js';
5658
export * from './uiTelemetry.js';
59+
export { QwenLogger } from './qwen-logger/qwen-logger.js';
5760
export { DEFAULT_OTLP_ENDPOINT, DEFAULT_TELEMETRY_TARGET };

packages/core/src/telemetry/loggers.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ describe('loggers', () => {
550550
2,
551551
),
552552
duration_ms: 100,
553+
status: 'success',
553554
success: true,
554555
decision: ToolCallDecision.ACCEPT,
555556
prompt_id: 'prompt-id-1',
@@ -619,6 +620,7 @@ describe('loggers', () => {
619620
2,
620621
),
621622
duration_ms: 100,
623+
status: 'error',
622624
success: false,
623625
decision: ToolCallDecision.REJECT,
624626
prompt_id: 'prompt-id-2',
@@ -691,6 +693,7 @@ describe('loggers', () => {
691693
2,
692694
),
693695
duration_ms: 100,
696+
status: 'success',
694697
success: true,
695698
decision: ToolCallDecision.MODIFY,
696699
prompt_id: 'prompt-id-3',
@@ -762,6 +765,7 @@ describe('loggers', () => {
762765
2,
763766
),
764767
duration_ms: 100,
768+
status: 'success',
765769
success: true,
766770
prompt_id: 'prompt-id-4',
767771
tool_type: 'native',
@@ -834,6 +838,7 @@ describe('loggers', () => {
834838
2,
835839
),
836840
duration_ms: 100,
841+
status: 'error',
837842
success: false,
838843
error: 'test-error',
839844
'error.message': 'test-error',

0 commit comments

Comments
 (0)