mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
fix: add missing trace info and cancellation events
This commit is contained in:
@@ -31,6 +31,9 @@ import {
|
||||
ConversationFinishedEvent,
|
||||
ApprovalMode,
|
||||
parseAndFormatApiError,
|
||||
logUserCancellation,
|
||||
UserCancellationEvent,
|
||||
UserCancellationType,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
|
||||
import type {
|
||||
@@ -448,6 +451,17 @@ export const useGeminiStream = (
|
||||
if (turnCancelledRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Log user cancellation event
|
||||
const prompt_id = config.getSessionId() + '########' + getPromptCount();
|
||||
const cancellationEvent = new UserCancellationEvent(
|
||||
UserCancellationType.REQUEST_CANCELLED,
|
||||
{
|
||||
prompt_id,
|
||||
},
|
||||
);
|
||||
logUserCancellation(config, cancellationEvent);
|
||||
|
||||
if (pendingHistoryItemRef.current) {
|
||||
if (pendingHistoryItemRef.current.type === 'tool_group') {
|
||||
const updatedTools = pendingHistoryItemRef.current.tools.map(
|
||||
@@ -475,7 +489,14 @@ export const useGeminiStream = (
|
||||
setIsResponding(false);
|
||||
setThought(null); // Reset thought when user cancels
|
||||
},
|
||||
[addItem, pendingHistoryItemRef, setPendingHistoryItem, setThought],
|
||||
[
|
||||
addItem,
|
||||
pendingHistoryItemRef,
|
||||
setPendingHistoryItem,
|
||||
setThought,
|
||||
config,
|
||||
getPromptCount,
|
||||
],
|
||||
);
|
||||
|
||||
const handleErrorEvent = useCallback(
|
||||
|
||||
@@ -23,6 +23,9 @@ import {
|
||||
logToolCall,
|
||||
ToolErrorType,
|
||||
ToolCallEvent,
|
||||
logUserCancellation,
|
||||
UserCancellationEvent,
|
||||
UserCancellationType,
|
||||
} from '../index.js';
|
||||
import type { Part, PartListUnion } from '@google/genai';
|
||||
import { getResponseTextFromParts } from '../utils/generateContentResponseUtilities.js';
|
||||
@@ -401,7 +404,7 @@ export class CoreToolScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
const cancelledCall = {
|
||||
request: currentCall.request,
|
||||
tool: toolInstance,
|
||||
invocation,
|
||||
@@ -426,6 +429,11 @@ export class CoreToolScheduler {
|
||||
durationMs,
|
||||
outcome,
|
||||
} as CancelledToolCall;
|
||||
|
||||
// Log the tool call cancellation
|
||||
this.logToolCallCancellation(cancelledCall);
|
||||
|
||||
return cancelledCall;
|
||||
}
|
||||
case 'validating':
|
||||
return {
|
||||
@@ -1011,6 +1019,17 @@ export class CoreToolScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
private logToolCallCancellation(toolCall: ToolCall): void {
|
||||
const cancellationEvent = new UserCancellationEvent(
|
||||
UserCancellationType.TOOL_CALL_CANCELLED,
|
||||
{
|
||||
prompt_id: toolCall.request.prompt_id,
|
||||
tool_name: toolCall.request.name,
|
||||
},
|
||||
);
|
||||
logUserCancellation(this.config, cancellationEvent);
|
||||
}
|
||||
|
||||
private setToolCallOutcome(callId: string, outcome: ToolConfirmationOutcome) {
|
||||
this.toolCalls = this.toolCalls.map((call) => {
|
||||
if (call.request.callId !== callId) return call;
|
||||
|
||||
@@ -221,6 +221,12 @@ export class ContentGenerationPipeline {
|
||||
mergedResponse.usageMetadata = lastResponse.usageMetadata;
|
||||
}
|
||||
|
||||
// Copy other essential properties from the current response
|
||||
mergedResponse.responseId = response.responseId;
|
||||
mergedResponse.createTime = response.createTime;
|
||||
mergedResponse.modelVersion = response.modelVersion;
|
||||
mergedResponse.promptFeedback = response.promptFeedback;
|
||||
|
||||
// Update the collected responses with the merged response
|
||||
collectedGeminiResponses[collectedGeminiResponses.length - 1] =
|
||||
mergedResponse;
|
||||
|
||||
@@ -25,6 +25,7 @@ export const EVENT_CONVERSATION_FINISHED = 'qwen-code.conversation_finished';
|
||||
export const EVENT_MALFORMED_JSON_RESPONSE =
|
||||
'qwen-code.malformed_json_response';
|
||||
export const EVENT_SUBAGENT_EXECUTION = 'qwen-code.subagent_execution';
|
||||
export const EVENT_USER_CANCELLATION = 'qwen-code.user_cancellation';
|
||||
|
||||
export const METRIC_TOOL_CALL_COUNT = 'qwen-code.tool.call.count';
|
||||
export const METRIC_TOOL_CALL_LATENCY = 'qwen-code.tool.call.latency';
|
||||
|
||||
@@ -26,6 +26,7 @@ export {
|
||||
logKittySequenceOverflow,
|
||||
logSlashCommand,
|
||||
logToolCall,
|
||||
logUserCancellation,
|
||||
logUserPrompt,
|
||||
} from './loggers.js';
|
||||
export {
|
||||
@@ -46,6 +47,8 @@ export {
|
||||
SlashCommandStatus,
|
||||
StartSessionEvent,
|
||||
ToolCallEvent,
|
||||
UserCancellationEvent,
|
||||
UserCancellationType,
|
||||
UserPromptEvent,
|
||||
} from './types.js';
|
||||
export type {
|
||||
@@ -54,4 +57,5 @@ export type {
|
||||
TelemetryEvent,
|
||||
} from './types.js';
|
||||
export * from './uiTelemetry.js';
|
||||
export { QwenLogger } from './qwen-logger/qwen-logger.js';
|
||||
export { DEFAULT_OTLP_ENDPOINT, DEFAULT_TELEMETRY_TARGET };
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
EVENT_SLASH_COMMAND,
|
||||
EVENT_SUBAGENT_EXECUTION,
|
||||
EVENT_TOOL_CALL,
|
||||
EVENT_USER_CANCELLATION,
|
||||
EVENT_USER_PROMPT,
|
||||
SERVICE_NAME,
|
||||
} from './constants.js';
|
||||
@@ -62,6 +63,7 @@ import type {
|
||||
StartSessionEvent,
|
||||
SubagentExecutionEvent,
|
||||
ToolCallEvent,
|
||||
UserCancellationEvent,
|
||||
UserPromptEvent,
|
||||
} from './types.js';
|
||||
import { type UiEvent, uiTelemetryService } from './uiTelemetry.js';
|
||||
@@ -591,3 +593,25 @@ export function logSubagentExecution(
|
||||
event.terminate_reason,
|
||||
);
|
||||
}
|
||||
|
||||
export function logUserCancellation(
|
||||
config: Config,
|
||||
event: UserCancellationEvent,
|
||||
): void {
|
||||
QwenLogger.getInstance(config)?.logUserCancellationEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_USER_CANCELLATION,
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `User cancellation: ${event.cancellation_type}.`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
IdeConnectionEvent,
|
||||
KittySequenceOverflowEvent,
|
||||
IdeConnectionType,
|
||||
UserCancellationEvent,
|
||||
UserCancellationType,
|
||||
} from '../types.js';
|
||||
import type { RumEvent } from './event-types.js';
|
||||
|
||||
@@ -318,6 +320,85 @@ describe('QwenLogger', () => {
|
||||
|
||||
expect(flushSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should log user cancellation events for request cancellation', () => {
|
||||
const logger = QwenLogger.getInstance(mockConfig)!;
|
||||
const enqueueSpy = vi.spyOn(logger, 'enqueueLogEvent');
|
||||
|
||||
const event = new UserCancellationEvent(
|
||||
UserCancellationType.REQUEST_CANCELLED,
|
||||
{
|
||||
prompt_id: 'test-prompt-123',
|
||||
},
|
||||
);
|
||||
|
||||
logger.logUserCancellationEvent(event);
|
||||
|
||||
expect(enqueueSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event_type: 'action',
|
||||
type: 'cancellation',
|
||||
name: 'user_cancellation',
|
||||
properties: {
|
||||
cancellation_type: UserCancellationType.REQUEST_CANCELLED,
|
||||
prompt_id: 'test-prompt-123',
|
||||
tool_name: undefined,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log user cancellation events for tool call cancellation', () => {
|
||||
const logger = QwenLogger.getInstance(mockConfig)!;
|
||||
const enqueueSpy = vi.spyOn(logger, 'enqueueLogEvent');
|
||||
|
||||
const event = new UserCancellationEvent(
|
||||
UserCancellationType.TOOL_CALL_CANCELLED,
|
||||
{
|
||||
prompt_id: 'test-prompt-456',
|
||||
tool_name: 'read_file',
|
||||
},
|
||||
);
|
||||
|
||||
logger.logUserCancellationEvent(event);
|
||||
|
||||
expect(enqueueSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event_type: 'action',
|
||||
type: 'cancellation',
|
||||
name: 'user_cancellation',
|
||||
properties: {
|
||||
cancellation_type: UserCancellationType.TOOL_CALL_CANCELLED,
|
||||
prompt_id: 'test-prompt-456',
|
||||
tool_name: 'read_file',
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log user cancellation events with minimal data', () => {
|
||||
const logger = QwenLogger.getInstance(mockConfig)!;
|
||||
const enqueueSpy = vi.spyOn(logger, 'enqueueLogEvent');
|
||||
|
||||
const event = new UserCancellationEvent(
|
||||
UserCancellationType.REQUEST_CANCELLED,
|
||||
);
|
||||
|
||||
logger.logUserCancellationEvent(event);
|
||||
|
||||
expect(enqueueSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
event_type: 'action',
|
||||
type: 'cancellation',
|
||||
name: 'user_cancellation',
|
||||
properties: {
|
||||
cancellation_type: UserCancellationType.REQUEST_CANCELLED,
|
||||
prompt_id: undefined,
|
||||
tool_name: undefined,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flush timing', () => {
|
||||
|
||||
@@ -687,6 +687,25 @@ export class QwenLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logUserCancellationEvent(
|
||||
event: import('../types.js').UserCancellationEvent,
|
||||
): void {
|
||||
const rumEvent = this.createActionEvent(
|
||||
'cancellation',
|
||||
'user_cancellation',
|
||||
{
|
||||
properties: {
|
||||
cancellation_type: event.cancellation_type,
|
||||
prompt_id: event.prompt_id,
|
||||
tool_name: event.tool_name,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
this.enqueueLogEvent(rumEvent);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logEndSessionEvent(_event: EndSessionEvent): void {
|
||||
const applicationEvent = this.createViewEvent('session', 'session_end', {});
|
||||
|
||||
|
||||
@@ -535,6 +535,33 @@ export class SubagentExecutionEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export enum UserCancellationType {
|
||||
REQUEST_CANCELLED = 'request_cancelled',
|
||||
TOOL_CALL_CANCELLED = 'tool_call_cancelled',
|
||||
}
|
||||
|
||||
export class UserCancellationEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'user_cancellation';
|
||||
'event.timestamp': string;
|
||||
cancellation_type: UserCancellationType;
|
||||
prompt_id?: string;
|
||||
tool_name?: string;
|
||||
|
||||
constructor(
|
||||
cancellation_type: UserCancellationType,
|
||||
options?: {
|
||||
prompt_id?: string;
|
||||
tool_name?: string;
|
||||
},
|
||||
) {
|
||||
this['event.name'] = 'user_cancellation';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.cancellation_type = cancellation_type;
|
||||
this.prompt_id = options?.prompt_id;
|
||||
this.tool_name = options?.tool_name;
|
||||
}
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| StartSessionEvent
|
||||
| EndSessionEvent
|
||||
@@ -555,4 +582,5 @@ export type TelemetryEvent =
|
||||
| InvalidChunkEvent
|
||||
| ContentRetryEvent
|
||||
| ContentRetryFailureEvent
|
||||
| SubagentExecutionEvent;
|
||||
| SubagentExecutionEvent
|
||||
| UserCancellationEvent;
|
||||
|
||||
Reference in New Issue
Block a user