refactor: move transform logic to Clearcut level (#2637)

I think this makes a little bit cleaner structures. As now Clearcut
holds the responsibility of how the data is formatted.
This commit is contained in:
Nikolay Vitkov
2026-09-02 12:42:48 +00:00
committed by GitHub
parent 446bc2e0ec
commit f0058d55d2
4 changed files with 29 additions and 31 deletions
+3 -4
View File
@@ -11,7 +11,6 @@ import type {DataFormat} from './McpResponse.js';
import {McpResponse} from './McpResponse.js';
import {SlimMcpResponse} from './SlimMcpResponse.js';
import {ClearcutLogger} from './telemetry/ClearcutLogger.js';
import {bucketizeLatency, buildContext} from './telemetry/transformation.js';
import type {CallToolResult} from './third_party/index.js';
import {zod} from './third_party/index.js';
import type {ToolCategory} from './tools/categories.js';
@@ -383,14 +382,14 @@ export class ToolHandler {
isError: true,
};
} finally {
const context = buildContext(devToolsData, pageUrl);
void ClearcutLogger.get()?.logToolInvocation({
toolName: this.tool.name,
params,
schema: this.inputSchema,
success,
latencyMs: bucketizeLatency(Date.now() - startTime),
context,
latencyMs: Date.now() - startTime,
devToolsData,
pageUrl,
});
guard[Symbol.dispose]();
}
+13 -6
View File
@@ -12,16 +12,21 @@ import {logger} from '../utils/logger.js';
import type {ErrorCode} from './errors.js';
import type {LocalState, Persistence} from './persistence.js';
import {sanitizeParams, stripUnderscoreBeforeNumber} from './transformation.js';
import {
bucketizeLatency,
buildContext,
sanitizeParams,
stripUnderscoreBeforeNumber,
} from './transformation.js';
import {
McpClient,
type FlagUsage,
WatchdogMessageType,
OsType,
type ToolInvocation,
type ToolInvocationContext,
} from './types.js';
import {WatchdogClient} from './WatchdogClient.js';
import type {DevToolsData} from '../tools/ToolDefinition.js';
const MS_PER_DAY = 24 * 60 * 60 * 1000;
@@ -121,16 +126,18 @@ export class ClearcutLogger {
schema: zod.ZodRawShape;
success: boolean;
latencyMs: number;
context: ToolInvocationContext;
devToolsData?: DevToolsData;
pageUrl?: string;
}): Promise<void> {
const context = buildContext(args.devToolsData, args.pageUrl);
const sanitizedToolName = stripUnderscoreBeforeNumber(args.toolName);
const tool_invocation: ToolInvocation = {
tool_name: sanitizedToolName,
success: args.success,
latency_ms: args.latencyMs,
latency_ms: bucketizeLatency(args.latencyMs),
};
if (Object.keys(args.context).length > 0) {
tool_invocation.context = args.context;
if (Object.keys(context).length > 0) {
tool_invocation.context = context;
}
if (Object.keys(args.params).length > 0) {
tool_invocation.tool_params = {
+6 -15
View File
@@ -21,6 +21,7 @@ import {ToolHandler} from '../src/ToolHandler.js';
import {ToolCategory} from '../src/tools/categories.js';
import type {
DefinedPageTool,
DevToolsData,
ToolDefinition,
} from '../src/tools/ToolDefinition.js';
import {createTools} from '../src/tools/tools.js';
@@ -165,7 +166,7 @@ describe('ToolHandler', () => {
assert.strictEqual(result.isError, undefined);
});
it('appends correct context to tool call logs', async () => {
it('passes devToolsData and pageUrl to logger', async () => {
const baseTool: ToolDefinition = {
name: 'test_tool',
description: 'A test tool',
@@ -183,9 +184,8 @@ describe('ToolHandler', () => {
const testCases: Array<{
tool: ToolDefinition | DefinedPageTool;
devToolsData: Record<string, unknown>;
devToolsData: DevToolsData;
pageUrl?: string;
expectedContext: Record<string, unknown>;
}> = [
{
tool: {
@@ -195,13 +195,6 @@ describe('ToolHandler', () => {
},
devToolsData: {cdpBackendNodeId: 1},
pageUrl: 'http://localhost:9222/',
expectedContext: {
is_devtools_open: true,
is_localhost: true,
devtools_data: {
is_dom_element_selected: true,
},
},
},
{
tool: {
@@ -210,9 +203,6 @@ describe('ToolHandler', () => {
},
devToolsData: {},
pageUrl: undefined,
expectedContext: {
is_devtools_open: false,
},
},
];
@@ -251,9 +241,10 @@ describe('ToolHandler', () => {
assert.strictEqual(logSpy.calledOnce, true);
assert.deepStrictEqual(
logSpy.firstCall.args[0].context,
testCase.expectedContext,
logSpy.firstCall.args[0].devToolsData,
testCase.devToolsData,
);
assert.strictEqual(logSpy.firstCall.args[0].pageUrl, testCase.pageUrl);
assert.strictEqual(handlerCalled, true);
sinon.restore();
+7 -6
View File
@@ -50,7 +50,6 @@ describe('ClearcutLogger', () => {
schema: {},
success: true,
latencyMs: 123,
context: {},
});
assert(mockWatchdogClient.send.calledOnce);
@@ -58,7 +57,7 @@ describe('ClearcutLogger', () => {
assert.strictEqual(msg.type, WatchdogMessageType.LOG_EVENT);
assert.strictEqual(msg.payload.tool_invocation?.tool_name, 'test_tool');
assert.strictEqual(msg.payload.tool_invocation?.success, true);
assert.strictEqual(msg.payload.tool_invocation?.latency_ms, 123);
assert.strictEqual(msg.payload.tool_invocation?.latency_ms, 250);
});
it('sends context when provided', async () => {
const logger = ClearcutLogger.initialize({
@@ -72,10 +71,10 @@ describe('ClearcutLogger', () => {
schema: {},
success: true,
latencyMs: 123,
context: {
is_devtools_open: true,
is_localhost: false,
devToolsData: {
cdpBackendNodeId: 1,
},
pageUrl: 'https://example.com',
});
assert(mockWatchdogClient.send.calledOnce);
@@ -84,6 +83,9 @@ describe('ClearcutLogger', () => {
assert.deepStrictEqual(msg.payload.tool_invocation?.context, {
is_devtools_open: true,
is_localhost: false,
devtools_data: {
is_dom_element_selected: true,
},
});
});
it('sends sanitized params', async () => {
@@ -109,7 +111,6 @@ describe('ClearcutLogger', () => {
schema,
success: true,
latencyMs: 123,
context: {},
});
assert(mockWatchdogClient.send.calledOnce);