mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-13 05:12:55 -07:00
feat(telemetry): Add content length to tool calls and log tool output truncation event (#8014)
This commit is contained in:
@@ -194,6 +194,7 @@ Logs are timestamped records of specific events. The following events are logged
|
|||||||
- `decision` (string: "accept", "reject", "auto_accept", or "modify", if applicable)
|
- `decision` (string: "accept", "reject", "auto_accept", or "modify", if applicable)
|
||||||
- `error` (if applicable)
|
- `error` (if applicable)
|
||||||
- `error_type` (if applicable)
|
- `error_type` (if applicable)
|
||||||
|
- `content_length` (int, if applicable)
|
||||||
- `metadata` (if applicable, dictionary of string -> any)
|
- `metadata` (if applicable, dictionary of string -> any)
|
||||||
|
|
||||||
- `gemini_cli.file_operation`: This event occurs for each file operation.
|
- `gemini_cli.file_operation`: This event occurs for each file operation.
|
||||||
@@ -238,6 +239,15 @@ Logs are timestamped records of specific events. The following events are logged
|
|||||||
- `response_text` (if applicable)
|
- `response_text` (if applicable)
|
||||||
- `auth_type`
|
- `auth_type`
|
||||||
|
|
||||||
|
- `gemini_cli.tool_output_truncated`: This event occurs when the output of a tool call is too large and gets truncated.
|
||||||
|
- **Attributes**:
|
||||||
|
- `tool_name` (string)
|
||||||
|
- `original_content_length` (int)
|
||||||
|
- `truncated_content_length` (int)
|
||||||
|
- `threshold` (int)
|
||||||
|
- `lines` (int)
|
||||||
|
- `prompt_id` (string)
|
||||||
|
|
||||||
- `gemini_cli.malformed_json_response`: This event occurs when a `generateJson` response from Gemini API cannot be parsed as a json.
|
- `gemini_cli.malformed_json_response`: This event occurs when a `generateJson` response from Gemini API cannot be parsed as a json.
|
||||||
- **Attributes**:
|
- **Attributes**:
|
||||||
- `model`
|
- `model`
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import {
|
|||||||
ToolErrorType,
|
ToolErrorType,
|
||||||
ToolCallEvent,
|
ToolCallEvent,
|
||||||
ShellTool,
|
ShellTool,
|
||||||
|
logToolOutputTruncated,
|
||||||
|
ToolOutputTruncatedEvent,
|
||||||
} from '../index.js';
|
} from '../index.js';
|
||||||
import type { Part, PartListUnion } from '@google/genai';
|
import type { Part, PartListUnion } from '@google/genai';
|
||||||
import { getResponseTextFromParts } from '../utils/generateContentResponseUtilities.js';
|
import { getResponseTextFromParts } from '../utils/generateContentResponseUtilities.js';
|
||||||
@@ -245,6 +247,7 @@ const createErrorResponse = (
|
|||||||
],
|
],
|
||||||
resultDisplay: error.message,
|
resultDisplay: error.message,
|
||||||
errorType,
|
errorType,
|
||||||
|
contentLength: error.message.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function truncateAndSaveToFile(
|
export async function truncateAndSaveToFile(
|
||||||
@@ -461,6 +464,7 @@ export class CoreToolScheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const errorMessage = `[Operation Cancelled] Reason: ${auxiliaryData}`;
|
||||||
return {
|
return {
|
||||||
request: currentCall.request,
|
request: currentCall.request,
|
||||||
tool: toolInstance,
|
tool: toolInstance,
|
||||||
@@ -474,7 +478,7 @@ export class CoreToolScheduler {
|
|||||||
id: currentCall.request.callId,
|
id: currentCall.request.callId,
|
||||||
name: currentCall.request.name,
|
name: currentCall.request.name,
|
||||||
response: {
|
response: {
|
||||||
error: `[Operation Cancelled] Reason: ${auxiliaryData}`,
|
error: errorMessage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -482,6 +486,7 @@ export class CoreToolScheduler {
|
|||||||
resultDisplay,
|
resultDisplay,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
|
contentLength: errorMessage.length,
|
||||||
},
|
},
|
||||||
durationMs,
|
durationMs,
|
||||||
outcome,
|
outcome,
|
||||||
@@ -975,6 +980,8 @@ export class CoreToolScheduler {
|
|||||||
if (toolResult.error === undefined) {
|
if (toolResult.error === undefined) {
|
||||||
let content = toolResult.llmContent;
|
let content = toolResult.llmContent;
|
||||||
let outputFile: string | undefined = undefined;
|
let outputFile: string | undefined = undefined;
|
||||||
|
const contentLength =
|
||||||
|
typeof content === 'string' ? content.length : undefined;
|
||||||
if (
|
if (
|
||||||
typeof content === 'string' &&
|
typeof content === 'string' &&
|
||||||
toolName === ShellTool.Name &&
|
toolName === ShellTool.Name &&
|
||||||
@@ -982,13 +989,34 @@ export class CoreToolScheduler {
|
|||||||
this.config.getTruncateToolOutputThreshold() > 0 &&
|
this.config.getTruncateToolOutputThreshold() > 0 &&
|
||||||
this.config.getTruncateToolOutputLines() > 0
|
this.config.getTruncateToolOutputLines() > 0
|
||||||
) {
|
) {
|
||||||
({ content, outputFile } = await truncateAndSaveToFile(
|
const originalContentLength = content.length;
|
||||||
|
const threshold = this.config.getTruncateToolOutputThreshold();
|
||||||
|
const lines = this.config.getTruncateToolOutputLines();
|
||||||
|
const truncatedResult = await truncateAndSaveToFile(
|
||||||
content,
|
content,
|
||||||
callId,
|
callId,
|
||||||
this.config.storage.getProjectTempDir(),
|
this.config.storage.getProjectTempDir(),
|
||||||
this.config.getTruncateToolOutputThreshold(),
|
threshold,
|
||||||
this.config.getTruncateToolOutputLines(),
|
lines,
|
||||||
));
|
);
|
||||||
|
content = truncatedResult.content;
|
||||||
|
outputFile = truncatedResult.outputFile;
|
||||||
|
|
||||||
|
if (outputFile) {
|
||||||
|
logToolOutputTruncated(
|
||||||
|
this.config,
|
||||||
|
new ToolOutputTruncatedEvent(
|
||||||
|
scheduledCall.request.prompt_id,
|
||||||
|
{
|
||||||
|
toolName,
|
||||||
|
originalContentLength,
|
||||||
|
truncatedContentLength: content.length,
|
||||||
|
threshold,
|
||||||
|
lines,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = convertToFunctionResponse(
|
const response = convertToFunctionResponse(
|
||||||
@@ -1003,6 +1031,7 @@ export class CoreToolScheduler {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
outputFile,
|
outputFile,
|
||||||
|
contentLength,
|
||||||
};
|
};
|
||||||
this.setStatusInternal(callId, 'success', successResponse);
|
this.setStatusInternal(callId, 'success', successResponse);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ describe('executeToolCall', () => {
|
|||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
outputFile: undefined,
|
outputFile: undefined,
|
||||||
resultDisplay: 'Success!',
|
resultDisplay: 'Success!',
|
||||||
|
contentLength:
|
||||||
|
typeof toolResult.llmContent === 'string'
|
||||||
|
? toolResult.llmContent.length
|
||||||
|
: undefined,
|
||||||
responseParts: [
|
responseParts: [
|
||||||
{
|
{
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
@@ -127,6 +131,7 @@ describe('executeToolCall', () => {
|
|||||||
error: new Error(expectedErrorMessage),
|
error: new Error(expectedErrorMessage),
|
||||||
errorType: ToolErrorType.TOOL_NOT_REGISTERED,
|
errorType: ToolErrorType.TOOL_NOT_REGISTERED,
|
||||||
resultDisplay: expectedErrorMessage,
|
resultDisplay: expectedErrorMessage,
|
||||||
|
contentLength: expectedErrorMessage.length,
|
||||||
responseParts: [
|
responseParts: [
|
||||||
{
|
{
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
@@ -176,6 +181,7 @@ describe('executeToolCall', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
resultDisplay: 'Invalid parameters',
|
resultDisplay: 'Invalid parameters',
|
||||||
|
contentLength: 'Invalid parameters'.length,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,6 +225,7 @@ describe('executeToolCall', () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
resultDisplay: 'Execution failed',
|
resultDisplay: 'Execution failed',
|
||||||
|
contentLength: 'Execution failed'.length,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,6 +253,7 @@ describe('executeToolCall', () => {
|
|||||||
error: new Error('Something went very wrong'),
|
error: new Error('Something went very wrong'),
|
||||||
errorType: ToolErrorType.UNHANDLED_EXCEPTION,
|
errorType: ToolErrorType.UNHANDLED_EXCEPTION,
|
||||||
resultDisplay: 'Something went very wrong',
|
resultDisplay: 'Something went very wrong',
|
||||||
|
contentLength: 'Something went very wrong'.length,
|
||||||
responseParts: [
|
responseParts: [
|
||||||
{
|
{
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
@@ -288,6 +296,7 @@ describe('executeToolCall', () => {
|
|||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
outputFile: undefined,
|
outputFile: undefined,
|
||||||
resultDisplay: 'Image processed',
|
resultDisplay: 'Image processed',
|
||||||
|
contentLength: undefined,
|
||||||
responseParts: [
|
responseParts: [
|
||||||
{
|
{
|
||||||
functionResponse: {
|
functionResponse: {
|
||||||
@@ -302,4 +311,56 @@ describe('executeToolCall', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should calculate contentLength for a string llmContent', async () => {
|
||||||
|
const request: ToolCallRequestInfo = {
|
||||||
|
callId: 'call7',
|
||||||
|
name: 'testTool',
|
||||||
|
args: {},
|
||||||
|
isClientInitiated: false,
|
||||||
|
prompt_id: 'prompt-id-7',
|
||||||
|
};
|
||||||
|
const toolResult: ToolResult = {
|
||||||
|
llmContent: 'This is a test string.',
|
||||||
|
returnDisplay: 'String returned',
|
||||||
|
};
|
||||||
|
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
||||||
|
mockTool.executeFn.mockReturnValue(toolResult);
|
||||||
|
|
||||||
|
const response = await executeToolCall(
|
||||||
|
mockConfig,
|
||||||
|
request,
|
||||||
|
abortController.signal,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.contentLength).toBe(
|
||||||
|
typeof toolResult.llmContent === 'string'
|
||||||
|
? toolResult.llmContent.length
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have undefined contentLength for array llmContent with no string parts', async () => {
|
||||||
|
const request: ToolCallRequestInfo = {
|
||||||
|
callId: 'call8',
|
||||||
|
name: 'testTool',
|
||||||
|
args: {},
|
||||||
|
isClientInitiated: false,
|
||||||
|
prompt_id: 'prompt-id-8',
|
||||||
|
};
|
||||||
|
const toolResult: ToolResult = {
|
||||||
|
llmContent: [{ inlineData: { mimeType: 'image/png', data: 'fakedata' } }],
|
||||||
|
returnDisplay: 'Image data returned',
|
||||||
|
};
|
||||||
|
vi.mocked(mockToolRegistry.getTool).mockReturnValue(mockTool);
|
||||||
|
mockTool.executeFn.mockReturnValue(toolResult);
|
||||||
|
|
||||||
|
const response = await executeToolCall(
|
||||||
|
mockConfig,
|
||||||
|
request,
|
||||||
|
abortController.signal,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(response.contentLength).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export interface ToolCallResponseInfo {
|
|||||||
error: Error | undefined;
|
error: Error | undefined;
|
||||||
errorType: ToolErrorType | undefined;
|
errorType: ToolErrorType | undefined;
|
||||||
outputFile?: string | undefined;
|
outputFile?: string | undefined;
|
||||||
|
contentLength?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerToolCallConfirmationDetails {
|
export interface ServerToolCallConfirmationDetails {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import type {
|
|||||||
ContentRetryEvent,
|
ContentRetryEvent,
|
||||||
ContentRetryFailureEvent,
|
ContentRetryFailureEvent,
|
||||||
ExtensionInstallEvent,
|
ExtensionInstallEvent,
|
||||||
|
ToolOutputTruncatedEvent,
|
||||||
} from '../types.js';
|
} from '../types.js';
|
||||||
import { EventMetadataKey } from './event-metadata-key.js';
|
import { EventMetadataKey } from './event-metadata-key.js';
|
||||||
import type { Config } from '../../config/config.js';
|
import type { Config } from '../../config/config.js';
|
||||||
@@ -58,6 +59,7 @@ export enum EventNames {
|
|||||||
CONTENT_RETRY = 'content_retry',
|
CONTENT_RETRY = 'content_retry',
|
||||||
CONTENT_RETRY_FAILURE = 'content_retry_failure',
|
CONTENT_RETRY_FAILURE = 'content_retry_failure',
|
||||||
EXTENSION_INSTALL = 'extension_install',
|
EXTENSION_INSTALL = 'extension_install',
|
||||||
|
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LogResponse {
|
export interface LogResponse {
|
||||||
@@ -464,6 +466,10 @@ export class ClearcutLogger {
|
|||||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_TYPE,
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_TYPE,
|
||||||
value: JSON.stringify(event.tool_type),
|
value: JSON.stringify(event.tool_type),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_CONTENT_LENGTH,
|
||||||
|
value: JSON.stringify(event.content_length),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (event.metadata) {
|
if (event.metadata) {
|
||||||
@@ -861,6 +867,39 @@ export class ClearcutLogger {
|
|||||||
this.flushIfNeeded();
|
this.flushIfNeeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logToolOutputTruncatedEvent(event: ToolOutputTruncatedEvent): void {
|
||||||
|
const data: EventValue[] = [
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
|
||||||
|
value: JSON.stringify(event.tool_name),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key:
|
||||||
|
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_ORIGINAL_LENGTH,
|
||||||
|
value: JSON.stringify(event.original_content_length),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key:
|
||||||
|
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_TRUNCATED_LENGTH,
|
||||||
|
value: JSON.stringify(event.truncated_content_length),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key:
|
||||||
|
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_THRESHOLD,
|
||||||
|
value: JSON.stringify(event.threshold),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_LINES,
|
||||||
|
value: JSON.stringify(event.lines),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
this.enqueueLogEvent(
|
||||||
|
this.createLogEvent(EventNames.TOOL_OUTPUT_TRUNCATED, data),
|
||||||
|
);
|
||||||
|
this.flushIfNeeded();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds default fields to data, and returns a new data array. This fields
|
* Adds default fields to data, and returns a new data array. This fields
|
||||||
* should exist on all log events.
|
* should exist on all log events.
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ export enum EventMetadataKey {
|
|||||||
// Logs the tool call error type, if any.
|
// Logs the tool call error type, if any.
|
||||||
GEMINI_CLI_TOOL_CALL_ERROR_TYPE = 19,
|
GEMINI_CLI_TOOL_CALL_ERROR_TYPE = 19,
|
||||||
|
|
||||||
|
// Logs the length of tool output
|
||||||
|
GEMINI_CLI_TOOL_CALL_CONTENT_LENGTH = 93,
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// GenAI API Request Event Keys
|
// GenAI API Request Event Keys
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
@@ -347,4 +350,20 @@ export enum EventMetadataKey {
|
|||||||
|
|
||||||
// Logs the status of the extension install.
|
// Logs the status of the extension install.
|
||||||
GEMINI_CLI_EXTENSION_INSTALL_STATUS = 88,
|
GEMINI_CLI_EXTENSION_INSTALL_STATUS = 88,
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Tool Output Truncated Event Keys
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
// Logs the original length of the tool output.
|
||||||
|
GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_ORIGINAL_LENGTH = 89,
|
||||||
|
|
||||||
|
// Logs the truncated length of the tool output.
|
||||||
|
GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_TRUNCATED_LENGTH = 90,
|
||||||
|
|
||||||
|
// Logs the threshold at which the tool output was truncated.
|
||||||
|
GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_THRESHOLD = 91,
|
||||||
|
|
||||||
|
// Logs the number of lines the tool output was truncated to.
|
||||||
|
GEMINI_CLI_TOOL_OUTPUT_TRUNCATED_LINES = 92,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export {
|
|||||||
logConversationFinishedEvent,
|
logConversationFinishedEvent,
|
||||||
logKittySequenceOverflow,
|
logKittySequenceOverflow,
|
||||||
logChatCompression,
|
logChatCompression,
|
||||||
|
logToolOutputTruncated,
|
||||||
} from './loggers.js';
|
} from './loggers.js';
|
||||||
export type { SlashCommandEvent, ChatCompressionEvent } from './types.js';
|
export type { SlashCommandEvent, ChatCompressionEvent } from './types.js';
|
||||||
export {
|
export {
|
||||||
@@ -44,6 +45,7 @@ export {
|
|||||||
ToolCallEvent,
|
ToolCallEvent,
|
||||||
ConversationFinishedEvent,
|
ConversationFinishedEvent,
|
||||||
KittySequenceOverflowEvent,
|
KittySequenceOverflowEvent,
|
||||||
|
ToolOutputTruncatedEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
export { makeSlashCommandEvent, makeChatCompressionEvent } from './types.js';
|
export { makeSlashCommandEvent, makeChatCompressionEvent } from './types.js';
|
||||||
export type { TelemetryEvent } from './types.js';
|
export type { TelemetryEvent } from './types.js';
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
logMalformedJsonResponse,
|
logMalformedJsonResponse,
|
||||||
logFileOperation,
|
logFileOperation,
|
||||||
logRipgrepFallback,
|
logRipgrepFallback,
|
||||||
|
logToolOutputTruncated,
|
||||||
} from './loggers.js';
|
} from './loggers.js';
|
||||||
import { ToolCallDecision } from './tool-call-decision.js';
|
import { ToolCallDecision } from './tool-call-decision.js';
|
||||||
import {
|
import {
|
||||||
@@ -57,6 +58,7 @@ import {
|
|||||||
MalformedJsonResponseEvent,
|
MalformedJsonResponseEvent,
|
||||||
makeChatCompressionEvent,
|
makeChatCompressionEvent,
|
||||||
FileOperationEvent,
|
FileOperationEvent,
|
||||||
|
ToolOutputTruncatedEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import * as metrics from './metrics.js';
|
import * as metrics from './metrics.js';
|
||||||
import { FileOperation } from './metrics.js';
|
import { FileOperation } from './metrics.js';
|
||||||
@@ -601,6 +603,7 @@ describe('loggers', () => {
|
|||||||
},
|
},
|
||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
|
contentLength: 13,
|
||||||
},
|
},
|
||||||
tool,
|
tool,
|
||||||
invocation: {} as AnyToolInvocation,
|
invocation: {} as AnyToolInvocation,
|
||||||
@@ -634,6 +637,7 @@ describe('loggers', () => {
|
|||||||
tool_type: 'native',
|
tool_type: 'native',
|
||||||
error: undefined,
|
error: undefined,
|
||||||
error_type: undefined,
|
error_type: undefined,
|
||||||
|
|
||||||
metadata: {
|
metadata: {
|
||||||
model_added_lines: 1,
|
model_added_lines: 1,
|
||||||
model_removed_lines: 2,
|
model_removed_lines: 2,
|
||||||
@@ -644,6 +648,7 @@ describe('loggers', () => {
|
|||||||
user_added_chars: 7,
|
user_added_chars: 7,
|
||||||
user_removed_chars: 8,
|
user_removed_chars: 8,
|
||||||
},
|
},
|
||||||
|
content_length: 13,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -681,6 +686,7 @@ describe('loggers', () => {
|
|||||||
resultDisplay: undefined,
|
resultDisplay: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
|
contentLength: undefined,
|
||||||
},
|
},
|
||||||
durationMs: 100,
|
durationMs: 100,
|
||||||
outcome: ToolConfirmationOutcome.Cancel,
|
outcome: ToolConfirmationOutcome.Cancel,
|
||||||
@@ -713,6 +719,7 @@ describe('loggers', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
error_type: undefined,
|
error_type: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
|
content_length: undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -751,6 +758,7 @@ describe('loggers', () => {
|
|||||||
resultDisplay: undefined,
|
resultDisplay: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
|
contentLength: 13,
|
||||||
},
|
},
|
||||||
outcome: ToolConfirmationOutcome.ModifyWithEditor,
|
outcome: ToolConfirmationOutcome.ModifyWithEditor,
|
||||||
tool: new EditTool(mockConfig),
|
tool: new EditTool(mockConfig),
|
||||||
@@ -785,6 +793,7 @@ describe('loggers', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
error_type: undefined,
|
error_type: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
|
content_length: 13,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -823,6 +832,7 @@ describe('loggers', () => {
|
|||||||
resultDisplay: undefined,
|
resultDisplay: undefined,
|
||||||
error: undefined,
|
error: undefined,
|
||||||
errorType: undefined,
|
errorType: undefined,
|
||||||
|
contentLength: 13,
|
||||||
},
|
},
|
||||||
tool: new EditTool(mockConfig),
|
tool: new EditTool(mockConfig),
|
||||||
invocation: {} as AnyToolInvocation,
|
invocation: {} as AnyToolInvocation,
|
||||||
@@ -856,6 +866,7 @@ describe('loggers', () => {
|
|||||||
error: undefined,
|
error: undefined,
|
||||||
error_type: undefined,
|
error_type: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
|
content_length: 13,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -876,6 +887,7 @@ describe('loggers', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should log a failed tool call with an error', () => {
|
it('should log a failed tool call with an error', () => {
|
||||||
|
const errorMessage = 'test-error';
|
||||||
const call: ErroredToolCall = {
|
const call: ErroredToolCall = {
|
||||||
status: 'error',
|
status: 'error',
|
||||||
request: {
|
request: {
|
||||||
@@ -892,11 +904,9 @@ describe('loggers', () => {
|
|||||||
callId: 'test-call-id',
|
callId: 'test-call-id',
|
||||||
responseParts: [{ text: 'test-response' }],
|
responseParts: [{ text: 'test-response' }],
|
||||||
resultDisplay: undefined,
|
resultDisplay: undefined,
|
||||||
error: {
|
error: new Error(errorMessage),
|
||||||
name: 'test-error-type',
|
|
||||||
message: 'test-error',
|
|
||||||
},
|
|
||||||
errorType: ToolErrorType.UNKNOWN,
|
errorType: ToolErrorType.UNKNOWN,
|
||||||
|
contentLength: errorMessage.length,
|
||||||
},
|
},
|
||||||
durationMs: 100,
|
durationMs: 100,
|
||||||
};
|
};
|
||||||
@@ -930,6 +940,7 @@ describe('loggers', () => {
|
|||||||
tool_type: 'native',
|
tool_type: 'native',
|
||||||
decision: undefined,
|
decision: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
|
content_length: errorMessage.length,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1035,4 +1046,40 @@ describe('loggers', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('logToolOutputTruncated', () => {
|
||||||
|
const mockConfig = {
|
||||||
|
getSessionId: () => 'test-session-id',
|
||||||
|
getUsageStatisticsEnabled: () => true,
|
||||||
|
} as unknown as Config;
|
||||||
|
|
||||||
|
it('should log a tool output truncated event', () => {
|
||||||
|
const event = new ToolOutputTruncatedEvent('prompt-id-1', {
|
||||||
|
toolName: 'test-tool',
|
||||||
|
originalContentLength: 1000,
|
||||||
|
truncatedContentLength: 100,
|
||||||
|
threshold: 500,
|
||||||
|
lines: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
logToolOutputTruncated(mockConfig, event);
|
||||||
|
|
||||||
|
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||||
|
body: 'Tool output truncated for test-tool.',
|
||||||
|
attributes: {
|
||||||
|
'session.id': 'test-session-id',
|
||||||
|
'user.email': 'test-user@example.com',
|
||||||
|
'event.name': 'tool_output_truncated',
|
||||||
|
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||||
|
eventName: 'tool_output_truncated',
|
||||||
|
prompt_id: 'prompt-id-1',
|
||||||
|
tool_name: 'test-tool',
|
||||||
|
original_content_length: 1000,
|
||||||
|
truncated_content_length: 100,
|
||||||
|
threshold: 500,
|
||||||
|
lines: 10,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import type {
|
|||||||
ContentRetryEvent,
|
ContentRetryEvent,
|
||||||
ContentRetryFailureEvent,
|
ContentRetryFailureEvent,
|
||||||
RipgrepFallbackEvent,
|
RipgrepFallbackEvent,
|
||||||
|
ToolOutputTruncatedEvent,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import {
|
import {
|
||||||
recordApiErrorMetrics,
|
recordApiErrorMetrics,
|
||||||
@@ -185,6 +186,28 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logToolOutputTruncated(
|
||||||
|
config: Config,
|
||||||
|
event: ToolOutputTruncatedEvent,
|
||||||
|
): void {
|
||||||
|
ClearcutLogger.getInstance(config)?.logToolOutputTruncatedEvent(event);
|
||||||
|
if (!isTelemetrySdkInitialized()) return;
|
||||||
|
|
||||||
|
const attributes: LogAttributes = {
|
||||||
|
...getCommonAttributes(config),
|
||||||
|
...event,
|
||||||
|
'event.name': 'tool_output_truncated',
|
||||||
|
'event.timestamp': new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const logger = logs.getLogger(SERVICE_NAME);
|
||||||
|
const logRecord: LogRecord = {
|
||||||
|
body: `Tool output truncated for ${event.tool_name}.`,
|
||||||
|
attributes,
|
||||||
|
};
|
||||||
|
logger.emit(logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
export function logFileOperation(
|
export function logFileOperation(
|
||||||
config: Config,
|
config: Config,
|
||||||
event: FileOperationEvent,
|
event: FileOperationEvent,
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
|||||||
error_type?: string;
|
error_type?: string;
|
||||||
prompt_id: string;
|
prompt_id: string;
|
||||||
tool_type: 'native' | 'mcp';
|
tool_type: 'native' | 'mcp';
|
||||||
|
content_length?: number;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
metadata?: { [key: string]: any };
|
metadata?: { [key: string]: any };
|
||||||
|
|
||||||
@@ -156,6 +157,7 @@ export class ToolCallEvent implements BaseTelemetryEvent {
|
|||||||
typeof call.tool !== 'undefined' && call.tool instanceof DiscoveredMCPTool
|
typeof call.tool !== 'undefined' && call.tool instanceof DiscoveredMCPTool
|
||||||
? 'mcp'
|
? 'mcp'
|
||||||
: 'native';
|
: 'native';
|
||||||
|
this.content_length = call.response.contentLength;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
call.status === 'success' &&
|
call.status === 'success' &&
|
||||||
@@ -535,7 +537,8 @@ export type TelemetryEvent =
|
|||||||
| InvalidChunkEvent
|
| InvalidChunkEvent
|
||||||
| ContentRetryEvent
|
| ContentRetryEvent
|
||||||
| ContentRetryFailureEvent
|
| ContentRetryFailureEvent
|
||||||
| ExtensionInstallEvent;
|
| ExtensionInstallEvent
|
||||||
|
| ToolOutputTruncatedEvent;
|
||||||
|
|
||||||
export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
||||||
'event.name': 'extension_install';
|
'event.name': 'extension_install';
|
||||||
@@ -559,3 +562,34 @@ export class ExtensionInstallEvent implements BaseTelemetryEvent {
|
|||||||
this.status = status;
|
this.status = status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ToolOutputTruncatedEvent implements BaseTelemetryEvent {
|
||||||
|
readonly eventName = 'tool_output_truncated';
|
||||||
|
readonly 'event.timestamp' = new Date().toISOString();
|
||||||
|
'event.name': string;
|
||||||
|
tool_name: string;
|
||||||
|
original_content_length: number;
|
||||||
|
truncated_content_length: number;
|
||||||
|
threshold: number;
|
||||||
|
lines: number;
|
||||||
|
prompt_id: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
prompt_id: string,
|
||||||
|
details: {
|
||||||
|
toolName: string;
|
||||||
|
originalContentLength: number;
|
||||||
|
truncatedContentLength: number;
|
||||||
|
threshold: number;
|
||||||
|
lines: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this['event.name'] = this.eventName;
|
||||||
|
this.prompt_id = prompt_id;
|
||||||
|
this.tool_name = details.toolName;
|
||||||
|
this.original_content_length = details.originalContentLength;
|
||||||
|
this.truncated_content_length = details.truncatedContentLength;
|
||||||
|
this.threshold = details.threshold;
|
||||||
|
this.lines = details.lines;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user