openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
233 lines • 7.24 kB
JavaScript
/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { isLocalhost } from '../utils/url.js';
const LATENCY_BUCKETS = [50, 100, 250, 500, 1000, 2500, 5000, 10000];
export function bucketizeLatency(latencyMs) {
for (const bucket of LATENCY_BUCKETS) {
if (latencyMs <= bucket) {
return bucket;
}
}
return LATENCY_BUCKETS[LATENCY_BUCKETS.length - 1];
}
export const MAX_ACTIVE_DAYS = 31;
export function bucketizeDaysSince(days) {
return Math.min(days, MAX_ACTIVE_DAYS);
}
export const REDACTED_CLIENT_NAME = '<redacted>';
const VALID_CLIENT_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
const MAX_CLIENT_NAME_LENGTH = 32;
export function sanitizeClientName(clientName) {
if (clientName.length > 0 &&
clientName.length < MAX_CLIENT_NAME_LENGTH &&
VALID_CLIENT_NAME_REGEX.test(clientName)) {
return clientName;
}
return REDACTED_CLIENT_NAME;
}
export const PARAM_BLOCKLIST = new Set(['uid', 'reqid', 'msgid']);
const SUPPORTED_ZOD_TYPES = [
'ZodString',
'ZodNumber',
'ZodBoolean',
'ZodArray',
'ZodEnum',
];
function isObjectWithDef(val) {
return typeof val === 'object' && val !== null && '_def' in val;
}
function isZodType(type) {
return SUPPORTED_ZOD_TYPES.includes(type);
}
export function getZodType(zodType) {
if (!isObjectWithDef(zodType)) {
throw new Error('Invalid zod schema');
}
const def = zodType._def;
let typeName = def.typeName;
if (!typeName && def.type) {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
if (typeName === 'ZodOptional' ||
typeName === 'ZodDefault' ||
typeName === 'ZodNullable') {
return getZodType(def.innerType);
}
if (typeName === 'ZodEffects') {
return getZodType(def.schema);
}
if (typeName === 'ZodPipeline' || typeName === 'ZodPipe') {
return getZodType(isObjectWithDef(def.in) &&
(def.in._def.type === 'transform' ||
def.in._def.typeName === 'ZodTransform')
? def.out
: def.in);
}
if (typeName && isZodType(typeName)) {
return typeName;
}
throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
}
/**
* Resolves the values of an enum parameter, unwrapping any optional/default/
* nullable/effects wrappers (in any order), mirroring {@link getZodType}.
*/
export function getEnumValues(zodType) {
if (!isObjectWithDef(zodType)) {
throw new Error('Invalid zod schema');
}
const def = zodType._def;
let typeName = def.typeName;
if (!typeName && def.type) {
typeName = 'Zod' + def.type.charAt(0).toUpperCase() + def.type.slice(1);
}
if (typeName === 'ZodOptional' ||
typeName === 'ZodDefault' ||
typeName === 'ZodNullable') {
return getEnumValues(def.innerType);
}
if (typeName === 'ZodEffects') {
return getEnumValues(def.schema);
}
if (typeName === 'ZodPipeline' || typeName === 'ZodPipe') {
return getEnumValues(isObjectWithDef(def.in) &&
(def.in._def.type === 'transform' ||
def.in._def.typeName === 'ZodTransform')
? def.out
: def.in);
}
if (typeName === 'ZodEnum') {
if (def.values) {
return def.values;
}
if (def.entries) {
return Object.values(def.entries);
}
}
throw new Error(`Cannot resolve enum values for zod type: ${typeName}`);
}
export function stripUnderscoreBeforeNumber(name) {
return name.replace(/_([0-9])/g, '$1');
}
export function transformArgName(zodType, name) {
const snakeCaseName = name.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
let transformed;
if (zodType === 'ZodString') {
transformed = `${snakeCaseName}_length`;
}
else if (zodType === 'ZodArray') {
transformed = `${snakeCaseName}_count`;
}
else {
transformed = snakeCaseName;
}
return stripUnderscoreBeforeNumber(transformed);
}
export function transformArgType(zodType) {
if (zodType === 'ZodString' || zodType === 'ZodArray') {
return 'number';
}
switch (zodType) {
case 'ZodNumber':
return 'number';
case 'ZodBoolean':
return 'boolean';
case 'ZodEnum':
return 'enum';
default:
throw new Error(`Unsupported zod type for tool parameter: ${zodType}`);
}
}
const BUCKETS = [
0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000,
];
function bucketize(value) {
for (const bucket of BUCKETS) {
if (bucket >= value) {
return bucket;
}
}
return BUCKETS[BUCKETS.length - 1];
}
function transformValue(zodType, value) {
if (zodType === 'ZodString') {
return bucketize(value.length);
}
else if (zodType === 'ZodArray') {
return value.length;
}
else {
return value;
}
}
function hasEquivalentType(zodType, value) {
if (zodType === 'ZodString') {
return typeof value === 'string';
}
else if (zodType === 'ZodArray') {
return Array.isArray(value);
}
else if (zodType === 'ZodNumber') {
return typeof value === 'number';
}
else if (zodType === 'ZodBoolean') {
return typeof value === 'boolean';
}
else if (zodType === 'ZodEnum') {
return (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean');
}
else {
return false;
}
}
export function sanitizeParams(params, schema) {
const transformed = {};
for (const [name, value] of Object.entries(params)) {
if (PARAM_BLOCKLIST.has(name)) {
continue;
}
const zodType = getZodType(schema[name]);
if (!hasEquivalentType(zodType, value)) {
throw new Error(`parameter ${name} has type ${zodType} but value ${value} is not of equivalent type`);
}
const transformedName = transformArgName(zodType, name);
const transformedValue = transformValue(zodType, value);
transformed[transformedName] = transformedValue;
}
return transformed;
}
function transformDevToolsData(devToolsData) {
const logged = {};
if (devToolsData.cdpBackendNodeId !== undefined) {
logged.is_dom_element_selected = true;
}
if (devToolsData.cdpRequestId !== undefined) {
logged.is_network_request_selected = true;
}
return logged;
}
export function buildContext(devToolsData, pageUrl) {
let context;
if (devToolsData === undefined) {
context = { is_devtools_open: false };
}
else {
context = {
is_devtools_open: Object.keys(devToolsData).length > 0,
};
const loggedDevtoolsData = transformDevToolsData(devToolsData);
if (Object.keys(loggedDevtoolsData).length > 0) {
context.devtools_data = loggedDevtoolsData;
}
}
if (pageUrl !== undefined) {
context.is_localhost = isLocalhost(pageUrl);
}
return context;
}
//# sourceMappingURL=transformation.js.map