@robota-sdk/tools
Version:
Tools and utilities package for Robota SDK - providing AI function calling and tool integration capabilities
1,814 lines (1,798 loc) • 73.7 kB
JavaScript
'use strict';
var zod = require('zod');
// src/factories/function-factory.ts
function zodToJsonSchema(schema) {
const jsonSchema = {
type: "object",
properties: {},
required: []
};
const shape = schema._def.shape();
const entries = Object.entries(shape);
for (const [key, value] of entries) {
const fieldSchema = convertZodTypeToJsonSchema(value, key);
jsonSchema.properties[key] = fieldSchema;
if (!isOptionalType(value) && !isNullableType(value)) {
if (!jsonSchema.required) {
jsonSchema.required = [];
}
jsonSchema.required.push(key);
}
}
return jsonSchema;
}
function convertZodTypeToJsonSchema(zodType, fieldName) {
const jsonSchema = {};
const description = getZodDescription(zodType);
if (description) {
jsonSchema.description = description;
}
if (zodType instanceof zod.z.ZodString) {
return convertZodString(zodType, jsonSchema);
} else if (zodType instanceof zod.z.ZodNumber) {
return convertZodNumber(zodType, jsonSchema);
} else if (zodType instanceof zod.z.ZodBoolean) {
jsonSchema.type = "boolean";
} else if (zodType instanceof zod.z.ZodArray) {
return convertZodArray(zodType, jsonSchema, fieldName);
} else if (zodType instanceof zod.z.ZodEnum) {
return convertZodEnum(zodType, jsonSchema);
} else if (zodType instanceof zod.z.ZodObject) {
return convertZodObject(zodType, jsonSchema);
} else if (zodType instanceof zod.z.ZodUnion) {
return convertZodUnion(zodType, jsonSchema, fieldName);
} else if (zodType instanceof zod.z.ZodOptional) {
return convertZodTypeToJsonSchema(zodType._def.innerType, fieldName);
} else if (zodType instanceof zod.z.ZodNullable) {
return convertZodNullable(zodType, jsonSchema, fieldName);
} else if (zodType instanceof zod.z.ZodDefault) {
return convertZodDefault(zodType, jsonSchema, fieldName);
} else {
jsonSchema.type = "string";
console.warn(`Unsupported zod type for field ${fieldName}, using string as fallback`);
}
return jsonSchema;
}
function convertZodString(zodType, jsonSchema) {
jsonSchema.type = "string";
if (zodType._def.checks) {
for (const check of zodType._def.checks) {
switch (check.kind) {
case "min":
jsonSchema.minLength = check.value;
break;
case "max":
jsonSchema.maxLength = check.value;
break;
case "regex":
jsonSchema.pattern = check.regex.source;
break;
case "email":
jsonSchema.format = "email";
break;
case "url":
jsonSchema.format = "uri";
break;
}
}
}
return jsonSchema;
}
function convertZodNumber(zodType, jsonSchema) {
jsonSchema.type = "number";
if (zodType._def.checks) {
for (const check of zodType._def.checks) {
switch (check.kind) {
case "min":
jsonSchema.minimum = check.value;
break;
case "max":
jsonSchema.maximum = check.value;
break;
case "int":
jsonSchema.type = "integer";
break;
}
}
}
return jsonSchema;
}
function convertZodArray(zodType, jsonSchema, fieldName) {
jsonSchema.type = "array";
jsonSchema.items = convertZodTypeToJsonSchema(zodType._def.type, `${fieldName}[]`);
if (zodType._def.minLength !== null) {
jsonSchema.minItems = zodType._def.minLength.value;
}
if (zodType._def.maxLength !== null) {
jsonSchema.maxItems = zodType._def.maxLength.value;
}
return jsonSchema;
}
function convertZodEnum(zodType, jsonSchema) {
jsonSchema.type = "string";
jsonSchema.enum = zodType._def.values;
return jsonSchema;
}
function convertZodObject(zodType, jsonSchema) {
jsonSchema.type = "object";
const nestedSchema = zodToJsonSchema(zodType);
jsonSchema.properties = nestedSchema.properties;
jsonSchema.required = nestedSchema.required;
return jsonSchema;
}
function convertZodUnion(zodType, jsonSchema, fieldName) {
jsonSchema.oneOf = zodType._def.options.map(
(option) => convertZodTypeToJsonSchema(option, fieldName)
);
return jsonSchema;
}
function convertZodNullable(zodType, jsonSchema, fieldName) {
const innerSchema = convertZodTypeToJsonSchema(zodType._def.innerType, fieldName);
jsonSchema.type = [innerSchema.type, "null"];
Object.assign(jsonSchema, innerSchema);
return jsonSchema;
}
function convertZodDefault(zodType, jsonSchema, fieldName) {
const innerSchema = convertZodTypeToJsonSchema(zodType._def.innerType, fieldName);
Object.assign(jsonSchema, innerSchema);
jsonSchema.default = zodType._def.defaultValue();
return jsonSchema;
}
function getZodDescription(zodType) {
const description = zodType._def.description;
if (description) return description;
if (zodType instanceof zod.z.ZodOptional || zodType instanceof zod.z.ZodNullable) {
return getZodDescription(zodType._def.innerType);
}
return void 0;
}
function isOptionalType(zodType) {
return zodType instanceof zod.z.ZodOptional || zodType instanceof zod.z.ZodDefault;
}
function isNullableType(zodType) {
return zodType instanceof zod.z.ZodNullable;
}
// src/factories/function-factory.ts
function createFunction(options) {
const { name, description, parameters, execute } = options;
const schema = {
name,
description,
parameters: parameters instanceof zod.z.ZodObject ? zodToJsonSchema(parameters) : parameters
};
const wrappedExecute = async (params) => {
try {
if (parameters instanceof zod.z.ZodObject) {
parameters.parse(params);
}
return await Promise.resolve(execute(params));
} catch (error) {
if (error instanceof zod.z.ZodError) {
const errorMessage = formatZodError(error);
throw new Error(`Parameter validation failed: ${errorMessage}`);
}
throw error;
}
};
return {
name,
description,
schema,
execute: wrappedExecute
};
}
function functionFromCallback(name, fn, description) {
const paramInfo = extractParameterInfo(fn);
const paramSchema = createParameterSchema(paramInfo.argNames);
const execute = async (params) => {
const args = paramInfo.argNames.map((name2) => params[name2]);
return await Promise.resolve(fn(...args));
};
return {
name,
description,
schema: { name, description, parameters: paramSchema },
execute
};
}
function createValidatedFunction(options) {
const baseFunction = createFunction(options);
if (!options.validateResult) {
return baseFunction;
}
const wrappedExecute = async (params) => {
const result = await baseFunction.execute(params);
if (!options.validateResult(result)) {
throw new Error(
options.resultErrorMessage || "Function result validation failed"
);
}
return result;
};
return {
...baseFunction,
execute: wrappedExecute
};
}
function formatZodError(error) {
return error.errors.map((e) => {
const path = e.path.length > 0 ? e.path.join(".") : "root";
return `${path}: ${e.message}`;
}).join(", ");
}
function extractParameterInfo(fn) {
const fnStr = fn.toString();
const argsMatch = fnStr.match(/\(([^)]*)\)/);
const argNames = argsMatch ? argsMatch[1].split(",").map((arg) => arg.trim()).filter(Boolean) : [];
return { argNames };
}
function createParameterSchema(argNames) {
return {
type: "object",
properties: Object.fromEntries(
argNames.map((name) => [name, { type: "string" }])
),
required: argNames
};
}
// src/registry/function-registry.ts
var FunctionRegistry = class {
/** @internal Map of function names to handlers */
functions = /* @__PURE__ */ new Map();
/** @internal Map of function names to definitions */
definitions = /* @__PURE__ */ new Map();
/**
* Register a function with its definition and handler
*
* @param definition - Function definition with schema
* @param handler - Function execution handler
*/
register(definition, handler) {
this.functions.set(definition.name, handler);
this.definitions.set(definition.name, definition);
}
/**
* Unregister a function by name
*
* @param name - Function name to unregister
* @returns True if function was found and removed
*/
unregister(name) {
const hadFunction = this.functions.delete(name);
const hadDefinition = this.definitions.delete(name);
return hadFunction || hadDefinition;
}
/**
* Check if a function is registered
*
* @param name - Function name to check
* @returns True if function is registered
*/
has(name) {
return this.functions.has(name);
}
/**
* Get all registered function definitions
*
* @returns Array of all function definitions
*/
getAllDefinitions() {
return Array.from(this.definitions.values());
}
/**
* Get function definition by name
*
* @param name - Function name
* @returns Function definition if found
*/
getDefinition(name) {
return this.definitions.get(name);
}
/**
* Get all registered function names
*
* @returns Array of function names
*/
getFunctionNames() {
return Array.from(this.functions.keys());
}
/**
* Get total number of registered functions
*
* @returns Number of registered functions
*/
getCount() {
return this.functions.size;
}
/**
* Clear all registered functions
*/
clear() {
this.functions.clear();
this.definitions.clear();
}
/**
* Execute function call
*
* @param functionCall - Function call with name and arguments
* @param context - Optional execution context
* @returns Promise resolving to function call result
*/
async execute(functionCall, context) {
const { name, arguments: args } = functionCall;
const handler = this.functions.get(name);
if (!handler) {
return {
name,
error: `Function '${name}' is not registered`
};
}
try {
const parsedArgs = this.parseArguments(args);
const result = await handler(parsedArgs, context);
return {
name,
result
};
} catch (error) {
return {
name,
error: error instanceof Error ? error.message : String(error)
};
}
}
/**
* Parse function arguments safely
*
* @param args - Arguments to parse (string or object)
* @returns Parsed arguments object
*/
parseArguments(args) {
if (typeof args === "string") {
try {
return JSON.parse(args);
} catch (error) {
throw new Error(`Failed to parse function arguments: ${error instanceof Error ? error.message : String(error)}`);
}
}
return args || {};
}
};
function createFunctionSchema(definition) {
const propertySchemas = {};
if (definition.parameters && definition.parameters.properties) {
for (const [key, prop] of Object.entries(definition.parameters.properties)) {
propertySchemas[key] = convertJsonSchemaToZod(prop, key);
}
}
return zod.z.object(propertySchemas);
}
function convertJsonSchemaToZod(property, fieldName) {
if (!property.type) {
console.warn(`No type specified for field ${fieldName}, using z.any()`);
return zod.z.any();
}
switch (property.type) {
case "string":
return createZodString(property);
case "number":
return createZodNumber(property);
case "integer":
return createZodInteger(property);
case "boolean":
return zod.z.boolean();
case "array":
return createZodArray(property, fieldName);
case "object":
return createZodObject();
default:
console.warn(`Unsupported type ${property.type} for field ${fieldName}, using z.any()`);
return zod.z.any();
}
}
function createZodString(property) {
let schema = zod.z.string();
if (property.minLength !== void 0) {
schema = schema.min(property.minLength);
}
if (property.maxLength !== void 0) {
schema = schema.max(property.maxLength);
}
if (property.pattern) {
schema = schema.regex(new RegExp(property.pattern));
}
if (property.format === "email") {
schema = schema.email();
}
if (property.format === "uri" || property.format === "url") {
schema = schema.url();
}
return schema;
}
function createZodNumber(property) {
let schema = zod.z.number();
if (property.minimum !== void 0) {
schema = schema.min(property.minimum);
}
if (property.maximum !== void 0) {
schema = schema.max(property.maximum);
}
return schema;
}
function createZodInteger(property) {
let schema = zod.z.number().int();
if (property.minimum !== void 0) {
schema = schema.min(property.minimum);
}
if (property.maximum !== void 0) {
schema = schema.max(property.maximum);
}
return schema;
}
function createZodArray(property, fieldName) {
const itemSchema = property.items ? convertJsonSchemaToZod(property.items, `${fieldName}[]`) : zod.z.any();
let schema = zod.z.array(itemSchema);
if (property.minItems !== void 0) {
schema = schema.min(property.minItems);
}
if (property.maxItems !== void 0) {
schema = schema.max(property.maxItems);
}
return schema;
}
function createZodObject(_property) {
return zod.z.record(zod.z.any());
}
function zodToJsonSchema2(schema) {
const shape = schema._def.shape();
const properties = {};
const required = [];
Object.entries(shape).forEach(([key, zodType]) => {
const typeObj = zodType;
let property = {};
if (typeObj instanceof zod.z.ZodNumber) {
property.type = "number";
} else if (typeObj instanceof zod.z.ZodString) {
property.type = "string";
} else if (typeObj instanceof zod.z.ZodBoolean) {
property.type = "boolean";
} else if (typeObj instanceof zod.z.ZodEnum) {
property.type = "string";
property.enum = typeObj._def.values;
} else if (typeObj instanceof zod.z.ZodArray) {
property.type = "array";
if (typeObj._def.type instanceof zod.z.ZodObject) {
property.items = zodToJsonSchema2(typeObj._def.type);
}
} else if (typeObj instanceof zod.z.ZodObject) {
property = zodToJsonSchema2(typeObj);
}
const description = typeObj._def.description;
if (description) {
property.description = description;
}
const isOptional = typeObj instanceof zod.z.ZodOptional;
if (!isOptional) {
required.push(key);
}
properties[key] = property;
});
return {
type: "object",
properties,
required: required.length > 0 ? required : void 0
};
}
function zodFunctionToSchema(tool) {
return {
name: tool.name,
description: tool.description,
parameters: zodToJsonSchema2(tool.parameters)
};
}
// src/performance/performance-monitor.ts
var PerformanceMonitor = class {
callRecords = [];
memorySnapshots = [];
maxRecords;
monitoringInterval;
eventListeners = [];
isMonitoring = false;
// External statistics sources
cacheStatsProvider;
lazyLoadStatsProvider;
resourceStatsProvider;
constructor(options = {}) {
this.maxRecords = options.maxRecords || 1e4;
if (options.monitoringIntervalMs) {
this.startMonitoring(options.monitoringIntervalMs);
}
}
/**
* Start monitoring
*/
startMonitoring(intervalMs = 5e3) {
if (this.isMonitoring) {
return;
}
this.isMonitoring = true;
this.monitoringInterval = setInterval(() => {
this.collectMetrics();
}, intervalMs);
}
/**
* Stop monitoring
*/
stopMonitoring() {
if (!this.isMonitoring) {
return;
}
this.isMonitoring = false;
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = void 0;
}
}
/**
* Record tool call
*/
recordToolCall(record) {
this.callRecords.push(record);
if (this.callRecords.length > this.maxRecords) {
const removeCount = Math.floor(this.maxRecords * 0.1);
this.callRecords.splice(0, removeCount);
}
this.collectMemorySnapshot();
}
/**
* Helper for recording tool call start time
*/
startToolCall(toolName, parameters) {
const callId = `${toolName}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const parameterSize = this.estimateObjectSize(parameters);
this._pendingCalls = this._pendingCalls || /* @__PURE__ */ new Map();
this._pendingCalls.set(callId, {
toolName,
startTime: performance.now(),
parameterSize
});
return callId;
}
/**
* Helper for recording tool call completion
*/
endToolCall(callId, success, response, error) {
const pendingCalls = this._pendingCalls;
if (!pendingCalls || !pendingCalls.has(callId)) {
return;
}
const pending = pendingCalls.get(callId);
pendingCalls.delete(callId);
const endTime = performance.now();
const responseSize = response ? this.estimateObjectSize(response) : 0;
this.recordToolCall({
toolName: pending.toolName,
startTime: pending.startTime,
endTime,
duration: endTime - pending.startTime,
success,
error,
parameterSize: pending.parameterSize,
responseSize
});
}
/**
* Register external statistics providers
*/
setCacheStatsProvider(provider) {
this.cacheStatsProvider = provider;
}
setLazyLoadStatsProvider(provider) {
this.lazyLoadStatsProvider = provider;
}
setResourceStatsProvider(provider) {
this.resourceStatsProvider = provider;
}
/**
* Register event listener
*/
addEventListener(listener) {
this.eventListeners.push(listener);
}
/**
* Remove event listener
*/
removeEventListener(listener) {
const index = this.eventListeners.indexOf(listener);
if (index > -1) {
this.eventListeners.splice(index, 1);
}
}
/**
* Get current performance metrics
*/
getMetrics() {
const now = performance.now();
const recentRecords = this.callRecords.filter(
(record) => now - record.endTime < 6e4
// last 1 minute
);
const totalCalls = this.callRecords.length;
const successfulCalls = this.callRecords.filter((r) => r.success).length;
const failedCalls = totalCalls - successfulCalls;
const successRate = totalCalls > 0 ? successfulCalls / totalCalls : 0;
const durations = this.callRecords.map((r) => r.duration);
const averageCallTime = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
const maxCallTime = durations.length > 0 ? Math.max(...durations) : 0;
const minCallTime = durations.length > 0 ? Math.min(...durations) : 0;
const throughput = recentRecords.length / 60;
const memoryUsage = this.calculateMemoryMetrics();
return {
toolCallCount: totalCalls,
averageCallTime,
maxCallTime,
minCallTime,
successfulCalls,
failedCalls,
successRate,
throughput,
memoryUsage,
cacheMetrics: this.cacheStatsProvider ? this.cacheStatsProvider() : null,
lazyLoadMetrics: this.lazyLoadStatsProvider ? this.lazyLoadStatsProvider() : null,
resourceMetrics: this.resourceStatsProvider ? this.resourceStatsProvider() : null
};
}
/**
* Get performance metrics for specific tool
*/
getToolMetrics(toolName) {
const toolRecords = this.callRecords.filter((r) => r.toolName === toolName);
if (toolRecords.length === 0) {
return {
toolCallCount: 0,
averageCallTime: 0,
maxCallTime: 0,
minCallTime: 0,
successfulCalls: 0,
failedCalls: 0,
successRate: 0,
throughput: 0
};
}
const successfulCalls = toolRecords.filter((r) => r.success).length;
const failedCalls = toolRecords.length - successfulCalls;
const durations = toolRecords.map((r) => r.duration);
return {
toolCallCount: toolRecords.length,
averageCallTime: durations.reduce((sum, d) => sum + d, 0) / durations.length,
maxCallTime: Math.max(...durations),
minCallTime: Math.min(...durations),
successfulCalls,
failedCalls,
successRate: successfulCalls / toolRecords.length,
throughput: toolRecords.filter(
(r) => performance.now() - r.endTime < 6e4
).length / 60
};
}
/**
* Reset performance metrics
*/
reset() {
this.callRecords = [];
this.memorySnapshots = [];
}
/**
* Generate performance report
*/
generateReport() {
const metrics = this.getMetrics();
return `
=== Tool Performance Report ===
Total Calls: ${metrics.toolCallCount}
Success Rate: ${(metrics.successRate * 100).toFixed(2)}%
Average Response Time: ${metrics.averageCallTime.toFixed(2)}ms
Max Response Time: ${metrics.maxCallTime.toFixed(2)}ms
Min Response Time: ${metrics.minCallTime.toFixed(2)}ms
Throughput: ${metrics.throughput.toFixed(2)} TPS
Memory Usage:
- Current Heap: ${(metrics.memoryUsage.currentHeapUsed / 1024 / 1024).toFixed(2)}MB
- Max Heap: ${(metrics.memoryUsage.maxHeapUsed / 1024 / 1024).toFixed(2)}MB
- Average Heap: ${(metrics.memoryUsage.averageHeapUsed / 1024 / 1024).toFixed(2)}MB
${metrics.cacheMetrics ? `
Cache Performance:
- Hit Rate: ${(metrics.cacheMetrics.hitRate * 100).toFixed(2)}%
- Cache Items: ${metrics.cacheMetrics.totalItems}
- Memory Usage: ${(metrics.cacheMetrics.estimatedMemoryUsage / 1024).toFixed(2)}KB
` : ""}
${metrics.resourceMetrics ? `
Resource Management:
- Total Resources: ${metrics.resourceMetrics.totalResources}
- Memory Usage: ${(metrics.resourceMetrics.estimatedMemoryUsage / 1024 / 1024).toFixed(2)}MB
` : ""}
`.trim();
}
/**
* Collect memory snapshot
*/
collectMemorySnapshot() {
const memUsage = process.memoryUsage();
this.memorySnapshots.push(memUsage.heapUsed);
if (this.memorySnapshots.length > 1e3) {
this.memorySnapshots.splice(0, 100);
}
}
/**
* Calculate memory metrics
*/
calculateMemoryMetrics() {
const memUsage = process.memoryUsage();
return {
currentHeapUsed: memUsage.heapUsed,
maxHeapUsed: this.memorySnapshots.length > 0 ? Math.max(...this.memorySnapshots) : memUsage.heapUsed,
averageHeapUsed: this.memorySnapshots.length > 0 ? this.memorySnapshots.reduce((sum, snap) => sum + snap, 0) / this.memorySnapshots.length : memUsage.heapUsed,
external: memUsage.external,
rss: memUsage.rss
};
}
/**
* Collect metrics and trigger events
*/
collectMetrics() {
try {
const metrics = this.getMetrics();
for (const listener of this.eventListeners) {
try {
listener(metrics);
} catch (error) {
console.error("Performance event listener error:", error);
}
}
} catch (error) {
console.error("Failed to collect performance metrics:", error);
}
}
/**
* Estimate object size
*/
estimateObjectSize(obj) {
if (obj === null || obj === void 0) {
return 8;
}
switch (typeof obj) {
case "string":
return obj.length * 2;
case "number":
return 8;
case "boolean":
return 4;
case "object":
if (Array.isArray(obj)) {
return obj.reduce((acc, item) => acc + this.estimateObjectSize(item), 0);
} else {
let size = 0;
for (const key in obj) {
size += key.length * 2;
size += this.estimateObjectSize(obj[key]);
}
return size;
}
default:
return 16;
}
}
};
var globalPerformanceMonitor = new PerformanceMonitor({
maxRecords: 1e4,
monitoringIntervalMs: 5e3
});
// src/tool-provider.ts
var ToolProviderError = class extends Error {
code;
context;
constructor(message, code = "TOOL_PROVIDER_ERROR", context) {
super(message);
this.name = "ToolProviderError";
this.code = code;
this.context = context;
}
};
var ToolNotFoundError = class extends ToolProviderError {
constructor(toolName, availableTools) {
super(
`Tool '${toolName}' not found.${availableTools ? ` Available tools: ${availableTools.join(", ")}` : ""}`,
"TOOL_NOT_FOUND",
{ toolName, availableTools }
);
}
};
var ToolExecutionError = class extends ToolProviderError {
constructor(toolName, originalError) {
const errorMessage = originalError instanceof Error ? originalError.message : String(originalError);
super(
`Tool '${toolName}' call failed: ${errorMessage}`,
"TOOL_EXECUTION_ERROR",
{ toolName, originalError: errorMessage }
);
}
};
var BaseToolProvider = class {
logger;
constructor(options) {
this.logger = options?.logger;
}
/**
* Get available tool names from functions list
*/
getAvailableTools() {
return this.functions?.map((f) => f.name) || [];
}
/**
* Check if a tool exists
*/
hasTool(toolName) {
return this.getAvailableTools().includes(toolName);
}
/**
* Validate tool existence before calling
*/
validateToolExists(toolName) {
if (!this.hasTool(toolName)) {
throw new ToolNotFoundError(toolName, this.getAvailableTools());
}
}
/**
* Log error with context
*/
logError(message, context) {
if (this.logger) {
this.logger(message, context);
} else {
console.error(message, context);
}
}
/**
* Handle tool execution with common error handling and performance monitoring
*/
async executeToolSafely(toolName, parameters, executor) {
const callId = globalPerformanceMonitor.startToolCall(toolName, parameters);
try {
this.validateToolExists(toolName);
this.logError(`Tool '${toolName}' execution started`, { toolName, parameters });
const result = await executor();
this.logError(`Tool '${toolName}' execution succeeded`, { toolName, result });
globalPerformanceMonitor.endToolCall(callId, true, result);
return result;
} catch (error) {
this.logError(`Error occurred while calling tool '${toolName}'`, { toolName, parameters, error });
const errorMessage = error instanceof Error ? error.message : String(error);
globalPerformanceMonitor.endToolCall(callId, false, void 0, errorMessage);
if (error instanceof ToolProviderError) {
throw error;
}
throw new ToolExecutionError(toolName, error);
}
}
};
// src/performance/cache-manager.ts
var CacheManager = class {
cache = /* @__PURE__ */ new Map();
maxSize;
defaultTTL;
hits = 0;
misses = 0;
expired = 0;
constructor(options = {}) {
this.maxSize = options.maxSize || 1e3;
this.defaultTTL = options.defaultTTL;
}
/**
* Get value from cache
*/
get(key) {
const item = this.cache.get(key);
if (!item) {
this.misses++;
return void 0;
}
if (this.isExpired(item)) {
this.cache.delete(key);
this.expired++;
this.misses++;
return void 0;
}
item.accessCount++;
item.lastAccessed = Date.now();
this.hits++;
return item.data;
}
/**
* Set value in cache
*/
set(key, value, ttl) {
if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
this.evictLRU();
}
const now = Date.now();
const item = {
data: value,
timestamp: now,
ttl: ttl || this.defaultTTL,
accessCount: 1,
lastAccessed: now
};
this.cache.set(key, item);
}
/**
* Delete item from cache
*/
delete(key) {
return this.cache.delete(key);
}
/**
* Check if specific key exists in cache
*/
has(key) {
const item = this.cache.get(key);
if (!item) return false;
if (this.isExpired(item)) {
this.cache.delete(key);
this.expired++;
return false;
}
return true;
}
/**
* Clear entire cache
*/
clear() {
this.cache.clear();
this.hits = 0;
this.misses = 0;
this.expired = 0;
}
/**
* Clean up expired items
*/
cleanup() {
let cleanedCount = 0;
const now = Date.now();
for (const [key, item] of this.cache.entries()) {
if (this.isExpired(item, now)) {
this.cache.delete(key);
cleanedCount++;
this.expired++;
}
}
return cleanedCount;
}
/**
* Get cache statistics
*/
getStats() {
const totalRequests = this.hits + this.misses;
const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0;
let estimatedMemoryUsage = 0;
for (const [key, item] of this.cache.entries()) {
estimatedMemoryUsage += key.length * 2;
estimatedMemoryUsage += this.estimateObjectSize(item);
}
return {
totalItems: this.cache.size,
hits: this.hits,
misses: this.misses,
hitRate,
expired: this.expired,
estimatedMemoryUsage
};
}
/**
* Get all cache keys
*/
keys() {
return Array.from(this.cache.keys());
}
/**
* Get all cache values
*/
values() {
return Array.from(this.cache.values()).map((item) => item.data);
}
/**
* Get cache size
*/
size() {
return this.cache.size;
}
/**
* Check if item has expired
*/
isExpired(item, now) {
if (!item.ttl) return false;
const currentTime = now || Date.now();
return currentTime - item.timestamp > item.ttl;
}
/**
* Evict least recently used item using LRU algorithm
*/
evictLRU() {
let lruKey;
let lruTime = Infinity;
for (const [key, item] of this.cache.entries()) {
if (item.lastAccessed < lruTime) {
lruTime = item.lastAccessed;
lruKey = key;
}
}
if (lruKey) {
this.cache.delete(lruKey);
}
}
/**
* Estimate object size (approximate)
*/
estimateObjectSize(obj) {
let size = 0;
if (obj === null || obj === void 0) {
return 8;
}
switch (typeof obj) {
case "string":
return obj.length * 2;
// UTF-16
case "number":
return 8;
case "boolean":
return 4;
case "object":
if (Array.isArray(obj)) {
return obj.reduce((acc, item) => acc + this.estimateObjectSize(item), 0);
} else {
for (const key in obj) {
size += key.length * 2;
size += this.estimateObjectSize(obj[key]);
}
return size;
}
default:
return 16;
}
}
};
var FunctionSchemaCacheManager = class extends CacheManager {
constructor() {
super({
maxSize: 500,
// smaller number as function schemas can be large
defaultTTL: 30 * 60 * 1e3
// 30 minutes
});
}
/**
* Generate cache key from tool definitions
*/
generateKey(toolDefinitions) {
const keys = Object.keys(toolDefinitions).sort();
const signature = keys.map((key) => {
const tool = toolDefinitions[key];
return `${key}:${tool.name}:${tool.description}`;
}).join("|");
return this.simpleHash(signature);
}
/**
* Simple hash function
*/
simpleHash(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
return Math.abs(hash).toString(36);
}
};
var globalFunctionSchemaCache = new FunctionSchemaCacheManager();
var globalToolCache = new CacheManager({
maxSize: 1e3,
defaultTTL: 60 * 60 * 1e3
// 1 hour
});
var CacheCleanupScheduler = class {
intervals = [];
/**
* Start periodic cache cleanup
*/
start(cacheManagers, intervalMs = 5 * 60 * 1e3) {
for (const cache of cacheManagers) {
const interval = setInterval(() => {
const cleaned = cache.cleanup();
if (cleaned > 0) {
console.log(`Cache cleanup: removed ${cleaned} expired items`);
}
}, intervalMs);
this.intervals.push(interval);
}
}
/**
* Stop periodic cache cleanup
*/
stop() {
for (const interval of this.intervals) {
clearInterval(interval);
}
this.intervals = [];
}
};
var globalCacheCleanupScheduler = new CacheCleanupScheduler();
// src/function-tool-provider.ts
var ZodFunctionToolProvider = class extends BaseToolProvider {
tools;
_functions = null;
enableCache;
cacheManager;
cacheKey;
constructor(options) {
super({ logger: options.logger });
this.tools = options.tools;
this.enableCache = options.enableCache !== false;
this.cacheManager = options.cacheManager || globalFunctionSchemaCache;
this.cacheKey = this.enableCache ? this.cacheManager.generateKey(this.tools) : "";
}
/**
* Function schema list (lazy loading + caching)
*/
get functions() {
if (this._functions) {
return this._functions;
}
if (this.enableCache) {
const cached = this.cacheManager.get(this.cacheKey);
if (cached) {
this._functions = cached;
this.logDebug("Loaded function schemas from cache.", {
toolCount: cached.length,
cacheKey: this.cacheKey
});
return this._functions;
}
}
this._functions = this.convertToolsToFunctions();
if (this.enableCache) {
this.cacheManager.set(this.cacheKey, this._functions);
this.logDebug("Saved function schemas to cache.", {
toolCount: this._functions.length,
cacheKey: this.cacheKey
});
}
return this._functions;
}
/**
* Convert tool definitions to JSON schema
*/
convertToolsToFunctions() {
const startTime = this.enableCache ? performance.now() : 0;
const functions = Object.values(this.tools).map((tool) => {
const properties = {};
const required = [];
const shape = tool.parameters.shape || {};
for (const propName in shape) {
const prop = shape[propName];
let type = "string";
if (prop._def.typeName === "ZodNumber") type = "number";
if (prop._def.typeName === "ZodBoolean") type = "boolean";
if (prop._def.typeName === "ZodArray") type = "array";
if (prop._def.typeName === "ZodObject") type = "object";
properties[propName] = {
type,
description: prop._def.description || `${propName} parameter`
};
if (prop._def.typeName === "ZodEnum") {
properties[propName].enum = prop._def.values;
}
if (!prop._def.isOptional) {
required.push(propName);
}
}
return {
name: tool.name,
description: tool.description,
parameters: {
type: "object",
properties,
required: required.length > 0 ? required : void 0
}
};
});
if (this.enableCache) {
const endTime = performance.now();
this.logDebug("Function schema conversion completed", {
toolCount: functions.length,
processingTime: `${(endTime - startTime).toFixed(2)}ms`
});
}
return functions;
}
/**
* Tool call implementation
*/
async callTool(toolName, parameters) {
return this.executeToolSafely(toolName, parameters, async () => {
const tool = this.tools[toolName];
if (!tool) {
throw new Error(`Tool definition not found.`);
}
return await tool.handler(parameters);
});
}
/**
* Check if specific tool exists (override)
*/
hasTool(toolName) {
return toolName in this.tools;
}
/**
* Get cache statistics
*/
getCacheStats() {
if (!this.enableCache) {
return null;
}
return this.cacheManager.getStats();
}
/**
* Clear cache
*/
clearCache() {
if (this.enableCache) {
this.cacheManager.delete(this.cacheKey);
this._functions = null;
this.logDebug("Deleted function schema cache.", { cacheKey: this.cacheKey });
}
}
/**
* Output debug log
*/
logDebug(message, context) {
this.logError(`[ZodFunctionToolProvider] ${message}`, context);
}
};
function createZodFunctionToolProvider(options) {
return new ZodFunctionToolProvider(options);
}
// src/mcp-tool-provider.ts
var MCPToolProvider = class extends BaseToolProvider {
mcpClient;
functions;
constructor(options) {
super({ logger: options.logger });
this.mcpClient = options.mcpClient;
this.initializeFunctions();
}
/**
* Get tool list from MCP client and convert to function schema
*/
async initializeFunctions() {
try {
if (this.mcpClient.listTools) {
const result = await this.mcpClient.listTools();
this.functions = result.tools.map((tool) => ({
name: tool.name,
description: tool.description || `MCP tool: ${tool.name}`,
parameters: tool.inputSchema || {
type: "object",
properties: {},
required: []
}
}));
}
} catch (error) {
this.logError("MCP tool list initialization failed", { error });
this.functions = [];
}
}
/**
* Tool call implementation
*/
async callTool(toolName, parameters) {
return this.executeToolSafely(toolName, parameters, async () => {
return await this.mcpClient.callTool(toolName, parameters);
});
}
/**
* Return available tool list (override)
* For MCP, function list may not exist, so handle dynamically
*/
getAvailableTools() {
if (!this.functions || this.functions.length === 0) {
return [];
}
return super.getAvailableTools();
}
/**
* Check if specific tool exists (override)
* For MCP, tool may exist even without function list, so always return true
*/
hasTool(toolName) {
if (!this.functions || this.functions.length === 0) {
return true;
}
return super.hasTool(toolName);
}
};
function createMcpToolProvider(mcpClient) {
return new MCPToolProvider({ mcpClient });
}
// src/openapi-tool-provider.ts
var OpenAPIToolProvider = class extends BaseToolProvider {
openApiSpec;
baseUrl;
functions;
constructor(options) {
super({ logger: options.logger });
this.openApiSpec = options.openApiSpec;
this.baseUrl = options.baseUrl;
}
/**
* Convert OpenAPI spec to function list
*/
convertOpenAPIToFunctions(spec) {
const functions = [];
for (const path in spec.paths) {
for (const method in spec.paths[path]) {
const operation = spec.paths[path][method];
const functionName = operation.operationId || `${method}${path.replace(/\//g, "_")}`;
const parameters = {};
const required = [];
if (operation.parameters) {
for (const param of operation.parameters) {
parameters[param.name] = {
type: param.schema?.type || "string",
description: param.description || `${param.name} parameter`
};
if (param.required) {
required.push(param.name);
}
}
}
if (operation.requestBody?.content?.["application/json"]?.schema) {
const schema = operation.requestBody.content["application/json"].schema;
if (schema.properties) {
for (const propName in schema.properties) {
parameters[propName] = {
type: schema.properties[propName].type || "string",
description: schema.properties[propName].description || `${propName} property`
};
}
if (schema.required) {
required.push(...schema.required);
}
}
}
functions.push({
name: functionName,
description: operation.summary || operation.description || `${method.toUpperCase()} ${path}`,
parameters: {
type: "object",
properties: parameters,
required: required.length > 0 ? required : void 0
}
});
}
}
return functions;
}
/**
* Initialize OpenAPI spec (lazy loading)
*/
async initializeSpec() {
if (this.functions) return;
try {
if (typeof this.openApiSpec === "string") {
const response = await fetch(this.openApiSpec);
if (!response.ok) {
throw new Error(`Cannot fetch OpenAPI spec: ${response.status} ${response.statusText}`);
}
this.openApiSpec = await response.json();
}
this.functions = this.convertOpenAPIToFunctions(this.openApiSpec);
} catch (error) {
throw new Error(`OpenAPI spec initialization failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
/**
* Tool call implementation
*/
async callTool(toolName, parameters) {
await this.initializeSpec();
return this.executeToolSafely(toolName, parameters, async () => {
const baseUrl = this.baseUrl || "";
const url = `${baseUrl}/${toolName}`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(parameters)
});
if (!response.ok) {
throw new Error(`API call failed: ${response.status} ${response.statusText}`);
}
return await response.json();
});
}
/**
* Return available tool list (override)
*/
getAvailableTools() {
if (!this.functions) {
return [];
}
return super.getAvailableTools();
}
/**
* Check if specific tool exists (override)
*/
hasTool(toolName) {
if (!this.functions) {
return false;
}
return super.hasTool(toolName);
}
};
function createOpenAPIToolProvider(openApiSpec, options) {
return new OpenAPIToolProvider({
openApiSpec,
baseUrl: options?.baseUrl
});
}
// src/tool-provider-factory.ts
var ToolProviderFactory = class {
providers = /* @__PURE__ */ new Map();
logger;
constructor(options) {
this.logger = options?.logger;
}
/**
* Create Zod Function Tool Provider
*/
createZodFunctionProvider(name, tools) {
const provider = new ZodFunctionToolProvider({
tools,
logger: this.logger
});
this.providers.set(name, provider);
return provider;
}
/**
* Create OpenAPI Tool Provider
*/
createOpenAPIProvider(name, openApiSpec, options) {
const provider = new OpenAPIToolProvider({
openApiSpec,
baseUrl: options?.baseUrl,
logger: this.logger
});
this.providers.set(name, provider);
return provider;
}
/**
* Create MCP Tool Provider
*/
createMCPProvider(name, mcpClient) {
const provider = new MCPToolProvider({
mcpClient,
logger: this.logger
});
this.providers.set(name, provider);
return provider;
}
/**
* Generic Tool Provider creation method
*/
createProvider(type, name, config) {
let provider;
switch (type) {
case "zod-function":
provider = new ZodFunctionToolProvider(config);
break;
case "openapi":
provider = new OpenAPIToolProvider(config);
break;
case "mcp":
provider = new MCPToolProvider(config);
break;
default:
throw new Error(`Unsupported tool provider type: ${type}`);
}
this.providers.set(name, provider);
return provider;
}
/**
* Get registered Provider
*/
getProvider(name) {
return this.providers.get(name);
}
/**
* Get all registered Provider list
*/
getAllProviders() {
return Object.fromEntries(this.providers.entries());
}
/**
* Remove Provider
*/
removeProvider(name) {
return this.providers.delete(name);
}
/**
* Get integrated list of available tools from all Providers
*/
getAllAvailableTools() {
const result = {};
for (const [name, provider] of this.providers.entries()) {
result[name] = provider.getAvailableTools?.() || [];
}
return result;
}
/**
* Find Provider that provides specific tool
*/
findProviderForTool(toolName) {
for (const [name, provider] of this.providers.entries()) {
if (provider.hasTool?.(toolName)) {
return { providerName: name, provider };
}
}
return void 0;
}
/**
* Tool call (automatically find appropriate Provider)
*/
async callTool(toolName, parameters) {
const providerInfo = this.findProviderForTool(toolName);
if (!providerInfo) {
throw new Error(`Cannot find provider that provides tool '${toolName}'.`);
}
return await providerInfo.provider.callTool(toolName, parameters);
}
};
var globalFactory;
function getGlobalToolProviderFactory() {
if (!globalFactory) {
globalFactory = new ToolProviderFactory();
}
return globalFactory;
}
function createZodFunctionProvider(tools, options) {
return new ZodFunctionToolProvider({
tools,
logger: options?.logger
});
}
function createOpenAPIProvider(openApiSpec, options) {
return new OpenAPIToolProvider({
openApiSpec,
baseUrl: options?.baseUrl,
logger: options?.logger
});
}
function createMCPProvider(mcpClient, options) {
return new MCPToolProvider({
mcpClient,
logger: options?.logger
});
}
// src/tool/base-tool.ts
var BaseTool = class {
/**
* Tool name
*/
name;
/**
* Tool description
*/
description;
/**
* Tool category
*/
category;
/**
* Tool version
*/
version;
/**
* Whether to validate parameters
*/
validateParams;
/**
* Tool execution function
*/
_execute;
/**
* Pre-execution hook
*/
beforeExecute;
/**
* Post-execution hook
*/
afterExecute;
/**
* Constructor
*
* @param options - Base tool options
*/
constructor(options) {
this.name = options.name;
this.description = options.description;
this.category = options.category;
this.version = options.version;
this.validateParams = options.validateParams ?? true;
this._execute = options.execute;
this.beforeExecute = options.beforeExecute;
this.afterExecute = options.afterExecute;
}
/**
* Execute tool
*
* @param params - Tool parameters
* @returns Tool execution result
*/
async execute(params) {
try {
let validatedParams = params;
if (this.validateParams) {
validatedParams = this.validateParameters(params);
}
if (this.beforeExecute) {
validatedParams = await Promise.resolve(this.beforeExecute(validatedParams));
}
let result = await Promise.resolve(this._execute(validatedParams));
if (this.afterExecute) {
result = await Promise.resolve(this.afterExecute(result));
}
return result;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
status: "error",
error: errorMessage
};
}
}
/**
* Convert to function schema
*
* @returns Function schema
*/
toFunctionSchema() {
return {
name: this.name,
description: this.description,
parameters: this.toJsonSchema()
};
}
/**
* Convert to function definition
*
* @returns Function definition
*/
toFunctionDefinition() {
return {
name: this.name,
description: this.description,
parameters: this.toJsonSchema()
};
}
/**
* Generate string representation
*
* @returns String representation of the tool
*/
toString() {
return `${this.constructor.name}(name=${this.name}, category=${this.category || "none"}, version=${this.version || "none"})`;
}
};
var ZodTool = class _ZodTool extends BaseTool {
/**
* Zod schema
*/
parameters;
/**
* Constructor
*
* @param options - Zod tool options
*/
constructor(options) {
super(options);
this.parameters = options.parameters;
}
/**
* Tool schema (returns Zod schema)
*
* @returns Zod schema
*/
get schema() {
return this.parameters;
}
/**
* Convert Zod schema to JSON schema
*
* @returns JSON schema format parameter definition
*/
toJsonSchema() {
const jsonSchema = {
type: "object",
properties: {}
};
const shape = this.parameters.shape;
for (const [key, zodType] of Object.entries(shape)) {
jsonSchema.properties[key] = {
type: this.getSchemaType(zodType),
description: this.getZodDescription(zodType) || void 0
};
if (zodType._def.values) {
jsonSchema.properties[key].enum = zodType._def.values;
}
}
jsonSchema.required = Object.entries(shape).filter(([_key, zodType]) => !this.isOptionalType(zodType)).map(([key]) => key);
if (jsonSchema.required?.length === 0) {
delete jsonSchema.required;
}
return jsonSchema;
}
/**
* Validate parameters using Zod schema
*
* @param params - Parameters to validate
* @returns Validated parameters
*/
validateParameters(params) {
return this.parameters.parse(params);
}
/**
* Extract description from Zod type
*
* @param zodType - Zod type
* @returns Description string
*/
getZodDescription(zodType) {
const description = zodType._def.description;
if (description) return description;
if (zodType instanceof zod.z.ZodOptional || zodType instanceof zod.z.ZodNullable) {
return this.getZodDescription(zodType._def.innerType);
}
return void 0;
}
/**
* Convert Zod schema type to JSON schema type
*
* @param schema - Zod schema
* @returns JSON schema type string
*/
getSchemaType(schema) {
if (schema instanceof zod.z.ZodString) {
return "string";
} else if (schema instanceof zod.z.ZodNumber) {
return "number";
} else if (schema instanceof zod.z.ZodBoolean) {
return "boolean";
} else if (schema instanceof zod.z.ZodArray) {
return "array";
} else if (schema instanceof zod.z.ZodObject) {
return "object";
} else if (schema instanceof zod.z.ZodEnum) {
return "string";
} else if (schema instanceof zod.z.ZodOptional) {
return this.getSchemaType(schema._def.innerType);
} else {
return "string";
}
}
/**
* Check if Zod type is optional
*
* @param zodType - Zod type
* @returns Whether the type is optional
*/