UNPKG

@utcp/sdk

Version:
1,340 lines (1,315 loc) 64.2 kB
// src/data/call_template.ts import { z } from "zod"; // src/interfaces/serializer.ts var ensurePluginsInitialized = null; function setPluginInitializer(fn) { ensurePluginsInitialized = fn; } var Serializer = class { constructor() { if (ensurePluginsInitialized) { ensurePluginsInitialized(); } } copy(obj) { return this.validateDict(this.toDict(obj)); } }; // src/data/call_template.ts var CallTemplateSerializer = class _CallTemplateSerializer extends Serializer { static serializers = {}; // No need for the whole plugin registry. Plugins just need to call this to register a new call template type static registerCallTemplate(callTemplateType, serializer, override = false) { if (!override && _CallTemplateSerializer.serializers[callTemplateType]) { return false; } _CallTemplateSerializer.serializers[callTemplateType] = serializer; return true; } toDict(obj) { const serializer = _CallTemplateSerializer.serializers[obj.call_template_type]; if (!serializer) { throw new Error(`No serializer found for call_template_type: ${obj.call_template_type}`); } return serializer.toDict(obj); } validateDict(obj) { const serializer = _CallTemplateSerializer.serializers[obj.call_template_type]; if (!serializer) { throw new Error(`Invalid call_template_type: ${obj.call_template_type}`); } return serializer.validateDict(obj); } }; var CallTemplateSchema = z.custom((obj) => { try { const validated = new CallTemplateSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid CallTemplate object" }); // src/data/utcp_manual.ts import { z as z3 } from "zod"; // src/data/tool.ts import { z as z2 } from "zod"; var JsonTypeSchema = z2.lazy(() => z2.union([ z2.string(), z2.number(), z2.boolean(), z2.null(), z2.record(z2.string(), JsonTypeSchema), z2.array(JsonTypeSchema) ])); var JsonSchemaSchema = z2.lazy(() => z2.object({ $schema: z2.string().optional().describe("JSON Schema version URI."), $id: z2.string().optional().describe("A URI for the schema."), title: z2.string().optional().describe("A short explanation about the purpose of the data described by this schema."), description: z2.string().optional().describe("A more lengthy explanation about the purpose of the data described by this schema."), type: z2.union([ z2.literal("string"), z2.literal("number"), z2.literal("integer"), z2.literal("boolean"), z2.literal("object"), z2.literal("array"), z2.literal("null"), z2.array(z2.string()) ]).optional(), properties: z2.record(z2.string(), z2.lazy(() => JsonSchemaSchema)).optional(), items: z2.union([z2.lazy(() => JsonSchemaSchema), z2.array(z2.lazy(() => JsonSchemaSchema))]).optional(), required: z2.array(z2.string()).optional(), enum: z2.array(JsonTypeSchema).optional(), const: JsonTypeSchema.optional(), default: JsonTypeSchema.optional(), format: z2.string().optional(), additionalProperties: z2.union([z2.boolean(), z2.lazy(() => JsonSchemaSchema)]).optional(), pattern: z2.string().optional(), minimum: z2.number().optional(), maximum: z2.number().optional(), minLength: z2.number().optional(), maxLength: z2.number().optional() }).catchall(z2.unknown())); var ToolSchema = z2.object({ name: z2.string().describe('Unique identifier for the tool, typically in format "manual_name.tool_name".'), description: z2.string().default("").describe("Human-readable description of what the tool does."), inputs: JsonSchemaSchema.default({}).describe("JSON Schema defining the tool's input parameters."), outputs: JsonSchemaSchema.default({}).describe("JSON Schema defining the tool's return value structure."), tags: z2.array(z2.string()).default([]).describe("List of tags for categorization and search."), average_response_size: z2.number().optional().describe("Optional hint about typical response size in bytes."), tool_call_template: CallTemplateSchema.describe("CallTemplate configuration for accessing this tool.") }).strict(); var JsonSchemaSerializer = class extends Serializer { toDict(obj) { return { ...obj }; } validateDict(obj) { return JsonSchemaSchema.parse(obj); } }; var ToolSerializer = class extends Serializer { toDict(obj) { return { name: obj.name, description: obj.description, inputs: obj.inputs, outputs: obj.outputs, tags: obj.tags, ...obj.average_response_size !== void 0 && { average_response_size: obj.average_response_size }, tool_call_template: obj.tool_call_template }; } validateDict(obj) { return ToolSchema.parse(obj); } }; // src/version.ts var _VERSION = "1.1.0"; var LIB_VERSION = _VERSION === "1.1.0" ? "1.0.0" : _VERSION; // src/data/utcp_manual.ts var UTCP_PACKAGE_VERSION = LIB_VERSION; var UtcpManualSchema = z3.object({ // Use .optional() to allow missing in input, then .default() to satisfy the interface. utcp_version: z3.string().optional().default(UTCP_PACKAGE_VERSION).describe("UTCP protocol version supported by the provider."), manual_version: z3.string().optional().default("1.0.0").describe("Version of this specific manual."), tools: z3.array(ToolSchema).describe("List of available tools with their complete configurations.") }).strict(); var UtcpManualSerializer = class extends Serializer { toDict(obj) { return { utcp_version: obj.utcp_version, manual_version: obj.manual_version, tools: obj.tools }; } validateDict(obj) { return UtcpManualSchema.parse(obj); } }; // src/interfaces/communication_protocol.ts var CommunicationProtocol = class { /** * Mapping of communication protocol types to their respective implementations. */ static communicationProtocols = {}; /** * Closes any persistent connections or resources held by the communication protocol. * This is a cleanup method that should be called when the client is shut down. */ async close() { } }; // src/client/utcp_client_config.ts import { z as z16 } from "zod"; // src/data/auth.ts import { z as z4 } from "zod"; var AuthSerializer = class _AuthSerializer extends Serializer { static serializers = {}; // No need for the whole plugin registry. Plugins just need to call this to register a new auth type static registerAuth(authType, serializer, override = false) { if (!override && _AuthSerializer.serializers[authType]) { return false; } _AuthSerializer.serializers[authType] = serializer; return true; } toDict(obj) { const serializer = _AuthSerializer.serializers[obj.auth_type]; if (!serializer) { throw new Error(`No serializer found for auth_type: ${obj.auth_type}`); } return serializer.toDict(obj); } validateDict(obj) { const serializer = _AuthSerializer.serializers[obj.auth_type]; if (!serializer) { throw new Error(`Invalid auth_type: ${obj.auth_type}`); } return serializer.validateDict(obj); } }; var AuthSchema = z4.custom((obj) => { try { const validated = new AuthSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid Auth object" }); // src/data/auth_implementations/api_key_auth.ts import { z as z5 } from "zod"; var ApiKeyAuthSerializer = class extends Serializer { toDict(obj) { return { ...obj }; } validateDict(obj) { return ApiKeyAuthSchema.parse(obj); } }; var ApiKeyAuthSchema = z5.object({ auth_type: z5.literal("api_key"), api_key: z5.string(), var_name: z5.string().default("X-Api-Key"), location: z5.enum(["header", "query", "cookie"]).default("header") }); // src/data/auth_implementations/basic_auth.ts import { z as z6 } from "zod"; var BasicAuthSerializer = class extends Serializer { toDict(obj) { return { ...obj }; } validateDict(obj) { return BasicAuthSchema.parse(obj); } }; var BasicAuthSchema = z6.object({ auth_type: z6.literal("basic"), username: z6.string().describe("The username for basic authentication. Recommended to use injected variables."), password: z6.string().describe("The password for basic authentication. Recommended to use injected variables.") }).strict(); // src/data/auth_implementations/oauth2_auth.ts import { z as z7 } from "zod"; var OAuth2AuthSerializer = class extends Serializer { toDict(obj) { return { ...obj }; } validateDict(obj) { return OAuth2AuthSchema.parse(obj); } }; var OAuth2AuthSchema = z7.object({ auth_type: z7.literal("oauth2"), token_url: z7.string().describe("The URL to fetch the OAuth2 access token from. Recommended to use injected variables."), client_id: z7.string().describe("The OAuth2 client ID. Recommended to use injected variables."), client_secret: z7.string().describe("The OAuth2 client secret. Recommended to use injected variables."), scope: z7.string().optional().describe("Optional scope parameter to limit the access token's permissions.") }).strict(); // src/implementations/in_mem_concurrent_tool_repository.ts import { z as z8 } from "zod"; var InMemConcurrentToolRepository = class { tool_repository_type = "in_memory"; _config; // Store the config to return in toDict _toolsByName = /* @__PURE__ */ new Map(); _manuals = /* @__PURE__ */ new Map(); _manualCallTemplates = /* @__PURE__ */ new Map(); _writeMutex = new AsyncMutex(); /** * The constructor must accept the config type to match the factory signature, * even if the implementation does not use it. */ constructor(config = { tool_repository_type: "in_memory" }) { this._config = config; } /** * Converts the repository instance's configuration to a dictionary. */ toDict() { return this._config; } /** * Saves a manual's call template and its associated tools in the repository. * This operation replaces any existing manual with the same name. * @param manualCallTemplate The call template associated with the manual to save. * @param manual The complete UTCP Manual object to save. */ async saveManual(manualCallTemplate, manual) { const release = await this._writeMutex.acquire(); try { const manualName = manualCallTemplate.name; const oldManual = this._manuals.get(manualName); if (oldManual) { for (const tool of oldManual.tools) { this._toolsByName.delete(tool.name); } } this._manualCallTemplates.set(manualName, { ...manualCallTemplate }); this._manuals.set(manualName, { ...manual, tools: manual.tools.map((t) => ({ ...t })) }); for (const tool of manual.tools) { this._toolsByName.set(tool.name, { ...tool }); } } finally { release(); } } /** * Removes a manual and its tools from the repository. * @param manualName The name of the manual to remove. * @returns True if the manual was removed, False otherwise. */ async removeManual(manualName) { const release = await this._writeMutex.acquire(); try { const oldManual = this._manuals.get(manualName); if (!oldManual) { return false; } for (const tool of oldManual.tools) { this._toolsByName.delete(tool.name); } this._manuals.delete(manualName); this._manualCallTemplates.delete(manualName); return true; } finally { release(); } } /** * Removes a specific tool from the repository. * Note: This also attempts to remove the tool from any associated manual. * @param toolName The full namespaced name of the tool to remove. * @returns True if the tool was removed, False otherwise. */ async removeTool(toolName) { const release = await this._writeMutex.acquire(); try { const toolRemoved = this._toolsByName.delete(toolName); if (!toolRemoved) { return false; } const manualName = toolName.split(".")[0]; if (manualName) { const manual = this._manuals.get(manualName); if (manual) { manual.tools = manual.tools.filter((t) => t.name !== toolName); } } return true; } finally { release(); } } /** * Retrieves a tool by its full namespaced name. * @param toolName The full namespaced name of the tool to retrieve. * @returns The tool if found, otherwise undefined. */ async getTool(toolName) { const tool = this._toolsByName.get(toolName); return tool ? { ...tool } : void 0; } /** * Retrieves all tools from the repository. * @returns A list of all registered tools. */ async getTools() { return Array.from(this._toolsByName.values()).map((t) => ({ ...t })); } /** * Retrieves all tools associated with a specific manual. * @param manualName The name of the manual. * @returns A list of tools associated with the manual, or undefined if the manual is not found. */ async getToolsByManual(manualName) { const manual = this._manuals.get(manualName); return manual ? manual.tools.map((t) => ({ ...t })) : void 0; } /** * Retrieves a complete UTCP Manual object by its name. * @param manualName The name of the manual to retrieve. * @returns The manual if found, otherwise undefined. */ async getManual(manualName) { const manual = this._manuals.get(manualName); return manual ? { ...manual, tools: manual.tools.map((t) => ({ ...t })) } : void 0; } /** * Retrieves all registered manuals from the repository. * @returns A list of all registered UtcpManual objects. */ async getManuals() { return Array.from(this._manuals.values()).map((m) => ({ ...m, tools: m.tools.map((t) => ({ ...t })) })); } /** * Retrieves a manual's CallTemplate by its name. * @param manualCallTemplateName The name of the manual's CallTemplate to retrieve. * @returns The CallTemplate if found, otherwise undefined. */ async getManualCallTemplate(manualCallTemplateName) { const template = this._manualCallTemplates.get(manualCallTemplateName); return template ? { ...template } : void 0; } /** * Retrieves all registered manual CallTemplates from the repository. * @returns A list of all registered CallTemplateBase objects. */ async getManualCallTemplates() { return Array.from(this._manualCallTemplates.values()).map((t) => ({ ...t })); } }; var InMemConcurrentToolRepositorySerializer = class extends Serializer { toDict(obj) { return { tool_repository_type: obj.tool_repository_type }; } validateDict(data) { try { return new InMemConcurrentToolRepository(InMemConcurrentToolRepositoryConfigSchema.parse(data)); } catch (e) { if (e instanceof z8.ZodError) { throw new Error(`Invalid configuration: ${e.message}`); } throw new Error("Unexpected error during validation"); } } }; var AsyncMutex = class { queue = []; locked = false; /** * Acquires the mutex. If the mutex is already locked, waits until it's released. * @returns A function to call to release the mutex. */ async acquire() { if (!this.locked) { this.locked = true; return this._release.bind(this); } else { return new Promise((resolve) => { this.queue.push(() => { this.locked = true; resolve(this._release.bind(this)); }); }); } } /** * Releases the mutex, allowing the next queued operation (if any) to proceed. */ _release() { this.locked = false; if (this.queue.length > 0) { const next = this.queue.shift(); if (next) { next(); } } } }; var InMemConcurrentToolRepositoryConfigSchema = z8.object({ tool_repository_type: z8.literal("in_memory") }).passthrough(); // src/interfaces/concurrent_tool_repository.ts import z9 from "zod"; var ConcurrentToolRepositoryConfigSerializer = class _ConcurrentToolRepositoryConfigSerializer extends Serializer { static implementations = {}; static default_strategy = "in_memory"; // No need for the whole plugin registry. Plugins just need to call this to register a new repository static registerRepository(type, serializer, override = false) { if (!override && _ConcurrentToolRepositoryConfigSerializer.implementations[type]) { return false; } _ConcurrentToolRepositoryConfigSerializer.implementations[type] = serializer; return true; } toDict(obj) { const serializer = _ConcurrentToolRepositoryConfigSerializer.implementations[obj.tool_repository_type]; if (!serializer) throw new Error(`No serializer for type: ${obj.tool_repository_type}`); return serializer.toDict(obj); } validateDict(data) { const serializer = _ConcurrentToolRepositoryConfigSerializer.implementations[data["tool_repository_type"]]; if (!serializer) throw new Error(`Invalid tool repository type: ${data["tool_repository_type"]}`); return serializer.validateDict(data); } }; var ConcurrentToolRepositorySchema = z9.custom((obj) => { try { const validated = new ConcurrentToolRepositoryConfigSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid ConcurrentToolRepository object" }); // src/implementations/tag_search_strategy.ts import { z as z10 } from "zod"; var TagSearchStrategy = class { tool_search_strategy_type = "tag_and_description_word_match"; descriptionWeight; tagWeight; _config; /** * Creates an instance of TagSearchStrategy. * * @param descriptionWeight The weight to apply to words found in the tool's description. * @param tagWeight The weight to apply to words found in the tool's tags. */ constructor(config) { this._config = TagSearchStrategyConfigSchema.parse(config); this.descriptionWeight = this._config.description_weight; this.tagWeight = this._config.tag_weight; } /** * Converts the search strategy instance's configuration to a dictionary. */ toDict() { return this._config; } /** * Searches for tools by matching tags and description content against a query. * * @param concurrentToolRepository The repository to search for tools. * @param query The search query string. * @param limit The maximum number of tools to return. If 0, all matched tools are returned. * @param anyOfTagsRequired Optional list of tags where one of them must be present in the tool's tags. * @returns A promise that resolves to a list of tools ordered by relevance. */ async searchTools(concurrentToolRepository, query, limit = 10, anyOfTagsRequired) { const queryLower = query.toLowerCase(); const queryWords = new Set(queryLower.match(/\w+/g) || []); let tools = await concurrentToolRepository.getTools(); if (anyOfTagsRequired && anyOfTagsRequired.length > 0) { const requiredTagsLower = new Set(anyOfTagsRequired.map((tag) => tag.toLowerCase())); tools = tools.filter( (tool) => tool.tags && tool.tags.some((tag) => requiredTagsLower.has(tag.toLowerCase())) ); } const toolScores = tools.map((tool) => { let score = 0; const toolNameLower = tool.name.toLowerCase(); const toolNameOnly = toolNameLower.includes(".") ? toolNameLower.split(".").pop() || toolNameLower : toolNameLower; if (queryLower === toolNameOnly || queryLower.includes(toolNameOnly) || toolNameOnly.includes(queryLower)) { score += this.tagWeight * 2; } const toolNameWords = new Set(toolNameOnly.match(/\w+/g) || []); for (const word of toolNameWords) { if (queryWords.has(word)) { score += this.tagWeight; } } if (tool.tags) { for (const tag of tool.tags) { const tagLower = tag.toLowerCase(); if (queryLower.includes(tagLower) || tagLower.includes(queryLower)) { score += this.tagWeight; } const tagWords = new Set(tagLower.match(/\w+/g) || []); for (const word of tagWords) { if (queryWords.has(word)) { score += this.tagWeight * 0.5; } } for (const queryWord of queryWords) { if (queryWord.length > 2) { for (const tagWord of tagWords) { if (tagWord.length > 2 && (tagWord.includes(queryWord) || queryWord.includes(tagWord))) { score += this.tagWeight * 0.3; break; } } } } } } if (tool.description) { const descriptionLower = tool.description.toLowerCase(); const descriptionWords = new Set( descriptionLower.match(/\w+/g) || [] ); for (const word of descriptionWords) { if (queryWords.has(word) && word.length > 2) { score += this.descriptionWeight; } } for (const queryWord of queryWords) { if (queryWord.length > 2) { for (const descWord of descriptionWords) { if (descWord.length > 2 && (descWord.includes(queryWord) || queryWord.includes(descWord))) { score += this.descriptionWeight * 0.5; break; } } } } } return { tool, score }; }); const sortedTools = toolScores.sort((a, b) => b.score - a.score).map((item) => item.tool); return limit > 0 ? sortedTools.slice(0, limit) : sortedTools; } }; var TagSearchStrategyConfigSerializer = class extends Serializer { toDict(obj) { return { tool_search_strategy_type: obj.tool_search_strategy_type, description_weight: obj.descriptionWeight, tag_weight: obj.tagWeight }; } validateDict(data) { try { return new TagSearchStrategy(TagSearchStrategyConfigSchema.parse(data)); } catch (e) { if (e instanceof z10.ZodError) { throw new Error(`Invalid configuration: ${e.message}`); } throw new Error("Unexpected error during validation"); } } }; var TagSearchStrategyConfigSchema = z10.object({ tool_search_strategy_type: z10.literal("tag_and_description_word_match"), description_weight: z10.number().optional().default(1), tag_weight: z10.number().optional().default(3) }).passthrough(); // src/interfaces/tool_search_strategy.ts import z11 from "zod"; var ToolSearchStrategyConfigSerializer = class _ToolSearchStrategyConfigSerializer extends Serializer { static implementations = {}; static default_strategy = "tag_and_description_word_match"; // No need for the whole plugin registry. Plugins just need to call this to register a new strategy static registerStrategy(type, serializer, override = false) { if (!override && _ToolSearchStrategyConfigSerializer.implementations[type]) { return false; } _ToolSearchStrategyConfigSerializer.implementations[type] = serializer; return true; } toDict(obj) { const serializer = _ToolSearchStrategyConfigSerializer.implementations[obj.tool_search_strategy_type]; if (!serializer) throw new Error(`No serializer for type: ${obj.tool_search_strategy_type}`); return serializer.toDict(obj); } validateDict(data) { const serializer = _ToolSearchStrategyConfigSerializer.implementations[data["tool_search_strategy_type"]]; if (!serializer) throw new Error(`Invalid tool search strategy type: ${data["tool_search_strategy_type"]}`); return serializer.validateDict(data); } }; var ToolSearchStrategySchema = z11.custom((obj) => { try { const validated = new ToolSearchStrategyConfigSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid ToolSearchStrategy object" }); // src/implementations/post_processors/filter_dict_post_processor.ts import { z as z12 } from "zod"; var FilterDictPostProcessor = class { tool_post_processor_type = "filter_dict"; excludeKeys; onlyIncludeKeys; excludeTools; onlyIncludeTools; excludeManuals; onlyIncludeManuals; _config; constructor(config) { this._config = FilterDictPostProcessorConfigSchema.parse(config); this.excludeKeys = config.exclude_keys ? new Set(config.exclude_keys) : void 0; this.onlyIncludeKeys = config.only_include_keys ? new Set(config.only_include_keys) : void 0; this.excludeTools = config.exclude_tools ? new Set(config.exclude_tools) : void 0; this.onlyIncludeTools = config.only_include_tools ? new Set(config.only_include_tools) : void 0; this.excludeManuals = config.exclude_manuals ? new Set(config.exclude_manuals) : void 0; this.onlyIncludeManuals = config.only_include_manuals ? new Set(config.only_include_manuals) : void 0; if (this.excludeKeys && this.onlyIncludeKeys) { console.warn("FilterDictPostProcessor configured with both 'exclude_keys' and 'only_include_keys'. 'exclude_keys' will be ignored."); } if (this.excludeTools && this.onlyIncludeTools) { console.warn("FilterDictPostProcessor configured with both 'exclude_tools' and 'only_include_tools'. 'exclude_tools' will be ignored."); } if (this.excludeManuals && this.onlyIncludeManuals) { console.warn("FilterDictPostProcessor configured with both 'exclude_manuals' and 'only_include_manuals'. 'exclude_manuals' will be ignored."); } } /** * Converts the post-processor instance's configuration to a dictionary. */ toDict() { return this._config; } /** * Processes the result of a tool call, applying filtering logic. * @param caller The UTCP client instance. * @param tool The Tool object that was called. * @param manualCallTemplate The CallTemplateBase object of the manual that owns the tool. * @param result The raw result returned by the tool's communication protocol. * @returns The processed result. */ postProcess(caller, tool, manualCallTemplate, result) { if (this.shouldSkipProcessing(tool, manualCallTemplate)) { return result; } if (this.onlyIncludeKeys) { return this._filterDictOnlyIncludeKeys(result); } if (this.excludeKeys) { return this._filterDictExcludeKeys(result); } return result; } /** * Determines if processing should be skipped based on tool and manual filters. * @param tool The Tool object. * @param manualCallTemplate The CallTemplateBase object of the manual. * @returns True if processing should be skipped, false otherwise. */ shouldSkipProcessing(tool, manualCallTemplate) { if (this.onlyIncludeTools && !this.onlyIncludeTools.has(tool.name)) { return true; } if (this.excludeTools && this.excludeTools.has(tool.name)) { return true; } const manualName = manualCallTemplate.name; if (manualName) { if (this.onlyIncludeManuals && !this.onlyIncludeManuals.has(manualName)) { return true; } if (this.excludeManuals && this.excludeManuals.has(manualName)) { return true; } } return false; } /** * Recursively filters a dictionary, keeping only specified keys. * @param data The data to filter. * @returns The filtered data. */ _filterDictOnlyIncludeKeys(data) { if (typeof data !== "object" || data === null) { return data; } if (Array.isArray(data)) { return data.map((item) => this._filterDictOnlyIncludeKeys(item)).filter((item) => { if (typeof item === "object" && item !== null) { if (Array.isArray(item)) return item.length > 0; return Object.keys(item).length > 0; } return true; }); } const newObject = {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { if (this.onlyIncludeKeys?.has(key)) { newObject[key] = this._filterDictOnlyIncludeKeys(data[key]); } else { const processedValue = this._filterDictOnlyIncludeKeys(data[key]); if (typeof processedValue === "object" && processedValue !== null) { if (Array.isArray(processedValue) && processedValue.length > 0) { newObject[key] = processedValue; } else if (Object.keys(processedValue).length > 0) { newObject[key] = processedValue; } } } } } return newObject; } /** * Recursively filters a dictionary, excluding specified keys. * @param data The data to filter. * @returns The filtered data. */ _filterDictExcludeKeys(data) { if (typeof data !== "object" || data === null) { return data; } if (Array.isArray(data)) { return data.map((item) => this._filterDictExcludeKeys(item)).filter((item) => { if (typeof item === "object" && item !== null) { if (Array.isArray(item)) return item.length > 0; return Object.keys(item).length > 0; } return true; }); } const newObject = {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { if (!this.excludeKeys?.has(key)) { newObject[key] = this._filterDictExcludeKeys(data[key]); } } } return newObject; } }; var FilterDictPostProcessorConfigSchema = z12.object({ tool_post_processor_type: z12.literal("filter_dict"), exclude_keys: z12.array(z12.string()).optional(), only_include_keys: z12.array(z12.string()).optional(), exclude_tools: z12.array(z12.string()).optional(), only_include_tools: z12.array(z12.string()).optional(), exclude_manuals: z12.array(z12.string()).optional(), only_include_manuals: z12.array(z12.string()).optional() }).passthrough(); var FilterDictPostProcessorSerializer = class extends Serializer { toDict(obj) { const filterDictConfig = obj.toDict(); return { tool_post_processor_type: filterDictConfig.tool_post_processor_type, exclude_keys: filterDictConfig.exclude_keys, only_include_keys: filterDictConfig.only_include_keys, exclude_tools: filterDictConfig.exclude_tools, only_include_tools: filterDictConfig.only_include_tools, exclude_manuals: filterDictConfig.exclude_manuals, only_include_manuals: filterDictConfig.only_include_manuals }; } validateDict(data) { try { return new FilterDictPostProcessor(FilterDictPostProcessorConfigSchema.parse(data)); } catch (e) { if (e instanceof z12.ZodError) { throw new Error(`Invalid configuration: ${e.message}`); } throw new Error("Unexpected error during validation"); } } }; // src/implementations/post_processors/limit_strings_post_processor.ts import { z as z13 } from "zod"; var LimitStringsPostProcessor = class { tool_post_processor_type = "limit_strings"; limit; excludeTools; onlyIncludeTools; excludeManuals; onlyIncludeManuals; _config; constructor(config) { this._config = LimitStringsPostProcessorConfigSchema.parse(config); this.limit = config.limit; this.excludeTools = config.exclude_tools ? new Set(config.exclude_tools) : void 0; this.onlyIncludeTools = config.only_include_tools ? new Set(config.only_include_tools) : void 0; this.excludeManuals = config.exclude_manuals ? new Set(config.exclude_manuals) : void 0; this.onlyIncludeManuals = config.only_include_manuals ? new Set(config.only_include_manuals) : void 0; if (this.excludeTools && this.onlyIncludeTools) { console.warn("LimitStringsPostProcessor configured with both 'exclude_tools' and 'only_include_tools'. 'exclude_tools' will be ignored."); } if (this.excludeManuals && this.onlyIncludeManuals) { console.warn("LimitStringsPostProcessor configured with both 'exclude_manuals' and 'only_include_manuals'. 'exclude_manuals' will be ignored."); } } /** * Converts the post-processor instance's configuration to a dictionary. */ toDict() { return this._config; } /** * Processes the result of a tool call, truncating string values if applicable. * @param caller The UTCP client instance. * @param tool The Tool object that was called. * @param manualCallTemplate The CallTemplateBase object of the manual that owns the tool. * @param result The raw result returned by the tool's communication protocol. * @returns The processed result. */ postProcess(caller, tool, manualCallTemplate, result) { if (this.shouldSkipProcessing(tool, manualCallTemplate)) { return result; } return this._processObject(result); } /** * Determines if processing should be skipped based on tool and manual filters. * @param tool The Tool object. * @param manualCallTemplate The CallTemplateBase object of the manual. * @returns True if processing should be skipped, false otherwise. */ shouldSkipProcessing(tool, manualCallTemplate) { if (this.onlyIncludeTools && !this.onlyIncludeTools.has(tool.name)) { return true; } if (this.excludeTools && this.excludeTools.has(tool.name)) { return true; } const manualName = manualCallTemplate.name; if (manualName) { if (this.onlyIncludeManuals && !this.onlyIncludeManuals.has(manualName)) { return true; } if (this.excludeManuals && this.excludeManuals.has(manualName)) { return true; } } return false; } /** * Recursively processes an object, truncating strings. * @param obj The object to process. * @returns The processed object. */ _processObject(obj) { if (typeof obj === "string") { return obj.length > this.limit ? obj.substring(0, this.limit) : obj; } if (Array.isArray(obj)) { return obj.map((item) => this._processObject(item)); } if (typeof obj === "object" && obj !== null) { const newObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = this._processObject(obj[key]); } } return newObj; } return obj; } }; var LimitStringsPostProcessorConfigSchema = z13.object({ tool_post_processor_type: z13.literal("limit_strings"), limit: z13.number().int().positive().default(1e4), exclude_tools: z13.array(z13.string()).optional(), only_include_tools: z13.array(z13.string()).optional(), exclude_manuals: z13.array(z13.string()).optional(), only_include_manuals: z13.array(z13.string()).optional() }).passthrough(); var LimitStringsPostProcessorSerializer = class extends Serializer { toDict(obj) { const limitStringsConfig = obj.toDict(); return { tool_post_processor_type: limitStringsConfig.tool_post_processor_type, limit: limitStringsConfig.limit, exclude_tools: limitStringsConfig.exclude_tools, only_include_tools: limitStringsConfig.only_include_tools, exclude_manuals: limitStringsConfig.exclude_manuals, only_include_manuals: limitStringsConfig.only_include_manuals }; } validateDict(data) { try { return new LimitStringsPostProcessor(LimitStringsPostProcessorConfigSchema.parse(data)); } catch (e) { if (e instanceof z13.ZodError) { throw new Error(`Invalid configuration: ${e.message}`); } throw new Error("Unexpected error during validation"); } } }; // src/interfaces/tool_post_processor.ts import z14 from "zod"; var ToolPostProcessorConfigSerializer = class _ToolPostProcessorConfigSerializer extends Serializer { static implementations = {}; // No need for the whole plugin registry. Plugins just need to call this to register a new post-processor static registerPostProcessor(type, serializer, override = false) { if (!override && _ToolPostProcessorConfigSerializer.implementations[type]) { return false; } _ToolPostProcessorConfigSerializer.implementations[type] = serializer; return true; } toDict(obj) { const serializer = _ToolPostProcessorConfigSerializer.implementations[obj.tool_post_processor_type]; if (!serializer) throw new Error(`No serializer for type: ${obj.tool_post_processor_type}`); return serializer.toDict(obj); } validateDict(data) { const serializer = _ToolPostProcessorConfigSerializer.implementations[data["tool_post_processor_type"]]; if (!serializer) throw new Error(`Invalid tool post-processor type: ${data["tool_post_processor_type"]}`); return serializer.validateDict(data); } }; var ToolPostProcessorSchema = z14.custom((obj) => { try { const validated = new ToolPostProcessorConfigSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid ToolPostProcessor object" }); // src/plugins/plugin_loader.ts var corePluginsInitialized = false; var initializing = false; setPluginInitializer(() => ensureCorePluginsInitialized()); function _registerCorePlugins() { AuthSerializer.registerAuth("api_key", new ApiKeyAuthSerializer()); AuthSerializer.registerAuth("basic", new BasicAuthSerializer()); AuthSerializer.registerAuth("oauth2", new OAuth2AuthSerializer()); ConcurrentToolRepositoryConfigSerializer.registerRepository("in_memory", new InMemConcurrentToolRepositorySerializer()); ToolSearchStrategyConfigSerializer.registerStrategy("tag_and_description_word_match", new TagSearchStrategyConfigSerializer()); ToolPostProcessorConfigSerializer.registerPostProcessor("filter_dict", new FilterDictPostProcessorSerializer()); ToolPostProcessorConfigSerializer.registerPostProcessor("limit_strings", new LimitStringsPostProcessorSerializer()); } function ensureCorePluginsInitialized() { if (!corePluginsInitialized && !initializing) { initializing = true; _registerCorePlugins(); corePluginsInitialized = true; initializing = false; } } // src/data/variable_loader.ts import { z as z15 } from "zod"; var VariableLoaderSerializer = class _VariableLoaderSerializer extends Serializer { static serializers = {}; /** * Registers a variable loader serializer for a specific type. * @param type The variable_loader_type identifier * @param serializer The serializer instance for this type * @param override Whether to override an existing registration * @returns true if registration succeeded, false if already exists and override is false */ static registerVariableLoader(type, serializer, override = false) { if (!override && _VariableLoaderSerializer.serializers[type]) { return false; } _VariableLoaderSerializer.serializers[type] = serializer; return true; } toDict(obj) { const serializer = _VariableLoaderSerializer.serializers[obj.variable_loader_type]; if (!serializer) { throw new Error(`No serializer found for variable_loader_type: ${obj.variable_loader_type}`); } return serializer.toDict(obj); } validateDict(obj) { const serializer = _VariableLoaderSerializer.serializers[obj.variable_loader_type]; if (!serializer) { throw new Error(`Invalid variable_loader_type: ${obj.variable_loader_type}`); } return serializer.validateDict(obj); } }; var VariableLoaderSchema = z15.custom((obj) => { try { const validated = new VariableLoaderSerializer().validateDict(obj); return validated; } catch (e) { return false; } }, { message: "Invalid VariableLoader object" }); // src/client/utcp_client_config.ts ensureCorePluginsInitialized(); var UtcpClientConfigSchema = z16.object({ variables: z16.record(z16.string(), z16.string()).optional().default({}), load_variables_from: z16.array(VariableLoaderSchema).nullable().optional().default(null).transform((val) => { if (val === null) return null; return val.map((item) => { if ("variable_loader_type" in item) { return new VariableLoaderSerializer().validateDict(item); } return item; }); }), tool_repository: z16.any().transform((val) => { if (typeof val === "object" && val !== null && "tool_repository_type" in val) { return new ConcurrentToolRepositoryConfigSerializer().validateDict(val); } return val; }).optional().default(new ConcurrentToolRepositoryConfigSerializer().validateDict({ tool_repository_type: ConcurrentToolRepositoryConfigSerializer.default_strategy })), tool_search_strategy: z16.any().transform((val) => { if (typeof val === "object" && val !== null && "tool_search_strategy_type" in val) { return new ToolSearchStrategyConfigSerializer().validateDict(val); } return val; }).optional().default(new ToolSearchStrategyConfigSerializer().validateDict({ tool_search_strategy_type: ToolSearchStrategyConfigSerializer.default_strategy })), post_processing: z16.array(z16.any()).transform((val) => { return val.map((item) => { if (typeof item === "object" && item !== null && "tool_post_processor_type" in item) { return new ToolPostProcessorConfigSerializer().validateDict(item); } return item; }); }).optional().default([]), manual_call_templates: z16.array(CallTemplateSchema).transform((val) => { return val.map((item) => { if (typeof item === "object" && item !== null && "call_template_type" in item) { return new CallTemplateSerializer().validateDict(item); } return item; }); }).optional().default([]) }).strict(); var UtcpClientConfigSerializer = class extends Serializer { /** * REQUIRED * Convert a UtcpClientConfig object to a dictionary. * * @param obj The UtcpClientConfig object to convert. * @returns The dictionary converted from the UtcpClientConfig object. */ toDict(obj) { return { variables: obj.variables, load_variables_from: obj.load_variables_from === null ? null : obj.load_variables_from?.map((item) => new VariableLoaderSerializer().toDict(item)), tool_repository: new ConcurrentToolRepositoryConfigSerializer().toDict(obj.tool_repository), tool_search_strategy: new ToolSearchStrategyConfigSerializer().toDict(obj.tool_search_strategy), post_processing: obj.post_processing.map((item) => new ToolPostProcessorConfigSerializer().toDict(item)), manual_call_templates: obj.manual_call_templates.map((item) => new CallTemplateSerializer().toDict(item)) }; } /** * REQUIRED * Validate a dictionary and convert it to a UtcpClientConfig object. * * @param data The dictionary to validate and convert. * @returns The UtcpClientConfig object converted from the dictionary. * @throws Error if validation fails */ validateDict(data) { try { return UtcpClientConfigSchema.parse(data); } catch (e) { throw new Error(`Invalid UtcpClientConfig: ${e.message} ${e.stack || ""}`); } } }; // src/exceptions/utcp_variable_not_found_error.ts var UtcpVariableNotFoundError = class extends Error { variableName; /** * Initializes the exception with the missing variable name. * * @param variableName The name of the variable that could not be found. */ constructor(variableName) { super( `Variable '${variableName}' referenced in call template configuration not found. Please ensure it's defined in client.config.variables, environment variables, or a configured variable loader.` ); this.variableName = variableName; this.name = "UtcpVariableNotFoundError"; } }; // src/implementations/default_variable_substitutor.ts var DefaultVariableSubstitutor = class { /** * Retrieves a variable value from configured sources, respecting namespaces. * * @param key The variable name to look up (without namespace prefix). * @param config The UTCP client configuration. * @param namespace An optional namespace to prepend to the variable name for lookup. * @returns The resolved variable value. * @throws UtcpVariableNotFoundError if the variable cannot be found. */ async _getVariable(key, config, namespace) { let effectiveKey = key; if (namespace) { effectiveKey = namespace.replace(/_/g, "!").replace(/!/g, "__") + "_" + key; } if (config.variables && effectiveKey in config.variables) { return config.variables[effectiveKey]; } if (config.load_variables_from) { for (const varLoader of config.load_variables_from) { const varValue = await varLoader.get(effectiveKey); if (varValue) { return varValue; } } } try { const envVar = process.env[effectiveKey]; if (envVar) { return envVar; } } catch (e) { } throw new UtcpVariableNotFoundError(effectiveKey); } /** * Recursively substitutes variables in the given object. * * @param obj The object (can be string, array, or object) containing potential variable references to substitute. * @param config The UTCP client configuration containing variable definitions and loaders. * @param namespace An optional namespace (e.g., manual name) to prefix variable lookups for isolation. * @returns The object with all variable references replaced by their values. * @throws UtcpVariableNotFoundError if a referenced variable cannot be resolved. */ async substitute(obj, config, namespace) { if (namespace && !/^[a-zA-Z0-9_]+$/.test(namespace)) { throw new Error(`Variable namespace '${namespace}' contains invalid characters. Only alphanumeric characters and underscores are allowed.`); } if (typeof obj === "string") { if (obj.includes("$ref")) { return obj; } let currentString = obj; const regex = /\$\{([a-zA-Z0-9_]+)\}|\$([a-zA-Z0-9_]+)/g; let match; let lastIndex = 0; const parts = []; regex.lastIndex = 0; while ((match = regex.exec(currentString)) !== null) { const varNameInTemplate = match[1] || match[2]; const fullMatch = match[0]; parts.push(currentString.substring(lastIndex, match.index)); try { const replacement = await this._getVariable(varNameInTemplate, config, namespace); parts.push(replacement); } catch (error) { if (error instanceof UtcpVariableNotFoundError) { throw new UtcpVariableNotFoundError(error.variableName); } throw error; } lastIndex = match.index + fullMatch.length; } parts.push(currentString.substring(lastIndex)); return parts.join(""); } if (Array.isArray(obj)) { return Promise.all(obj.map((item) => this.substitute(item, config, namespace))); } if (obj !== null && typeof obj === "object") { const newObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = await this.substitute(obj[key], config, namespace); } } return newObj; } return obj; } /** * Recursively finds all variable references in the given object. * * @param obj The object (can be string, array, or object) to scan for variable references. * @param namespace An optional namespace (e.g., manual name) to prefix variable lookups for isolation. * @returns A list of fully-qualified variable names found in the object. */ findRequiredVariables(obj, namespace) { if (namespace && !/^[a-zA-Z0-9_]+$/.test(namespace)) { throw new Error(`Variable namespace '${namespace}' contains invalid characters. Only alphanumeric characters and underscores are allowed.`); } const variables = []; const regex = /\$\{([a-zA-Z0-9_]+)\}|\$([a-zA-Z0-9_]+)/g; if (typeof obj === "string") { if (obj.includes("$ref")) { return []; } let match; while ((match = regex.exec(obj)) !== null) { const varNameInTemplate = match[1] || match[2]; const effectiveNamespace = namespace ? namespace.replace(/_/g, "__") : void 0; const prefixedVarName = effectiveNamespace ? `${effectiveNamespace}_${varNameInTemplate}` : varNameInTemplate; variables.push(prefixedVarName); } } else if (Array.isArray(obj)) { for (const item of obj) { variables.push(...this.findRequiredVariables(item, namespace)); } } else if (obj !== null && typeof obj === "object") { for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { variables.push(...this.findRequiredVariables(obj[key], namespace)); } } } return Array.from(new Set(variables)); } }; // src/client/utcp_client.ts var UtcpClient = class _UtcpClient { constructor(config, variableSubstitutor, root_dir = null) { this.config = config; this.variableSubstitutor = variableSubstitutor; this.root_dir = root_dir; for (const [type, protocol] of Object.entries(CommunicationProtocol.communicationProtocols)) { this._registeredCommProtocols.set(type, protocol); } this.postProcessors = config.post_processing.map((ppConfig) => { const serializer = new ToolPostProcessorConfigSerializer(); return serializer.validateDict(ppConfig); }); } _registeredCommProtocols = /* @__PURE__ */ new Map(); postProcessors; /** * REQUIRED * Create a new instance of UtcpClient. * * @param root_dir The root directory for the client to resolve relative paths from. Defaults to the current working directory. * @param config The configuration for the client. Can be a path to a configuration file, a dictionary, or UtcpClientConfig object. * @returns A new instance of UtcpClient. */ static async create(root_dir = process.cwd(), config = null) { ensureCorePluginsInitialized(); let loadedConfig; if (config === null) { loadedConfig = new UtcpClientConfigSerializer().validateDict({}); } else { loadedConfig = config; } const validatedConfig = UtcpClientConfigSchema.parse(loadedConfig); const repoSerializer = new ConcurrentToolRepositoryConfigSerializer(); const concurrentToolRepository = repoSerializer.validateDict(validatedConfig.tool_repository); co