feat(telemetry): report whether tool call is made on localhost. (#2455)
This commit is contained in:
@@ -214,6 +214,7 @@ export class ToolHandler {
|
||||
const startTime = Date.now();
|
||||
let success = false;
|
||||
let devToolsData: DevToolsData | undefined;
|
||||
let pageUrl: string | undefined;
|
||||
try {
|
||||
logger?.(
|
||||
`${this.tool.name} request: ${JSON.stringify(params, null, ' ')}`,
|
||||
@@ -270,6 +271,10 @@ export class ToolHandler {
|
||||
response.setError(err);
|
||||
}
|
||||
devToolsData = await context.getDevToolsData(page);
|
||||
const targetPage = page ?? context.getSelectedMcpPage();
|
||||
if (targetPage?.pptrPage?.isClosed() === false) {
|
||||
pageUrl = targetPage.pptrPage.url();
|
||||
}
|
||||
// Resolve data format: --experimentalDataFormat takes precedence, fall back to legacy --experimentalToonFormat
|
||||
let dataFormat: DataFormat = 'default';
|
||||
if (this.serverArgs.experimentalDataFormat) {
|
||||
@@ -321,6 +326,7 @@ export class ToolHandler {
|
||||
success,
|
||||
latencyMs: bucketizeLatency(Date.now() - startTime),
|
||||
isDevToolsOpen,
|
||||
pageUrl,
|
||||
});
|
||||
guard[Symbol.dispose]();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import process from 'node:process';
|
||||
import {DAEMON_CLIENT_NAME} from '../daemon/utils.js';
|
||||
import type {zod, ShapeOutput} from '../third_party/index.js';
|
||||
import {logger} from '../utils/logger.js';
|
||||
import {isLocalhost} from '../utils/url.js';
|
||||
|
||||
import type {ErrorCode} from './errors.js';
|
||||
import type {LocalState, Persistence} from './persistence.js';
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
WatchdogMessageType,
|
||||
OsType,
|
||||
type ToolInvocation,
|
||||
type ToolInvocationContext,
|
||||
} from './types.js';
|
||||
import {WatchdogClient} from './WatchdogClient.js';
|
||||
|
||||
@@ -113,6 +115,7 @@ export class ClearcutLogger {
|
||||
success: boolean;
|
||||
latencyMs: number;
|
||||
isDevToolsOpen?: boolean;
|
||||
pageUrl?: string;
|
||||
}): Promise<void> {
|
||||
const sanitizedToolName = stripUnderscoreBeforeNumber(args.toolName);
|
||||
const tool_invocation: ToolInvocation = {
|
||||
@@ -120,10 +123,15 @@ export class ClearcutLogger {
|
||||
success: args.success,
|
||||
latency_ms: args.latencyMs,
|
||||
};
|
||||
const context: ToolInvocationContext = {};
|
||||
if (args.isDevToolsOpen !== undefined) {
|
||||
tool_invocation.context = {
|
||||
is_devtools_open: args.isDevToolsOpen,
|
||||
};
|
||||
context.is_devtools_open = args.isDevToolsOpen;
|
||||
}
|
||||
if (args.pageUrl !== undefined) {
|
||||
context.is_localhost = isLocalhost(args.pageUrl);
|
||||
}
|
||||
if (Object.keys(context).length > 0) {
|
||||
tool_invocation.context = context;
|
||||
}
|
||||
if (Object.keys(args.params).length > 0) {
|
||||
tool_invocation.tool_params = {
|
||||
|
||||
@@ -28,6 +28,7 @@ export type ServerShutdown = Record<string, never>;
|
||||
|
||||
export interface ToolInvocationContext {
|
||||
is_devtools_open?: boolean;
|
||||
is_localhost?: boolean;
|
||||
}
|
||||
|
||||
export interface ToolInvocation {
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('ToolHandler', () => {
|
||||
const result = await toolHandler.handle({});
|
||||
|
||||
assert.strictEqual(mockContext.getDevToolsData.calledOnce, true);
|
||||
assert.strictEqual(mockContext.getSelectedMcpPage.called, false);
|
||||
assert.strictEqual(mockContext.getSelectedMcpPage.calledOnce, true);
|
||||
assert.strictEqual(mockContext.getPageById.called, false);
|
||||
assert.strictEqual(handlerCalled, true);
|
||||
assert.strictEqual(result.isError, undefined);
|
||||
@@ -112,9 +112,10 @@ describe('ToolHandler', () => {
|
||||
|
||||
it('logs isDevToolsOpen telemetry based on getDevToolsData', async () => {
|
||||
let handlerCalled = false;
|
||||
const tool: ToolDefinition = {
|
||||
name: 'global_tool',
|
||||
description: 'A global tool',
|
||||
const tool: DefinedPageTool = {
|
||||
name: 'page_tool',
|
||||
description: 'A page tool',
|
||||
pageScoped: true,
|
||||
annotations: {
|
||||
category: ToolCategory.NAVIGATION,
|
||||
readOnlyHint: true,
|
||||
@@ -129,6 +130,13 @@ describe('ToolHandler', () => {
|
||||
|
||||
const mockContext = sinon.createStubInstance(McpContext);
|
||||
mockContext.getDevToolsData.resolves({cdpBackendNodeId: 1});
|
||||
const mockPage = {
|
||||
pptrPage: {
|
||||
isClosed: () => false,
|
||||
url: () => 'http://localhost:9222/',
|
||||
},
|
||||
} as unknown as McpPage;
|
||||
mockContext.getSelectedMcpPage.returns(mockPage);
|
||||
|
||||
const logSpy = sinon.spy();
|
||||
sinon.stub(ClearcutLogger, 'get').returns({
|
||||
@@ -151,6 +159,10 @@ describe('ToolHandler', () => {
|
||||
|
||||
assert.strictEqual(logSpy.calledOnce, true);
|
||||
assert.strictEqual(logSpy.firstCall.args[0].isDevToolsOpen, true);
|
||||
assert.strictEqual(
|
||||
logSpy.firstCall.args[0].pageUrl,
|
||||
'http://localhost:9222/',
|
||||
);
|
||||
assert.strictEqual(handlerCalled, true);
|
||||
});
|
||||
|
||||
|
||||
@@ -81,6 +81,35 @@ describe('ClearcutLogger', () => {
|
||||
is_devtools_open: true,
|
||||
});
|
||||
});
|
||||
it('sends context with correct is_localhost based on the URL', async () => {
|
||||
const logger = ClearcutLogger.initialize({
|
||||
persistence: mockPersistence,
|
||||
appVersion: '1.0.0',
|
||||
watchdogClient: mockWatchdogClient,
|
||||
});
|
||||
|
||||
for (const {pageUrl, isLocalhost} of [
|
||||
{pageUrl: 'http://localhost:9222/test', isLocalhost: true},
|
||||
{pageUrl: 'https://example.com/test', isLocalhost: false},
|
||||
]) {
|
||||
mockWatchdogClient.send.resetHistory();
|
||||
await logger.logToolInvocation({
|
||||
toolName: 'test_tool',
|
||||
params: {},
|
||||
schema: {},
|
||||
success: true,
|
||||
latencyMs: 123,
|
||||
pageUrl,
|
||||
});
|
||||
|
||||
assert(mockWatchdogClient.send.calledOnce);
|
||||
const msg = mockWatchdogClient.send.firstCall.args[0];
|
||||
assert.strictEqual(msg.type, WatchdogMessageType.LOG_EVENT);
|
||||
assert.deepStrictEqual(msg.payload.tool_invocation?.context, {
|
||||
is_localhost: isLocalhost,
|
||||
});
|
||||
}
|
||||
});
|
||||
it('sends sanitized params', async () => {
|
||||
const logger = ClearcutLogger.initialize({
|
||||
persistence: mockPersistence,
|
||||
|
||||
Reference in New Issue
Block a user