Skip to content

Commit 71ccdb0

Browse files
authored
chore: better organize telemetry utilities (#2057)
There is no behavior change. This groups utilities into two modules: - `transformation.ts`: for any logic related to mutating / filtering of the names, values of telemetry entries. - `metricsRegistry.ts`: for any logic that related to the maintenance of metrics.json files.
1 parent 081c903 commit 71ccdb0

10 files changed

Lines changed: 296 additions & 291 deletions

scripts/update_metrics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
applyToExistingMetrics,
2121
generateToolMetrics,
2222
type ToolMetric,
23-
} from '../build/src/telemetry/toolMetricsUtils.js';
23+
} from '../build/src/telemetry/metricsRegistry.js';
2424
import {createTools} from '../build/src/tools/tools.js';
2525

2626
export function HaveUniqueNames(tools: Array<{name: string}>): boolean {

src/ToolHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {McpResponse} from './McpResponse.js';
1111
import type {Mutex} from './Mutex.js';
1212
import {SlimMcpResponse} from './SlimMcpResponse.js';
1313
import {ClearcutLogger} from './telemetry/ClearcutLogger.js';
14-
import {bucketizeLatency} from './telemetry/metricUtils.js';
14+
import {bucketizeLatency} from './telemetry/transformation.js';
1515
import type {CallToolResult, zod} from './third_party/index.js';
1616
import type {ToolCategory} from './tools/categories.js';
1717
import {labels, OFF_BY_DEFAULT_CATEGORIES} from './tools/categories.js';

src/telemetry/ClearcutLogger.ts

Lines changed: 1 addition & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {zod, ShapeOutput} from '../third_party/index.js';
1212

1313
import type {ErrorCode} from './errors.js';
1414
import type {LocalState, Persistence} from './persistence.js';
15+
import {sanitizeParams} from './transformation.js';
1516
import {
1617
McpClient,
1718
type FlagUsage,
@@ -22,141 +23,6 @@ import {
2223
import {WatchdogClient} from './WatchdogClient.js';
2324

2425
const MS_PER_DAY = 24 * 60 * 60 * 1000;
25-
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
26-
27-
const SUPPORTED_ZOD_TYPES = [
28-
'ZodString',
29-
'ZodNumber',
30-
'ZodBoolean',
31-
'ZodArray',
32-
'ZodEnum',
33-
] as const;
34-
type ZodType = (typeof SUPPORTED_ZOD_TYPES)[number];
35-
36-
function isZodType(type: string): type is ZodType {
37-
return SUPPORTED_ZOD_TYPES.includes(type as ZodType);
38-
}
39-
40-
export function getZodType(zodType: zod.ZodTypeAny): ZodType {
41-
const def = zodType._def;
42-
const typeName = def.typeName;
43-
44-
if (
45-
typeName === 'ZodOptional' ||
46-
typeName === 'ZodDefault' ||
47-
typeName === 'ZodNullable'
48-
) {
49-
return getZodType(def.innerType);
50-
}
51-
if (typeName === 'ZodEffects') {
52-
return getZodType(def.schema);
53-
}
54-
55-
if (isZodType(typeName)) {
56-
return typeName;
57-
}
58-
throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
59-
}
60-
61-
type LoggedToolCallArgValue = string | number | boolean;
62-
63-
export function transformArgName(zodType: ZodType, name: string): string {
64-
const snakeCaseName = name.replace(
65-
/[A-Z]/g,
66-
letter => `_${letter.toLowerCase()}`,
67-
);
68-
if (zodType === 'ZodString') {
69-
return `${snakeCaseName}_length`;
70-
} else if (zodType === 'ZodArray') {
71-
return `${snakeCaseName}_count`;
72-
} else {
73-
return snakeCaseName;
74-
}
75-
}
76-
77-
export function transformArgType(zodType: ZodType): string {
78-
if (zodType === 'ZodString' || zodType === 'ZodArray') {
79-
return 'number';
80-
}
81-
switch (zodType) {
82-
case 'ZodNumber':
83-
return 'number';
84-
case 'ZodBoolean':
85-
return 'boolean';
86-
case 'ZodEnum':
87-
return 'enum';
88-
default:
89-
throw new Error(`Unsupported zod type for tool parameter: ${zodType}`);
90-
}
91-
}
92-
93-
const BUCKETS = [
94-
0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000,
95-
];
96-
97-
function bucketize(value: number): number {
98-
for (const bucket of BUCKETS) {
99-
if (bucket >= value) {
100-
return bucket;
101-
}
102-
}
103-
return BUCKETS[BUCKETS.length - 1];
104-
}
105-
106-
function transformValue(
107-
zodType: ZodType,
108-
value: unknown,
109-
): LoggedToolCallArgValue {
110-
if (zodType === 'ZodString') {
111-
return bucketize((value as string).length);
112-
} else if (zodType === 'ZodArray') {
113-
return (value as unknown[]).length;
114-
} else {
115-
return value as LoggedToolCallArgValue;
116-
}
117-
}
118-
119-
function hasEquivalentType(zodType: ZodType, value: unknown): boolean {
120-
if (zodType === 'ZodString') {
121-
return typeof value === 'string';
122-
} else if (zodType === 'ZodArray') {
123-
return Array.isArray(value);
124-
} else if (zodType === 'ZodNumber') {
125-
return typeof value === 'number';
126-
} else if (zodType === 'ZodBoolean') {
127-
return typeof value === 'boolean';
128-
} else if (zodType === 'ZodEnum') {
129-
return (
130-
typeof value === 'string' ||
131-
typeof value === 'number' ||
132-
typeof value === 'boolean'
133-
);
134-
} else {
135-
return false;
136-
}
137-
}
138-
139-
export function sanitizeParams(
140-
params: ShapeOutput<zod.ZodRawShape>,
141-
schema: zod.ZodRawShape,
142-
): ShapeOutput<zod.ZodRawShape> {
143-
const transformed: ShapeOutput<zod.ZodRawShape> = {};
144-
for (const [name, value] of Object.entries(params)) {
145-
if (PARAM_BLOCKLIST.has(name)) {
146-
continue;
147-
}
148-
const zodType = getZodType(schema[name]);
149-
if (!hasEquivalentType(zodType, value)) {
150-
throw new Error(
151-
`parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`,
152-
);
153-
}
154-
const transformedName = transformArgName(zodType, name);
155-
const transformedValue = transformValue(zodType, value);
156-
transformed[transformedName] = transformedValue;
157-
}
158-
return transformed;
159-
}
16026

16127
function detectOsType(): OsType {
16228
switch (process.platform) {

src/telemetry/metricUtils.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
transformArgType,
1212
getZodType,
1313
PARAM_BLOCKLIST,
14-
} from './ClearcutLogger.js';
14+
} from './transformation.js';
1515

1616
/**
1717
* Validates that all values in an enum are of the homogeneous primitive type.

src/telemetry/transformation.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import type {zod, ShapeOutput} from '../third_party/index.js';
8+
9+
const LATENCY_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000];
10+
11+
export function bucketizeLatency(latencyMs: number): number {
12+
for (const bucket of LATENCY_BUCKETS) {
13+
if (latencyMs <= bucket) {
14+
return bucket;
15+
}
16+
}
17+
return LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1];
18+
}
19+
20+
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
21+
22+
const SUPPORTED_ZOD_TYPES = [
23+
'ZodString',
24+
'ZodNumber',
25+
'ZodBoolean',
26+
'ZodArray',
27+
'ZodEnum',
28+
] as const;
29+
type ZodType = (typeof SUPPORTED_ZOD_TYPES)[number];
30+
31+
function isZodType(type: string): type is ZodType {
32+
return SUPPORTED_ZOD_TYPES.includes(type as ZodType);
33+
}
34+
35+
export function getZodType(zodType: zod.ZodTypeAny): ZodType {
36+
const def = zodType._def;
37+
const typeName = def.typeName;
38+
39+
if (
40+
typeName === 'ZodOptional' ||
41+
typeName === 'ZodDefault' ||
42+
typeName === 'ZodNullable'
43+
) {
44+
return getZodType(def.innerType);
45+
}
46+
if (typeName === 'ZodEffects') {
47+
return getZodType(def.schema);
48+
}
49+
50+
if (isZodType(typeName)) {
51+
return typeName;
52+
}
53+
throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
54+
}
55+
56+
type LoggedToolCallArgValue = string | number | boolean;
57+
58+
export function transformArgName(zodType: ZodType, name: string): string {
59+
const snakeCaseName = name.replace(
60+
/[A-Z]/g,
61+
letter => `_${letter.toLowerCase()}`,
62+
);
63+
if (zodType === 'ZodString') {
64+
return `${snakeCaseName}_length`;
65+
} else if (zodType === 'ZodArray') {
66+
return `${snakeCaseName}_count`;
67+
} else {
68+
return snakeCaseName;
69+
}
70+
}
71+
72+
export function transformArgType(zodType: ZodType): string {
73+
if (zodType === 'ZodString' || zodType === 'ZodArray') {
74+
return 'number';
75+
}
76+
switch (zodType) {
77+
case 'ZodNumber':
78+
return 'number';
79+
case 'ZodBoolean':
80+
return 'boolean';
81+
case 'ZodEnum':
82+
return 'enum';
83+
default:
84+
throw new Error(`Unsupported zod type for tool parameter: ${zodType}`);
85+
}
86+
}
87+
88+
const BUCKETS = [
89+
0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000,
90+
];
91+
92+
function bucketize(value: number): number {
93+
for (const bucket of BUCKETS) {
94+
if (bucket >= value) {
95+
return bucket;
96+
}
97+
}
98+
return BUCKETS[BUCKETS.length - 1];
99+
}
100+
101+
function transformValue(
102+
zodType: ZodType,
103+
value: unknown,
104+
): LoggedToolCallArgValue {
105+
if (zodType === 'ZodString') {
106+
return bucketize((value as string).length);
107+
} else if (zodType === 'ZodArray') {
108+
return (value as unknown[]).length;
109+
} else {
110+
return value as LoggedToolCallArgValue;
111+
}
112+
}
113+
114+
function hasEquivalentType(zodType: ZodType, value: unknown): boolean {
115+
if (zodType === 'ZodString') {
116+
return typeof value === 'string';
117+
} else if (zodType === 'ZodArray') {
118+
return Array.isArray(value);
119+
} else if (zodType === 'ZodNumber') {
120+
return typeof value === 'number';
121+
} else if (zodType === 'ZodBoolean') {
122+
return typeof value === 'boolean';
123+
} else if (zodType === 'ZodEnum') {
124+
return (
125+
typeof value === 'string' ||
126+
typeof value === 'number' ||
127+
typeof value === 'boolean'
128+
);
129+
} else {
130+
return false;
131+
}
132+
}
133+
134+
export function sanitizeParams(
135+
params: ShapeOutput<zod.ZodRawShape>,
136+
schema: zod.ZodRawShape,
137+
): ShapeOutput<zod.ZodRawShape> {
138+
const transformed: ShapeOutput<zod.ZodRawShape> = {};
139+
for (const [name, value] of Object.entries(params)) {
140+
if (PARAM_BLOCKLIST.has(name)) {
141+
continue;
142+
}
143+
const zodType = getZodType(schema[name]);
144+
if (!hasEquivalentType(zodType, value)) {
145+
throw new Error(
146+
`parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`,
147+
);
148+
}
149+
const transformedName = transformArgName(zodType, name);
150+
const transformedValue = transformValue(zodType, value);
151+
transformed[transformedName] = transformedValue;
152+
}
153+
return transformed;
154+
}

0 commit comments

Comments
 (0)