UNPKG

@utcp/sdk

Version:
1 lines 144 kB
{"version":3,"sources":["../src/index.ts","../src/data/call_template.ts","../src/interfaces/serializer.ts","../src/data/utcp_manual.ts","../src/data/tool.ts","../src/version.ts","../src/interfaces/communication_protocol.ts","../src/client/utcp_client_config.ts","../src/data/auth.ts","../src/data/auth_implementations/api_key_auth.ts","../src/data/auth_implementations/basic_auth.ts","../src/data/auth_implementations/oauth2_auth.ts","../src/implementations/in_mem_concurrent_tool_repository.ts","../src/interfaces/concurrent_tool_repository.ts","../src/implementations/tag_search_strategy.ts","../src/interfaces/tool_search_strategy.ts","../src/implementations/post_processors/filter_dict_post_processor.ts","../src/implementations/post_processors/limit_strings_post_processor.ts","../src/interfaces/tool_post_processor.ts","../src/plugins/plugin_loader.ts","../src/data/variable_loader.ts","../src/exceptions/utcp_variable_not_found_error.ts","../src/implementations/default_variable_substitutor.ts","../src/client/utcp_client.ts"],"sourcesContent":["// packages/core/src/index.ts\n// Client\nexport * from './client/utcp_client';\nexport * from './client/utcp_client_config';\n\n// Data Models\nexport * from './data/auth';\nexport * from './data/auth_implementations/api_key_auth';\nexport * from './data/auth_implementations/basic_auth';\nexport * from './data/auth_implementations/oauth2_auth';\nexport * from './data/call_template';\nexport * from './data/tool';\nexport * from './data/utcp_manual';\nexport * from './data/register_manual_result'; \nexport * from './data/variable_loader';\n\n// Interfaces\nexport * from './interfaces';\n\n// Implementations\nexport * from './implementations/in_mem_concurrent_tool_repository';\nexport * from './implementations/tag_search_strategy';\nexport * from './implementations/default_variable_substitutor';\nexport * from './implementations/post_processors/filter_dict_post_processor';\nexport * from './implementations/post_processors/limit_strings_post_processor';\n\n// Plugins\nexport * from './plugins/plugin_loader';","// packages/core/src/data/call_template.ts\r\nimport { z } from 'zod';\r\nimport { Auth, AuthSchema } from './auth';\r\nimport { Serializer } from '../interfaces/serializer';\r\n\r\n/**\r\n * Base interface for all CallTemplates. Each protocol plugin will implement this structure.\r\n * It provides the common fields every call template must have.\r\n */\r\nexport interface CallTemplate {\r\n /**\r\n * Unique identifier for the CallTemplate/Manual. Recommended to be a human-readable name.\r\n */\r\n name?: string;\r\n\r\n /**\r\n * The transport protocol type used by this call template (e.g., 'http', 'mcp', 'text').\r\n */\r\n call_template_type: string;\r\n\r\n /**\r\n * Optional authentication configuration for the provider.\r\n */\r\n auth?: Auth;\r\n\r\n /**\r\n * Optional list of allowed communication protocol types for tools within this manual.\r\n * \r\n * Behavior:\r\n * - If undefined, null, or empty array → defaults to only allowing the manual's own call_template_type\r\n * - If set to a non-empty array → only those protocol types are allowed\r\n * \r\n * This provides secure-by-default behavior where a manual can only register/call tools\r\n * that use its own protocol unless explicitly configured otherwise.\r\n */\r\n allowed_communication_protocols?: string[];\r\n\r\n [key: string]: any;\r\n}\r\n\r\nexport class CallTemplateSerializer extends Serializer<CallTemplate> {\r\n private static serializers: Record<string, Serializer<CallTemplate>> = {};\r\n\r\n // No need for the whole plugin registry. Plugins just need to call this to register a new call template type\r\n static registerCallTemplate(\r\n callTemplateType: string,\r\n serializer: Serializer<CallTemplate>,\r\n override = false\r\n ): boolean {\r\n if (!override && CallTemplateSerializer.serializers[callTemplateType]) {\r\n return false;\r\n }\r\n CallTemplateSerializer.serializers[callTemplateType] = serializer;\r\n return true;\r\n }\r\n\r\n toDict(obj: CallTemplate): Record<string, unknown> {\r\n const serializer = CallTemplateSerializer.serializers[obj.call_template_type];\r\n if (!serializer) {\r\n throw new Error(`No serializer found for call_template_type: ${obj.call_template_type}`);\r\n }\r\n return serializer.toDict(obj);\r\n }\r\n\r\n validateDict(obj: Record<string, unknown>): CallTemplate {\r\n const serializer = CallTemplateSerializer.serializers[obj.call_template_type as string];\r\n if (!serializer) {\r\n throw new Error(`Invalid call_template_type: ${obj.call_template_type}`);\r\n }\r\n return serializer.validateDict(obj);\r\n }\r\n}\r\n\r\nexport const CallTemplateSchema = z\r\n .custom<CallTemplate>((obj) => {\r\n try {\r\n // Use the centralized serializer to validate & return the correct subtype\r\n const validated = new CallTemplateSerializer().validateDict(obj as Record<string, unknown>);\r\n return validated;\r\n } catch (e) {\r\n return false; // z.custom treats false as validation failure\r\n }\r\n }, {\r\n message: \"Invalid CallTemplate object\",\r\n });","let ensurePluginsInitialized: (() => void) | null = null;\r\n\r\nexport function setPluginInitializer(fn: () => void): void {\r\n ensurePluginsInitialized = fn;\r\n}\r\n\r\nexport abstract class Serializer<T> {\r\n constructor() {\r\n // Use lazy initialization to avoid circular dependency during module loading\r\n if (ensurePluginsInitialized) {\r\n ensurePluginsInitialized();\r\n }\r\n }\r\n\r\n abstract toDict(obj: T): { [key: string]: any };\r\n\r\n abstract validateDict(obj: { [key: string]: any }): T;\r\n\r\n copy(obj: T): T {\r\n return this.validateDict(this.toDict(obj));\r\n }\r\n}","// packages/core/src/data/utcp_manual.ts\r\nimport { z } from 'zod';\r\nimport { ToolSchema, Tool } from './tool';\r\nimport { Serializer } from '../interfaces/serializer';\r\nimport { LIB_VERSION } from '../version';\r\n\r\n/**\r\n * The default UTCP protocol version used throughout the library.\r\n * This is replaced at build time with the actual package version.\r\n * Use this constant when creating UtcpManual objects to ensure version consistency.\r\n */\r\nexport const UTCP_PACKAGE_VERSION = LIB_VERSION;\r\n\r\n/**\r\n * Interface for the standard format for tool provider responses during discovery.\r\n * Represents the complete set of tools available from a provider, along\r\n * with version information for compatibility checking.\r\n */\r\nexport interface UtcpManual {\r\n /**\r\n * The UTCP protocol version supported by the provider.\r\n */\r\n utcp_version: string;\r\n\r\n /**\r\n * The version of this specific manual/specification.\r\n */\r\n manual_version: string;\r\n\r\n /**\r\n * List of available tools with their complete configurations.\r\n */\r\n tools: Tool[];\r\n}\r\n\r\n/**\r\n * The standard format for tool provider responses during discovery.\r\n * This schema is used for runtime validation and parsing of UTCP manuals.\r\n */\r\nexport const UtcpManualSchema: z.ZodType<UtcpManual> = z.object({\r\n // Use .optional() to allow missing in input, then .default() to satisfy the interface.\r\n utcp_version: z.string().optional().default(UTCP_PACKAGE_VERSION)\r\n .describe('UTCP protocol version supported by the provider.'),\r\n manual_version: z.string().optional().default('1.0.0')\r\n .describe('Version of this specific manual.'),\r\n tools: z.array(ToolSchema)\r\n .describe('List of available tools with their complete configurations.'),\r\n}).strict() as z.ZodType<UtcpManual>;\r\n\r\n/**\r\n * Serializer for UtcpManual objects.\r\n * Handles serialization and deserialization of complete UTCP manual definitions.\r\n */\r\nexport class UtcpManualSerializer extends Serializer<UtcpManual> {\r\n toDict(obj: UtcpManual): Record<string, unknown> {\r\n return {\r\n utcp_version: obj.utcp_version,\r\n manual_version: obj.manual_version,\r\n tools: obj.tools,\r\n };\r\n }\r\n\r\n validateDict(obj: Record<string, unknown>): UtcpManual {\r\n return UtcpManualSchema.parse(obj);\r\n }\r\n}","// packages/core/src/data/tool.ts\nimport { z } from 'zod';\nimport { CallTemplate, CallTemplateSchema } from './call_template';\nimport { Serializer } from '../interfaces/serializer';\n\n// Define a recursive type for basic JSON values\n/**\n * A recursive type representing any valid JSON value: string, number, boolean, null, object, or array.\n */\ntype JsonValue = string | number | boolean | null | { [key: string]: JsonValue } | JsonValue[];\n\n// --- JSON Schema Typing and Validation ---\n\n/**\n * Zod type for basic JSON primitive or recursive structure.\n */\nexport const JsonTypeSchema: z.ZodType<JsonValue> = z.lazy(() => z.union([\n z.string(),\n z.number(),\n z.boolean(),\n z.null(),\n z.record(z.string(), JsonTypeSchema),\n z.array(JsonTypeSchema),\n]));\nexport type JsonType = z.infer<typeof JsonTypeSchema>;\n\n\n/**\n * Interface for a JSON Schema definition (based on draft-07).\n * This defines the structure for tool inputs and outputs.\n */\nexport interface JsonSchema {\n /**\n * Optional schema identifier.\n */\n $schema?: string;\n /**\n * Optional schema identifier.\n */\n $id?: string;\n /**\n * Optional schema title.\n */\n title?: string;\n /**\n * Optional schema description.\n */\n description?: string;\n /**\n * The JSON data type (e.g., 'object', 'string', 'number').\n */\n type?: 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null' | string[];\n /**\n * Defines properties if type is 'object'.\n */\n properties?: { [key: string]: JsonSchema };\n /**\n * Defines item structure if type is 'array'.\n */\n items?: JsonSchema | JsonSchema[];\n /**\n * List of required properties if type is 'object'.\n */\n required?: string[];\n /**\n * List of allowable values.\n */\n enum?: JsonType[];\n /**\n * The exact required value.\n */\n const?: JsonType;\n /**\n * A default value for the property.\n */\n default?: JsonType;\n /**\n * Optional format hint (e.g., 'date-time', 'email').\n */\n format?: string;\n /**\n * Allows or specifies schema for additional properties.\n */\n additionalProperties?: boolean | JsonSchema;\n /**\n * Regex pattern for string validation.\n */\n pattern?: string;\n /**\n * Minimum numeric value.\n */\n minimum?: number;\n /**\n * Maximum numeric value.\n */\n maximum?: number;\n /**\n * Minimum string length.\n */\n minLength?: number;\n /**\n * Maximum string length.\n */\n maxLength?: number;\n [k: string]: unknown;\n}\n\n\n/**\n * Zod schema corresponding to the JsonSchema interface.\n */\nexport const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() => z.object({\n $schema: z.string().optional().describe('JSON Schema version URI.'),\n $id: z.string().optional().describe('A URI for the schema.'),\n title: z.string().optional().describe('A short explanation about the purpose of the data described by this schema.'),\n description: z.string().optional().describe('A more lengthy explanation about the purpose of the data described by this schema.'),\n type: z.union([\n z.literal('string'), z.literal('number'), z.literal('integer'), z.literal('boolean'),\n z.literal('object'), z.literal('array'), z.literal('null'), z.array(z.string())\n ]).optional(),\n properties: z.record(z.string(), z.lazy(() => JsonSchemaSchema)).optional(),\n items: z.union([z.lazy(() => JsonSchemaSchema), z.array(z.lazy(() => JsonSchemaSchema))]).optional(),\n required: z.array(z.string()).optional(),\n enum: z.array(JsonTypeSchema).optional(),\n const: JsonTypeSchema.optional(),\n default: JsonTypeSchema.optional(),\n format: z.string().optional(),\n additionalProperties: z.union([z.boolean(), z.lazy(() => JsonSchemaSchema)]).optional(),\n pattern: z.string().optional(),\n minimum: z.number().optional(),\n maximum: z.number().optional(),\n minLength: z.number().optional(),\n maxLength: z.number().optional(),\n}).catchall(z.unknown()));\n\n\n// --- Tool Typing and Validation ---\n\n/**\n * Interface for a UTCP Tool.\n * Represents a callable tool with its metadata, input/output schemas,\n * and associated call template. Tools are the fundamental units of\n * functionality in the UTCP ecosystem.\n */\nexport interface Tool {\n /**\n * Unique identifier for the tool, typically in format \"manual_name.tool_name\".\n */\n name: string;\n /**\n * Human-readable description of what the tool does.\n */\n description: string;\n /**\n * JSON Schema defining the tool's input parameters.\n */\n inputs: JsonSchema;\n /**\n * JSON Schema defining the tool's return value structure.\n */\n outputs: JsonSchema;\n /**\n * List of tags for categorization and search.\n */\n tags: string[];\n /**\n * Optional hint about typical response size in bytes.\n */\n average_response_size?: number;\n /**\n * CallTemplate configuration for accessing this tool.\n */\n tool_call_template: CallTemplate;\n}\n\n/**\n * Zod schema corresponding to the Tool interface.\n */\nexport const ToolSchema: z.ZodType<Tool> = z.object({\n name: z.string().describe('Unique identifier for the tool, typically in format \"manual_name.tool_name\".'),\n description: z.string().default('').describe('Human-readable description of what the tool does.'),\n inputs: JsonSchemaSchema.default({}).describe('JSON Schema defining the tool\\'s input parameters.'),\n outputs: JsonSchemaSchema.default({}).describe('JSON Schema defining the tool\\'s return value structure.'),\n tags: z.array(z.string()).default([]).describe('List of tags for categorization and search.'),\n average_response_size: z.number().optional().describe('Optional hint about typical response size in bytes.'),\n tool_call_template: CallTemplateSchema.describe('CallTemplate configuration for accessing this tool.'),\n}).strict() as z.ZodType<Tool>;\n\n/**\n * Serializer for JsonSchema objects.\n * Since JsonSchema is a standard format without subtypes, this is a simple passthrough serializer.\n */\nexport class JsonSchemaSerializer extends Serializer<JsonSchema> {\n toDict(obj: JsonSchema): Record<string, unknown> {\n return { ...obj };\n }\n\n validateDict(obj: Record<string, unknown>): JsonSchema {\n return JsonSchemaSchema.parse(obj);\n }\n}\n\n/**\n * Serializer for Tool objects.\n * Handles serialization and deserialization of complete tool definitions.\r\n */\r\nexport class ToolSerializer extends Serializer<Tool> {\r\n toDict(obj: Tool): Record<string, unknown> {\r\n return {\r\n name: obj.name,\r\n description: obj.description,\r\n inputs: obj.inputs,\r\n outputs: obj.outputs,\r\n tags: obj.tags,\r\n ...(obj.average_response_size !== undefined && { average_response_size: obj.average_response_size }),\r\n tool_call_template: obj.tool_call_template,\r\n };\r\n }\r\n\r\n validateDict(obj: Record<string, unknown>): Tool {\r\n return ToolSchema.parse(obj);\r\n }\r\n}","/**\r\n * Library version - replaced during build process.\r\n * Do not modify this file manually.\r\n */\r\nconst _VERSION = \"__LIB_VERSION__\";\r\n\r\n/**\r\n * The library version. Falls back to \"1.0.0\" if the build script hasn't replaced the placeholder.\r\n */\r\nexport const LIB_VERSION = _VERSION === \"__LIB_VERSION__\" ? \"1.0.0\" : _VERSION;\r\n","// packages/core/src/interfaces/communication_protocol.ts\nimport { CallTemplate } from '../data/call_template';\nimport { RegisterManualResult } from '../data/register_manual_result';\nimport { IUtcpClient } from './utcp_client_interface';\n\n/**\n * Abstract interface for UTCP client transport implementations (Communication Protocols).\n *\n * Defines the contract that all transport implementations must follow to\n * integrate with the UTCP client. Each transport handles communication\n * with a specific type of provider (HTTP, CLI, WebSocket, etc.).\n */\nexport abstract class CommunicationProtocol {\n\n /**\n * Mapping of communication protocol types to their respective implementations.\n */\n static communicationProtocols: { [type: string]: CommunicationProtocol } = {};\n\n /**\n * Registers a manual and its tools.\n *\n * Connects to the provider and retrieves the list of tools it offers.\n * This may involve making discovery requests, parsing configuration files,\n * or initializing connections depending on the provider type.\n *\n * @param caller The UTCP client that is calling this method. (Type will be properly defined in UtcpClient).\n * @param manualCallTemplate The call template of the manual to register.\n * @returns A RegisterManualResult object containing the call template and manual.\n */\n abstract registerManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<RegisterManualResult>;\n\n /**\n * Deregisters a manual and its tools.\n *\n * Cleanly disconnects from the provider and releases any associated\n * resources such as connections, processes, or file handles.\n *\n * @param caller The UTCP client that is calling this method.\n * @param manualCallTemplate The call template of the manual to deregister.\n */\n abstract deregisterManual(caller: IUtcpClient, manualCallTemplate: CallTemplate): Promise<void>;\n\n /**\n * Executes a tool call through this transport.\n *\n * Sends a tool invocation request to the provider using the appropriate\n * protocol and returns the result. Handles serialization of arguments\n * and deserialization of responses according to the transport type.\n *\n * @param caller The UTCP client that is calling this method.\n * @param toolName Name of the tool to call (may include provider prefix).\n * @param toolArgs Dictionary of arguments to pass to the tool.\n * @param toolCallTemplate Call template of the tool to call.\n * @returns The tool's response.\n */\n abstract callTool(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): Promise<any>;\n\n /**\n * Executes a tool call through this transport streamingly.\n *\n * Sends a tool invocation request to the provider using the appropriate\n * protocol and returns an async generator for streaming results.\n *\n * @param caller The UTCP client that is calling this method.\n * @param toolName Name of the tool to call.\n * @param toolArgs Arguments to pass to the tool.\n * @param toolCallTemplate Call template of the tool to call.\n * @returns An async generator that yields chunks of the tool's response.\n */\n abstract callToolStreaming(caller: IUtcpClient, toolName: string, toolArgs: Record<string, any>, toolCallTemplate: CallTemplate): AsyncGenerator<any, void, unknown>;\n\n /**\n * Closes any persistent connections or resources held by the communication protocol.\n * This is a cleanup method that should be called when the client is shut down.\n */\n async close(): Promise<void> {}\n}","// packages/core/src/client/utcp_client_config.ts\nimport { z } from 'zod';\nimport { ensureCorePluginsInitialized } from '../plugins/plugin_loader';\nimport { CallTemplate, CallTemplateSchema, CallTemplateSerializer } from '../data/call_template';\nimport { ToolSearchStrategy, ToolSearchStrategyConfigSerializer } from '../interfaces/tool_search_strategy';\nimport { VariableLoader, VariableLoaderSchema, VariableLoaderSerializer } from '../data/variable_loader';\nimport { ConcurrentToolRepository, ConcurrentToolRepositoryConfigSerializer } from '../interfaces/concurrent_tool_repository';\nimport { ToolPostProcessor, ToolPostProcessorConfigSerializer } from '../interfaces/tool_post_processor';\nimport { Serializer } from '../interfaces/serializer';\n\n// Ensure core plugins are initialized before this module uses any serializers\nensureCorePluginsInitialized();\n\n/**\n * REQUIRED\n * Configuration model for UTCP client setup.\n *\n * Provides comprehensive configuration options for UTCP clients including\n * variable definitions, provider file locations, and variable loading\n * mechanisms. Supports hierarchical variable resolution with multiple\n * sources.\n *\n * Variable Resolution Order:\n * 1. Direct variables dictionary\n * 2. Custom variable loaders (in order)\n * 3. Environment variables\n *\n * Attributes:\n * variables: A dictionary of directly-defined\n * variables for substitution.\n * load_variables_from: A list of\n * variable loader configurations for loading variables from external\n * sources like .env files or remote services.\n * tool_repository: Configuration for the tool\n * repository, which manages the storage and retrieval of tools.\n * Defaults to an in-memory repository.\n * tool_search_strategy: Configuration for the tool\n * search strategy, defining how tools are looked up. Defaults to a\n * tag and description-based search.\n * post_processing: A list of tool post-processor\n * configurations to be applied after a tool call.\n * manual_call_templates: A list of manually defined\n * call templates for registering tools that don't have a provider.\n *\n * Example:\n * ```typescript\n * const config: UtcpClientConfig = {\n * variables: {\"MANUAL__NAME_API_KEY_NAME\": \"$REMAPPED_API_KEY\"},\n * load_variables_from: [\n * new VariableLoaderSerializer().validateDict({\"variable_loader_type\": \"dotenv\", \"env_file_path\": \".env\"})\n * ],\n * tool_repository: new ConcurrentToolRepositoryConfigSerializer().validateDict({\n * \"tool_repository_type\": \"in_memory\"\n * }),\n * tool_search_strategy: new ToolSearchStrategyConfigSerializer().validateDict({\n * \"tool_search_strategy_type\": \"tag_and_description_word_match\"\n * }),\n * post_processing: [],\n * manual_call_templates: []\n * };\n * ```\n */\nexport interface UtcpClientConfig {\n /**\n * A dictionary of directly-defined variables for substitution.\n */\n variables: Record<string, string>;\n\n /**\n * A list of variable loader configurations for loading variables from external\n * sources like .env files. Loaders are processed in order.\n */\n load_variables_from: VariableLoader[] | null;\n\n /**\n * Configuration for the tool repository.\n * Defaults to an in-memory repository.\n */\n tool_repository: ConcurrentToolRepository;\n\n /**\n * Configuration for the tool search strategy.\n * Defaults to a tag and description-based search.\n */\n tool_search_strategy: ToolSearchStrategy;\n\n /**\n * A list of tool post-processor configurations to be applied after a tool call.\n */\n post_processing: ToolPostProcessor[];\n\n /**\n * A list of manually defined call templates for registering tools at client initialization.\n */\n manual_call_templates: CallTemplate[];\n}\n\n/**\n * The main configuration schema for the UTCP client.\n */\nexport const UtcpClientConfigSchema = z.object({\n variables: z.record(z.string(), z.string()).optional().default({}),\n \n load_variables_from: z.array(VariableLoaderSchema).nullable().optional().default(null)\n .transform((val) => {\n if (val === null) return null;\n return val.map(item => {\n if ('variable_loader_type' in item) {\n return new VariableLoaderSerializer().validateDict(item as Record<string, unknown>);\n }\n return item as VariableLoader;\n });\n }),\n \n tool_repository: z.any()\n .transform((val) => {\n if (typeof val === 'object' && val !== null && 'tool_repository_type' in val) {\n return new ConcurrentToolRepositoryConfigSerializer().validateDict(val as Record<string, unknown>);\n }\n return val as ConcurrentToolRepository;\n })\n .optional()\n .default(new ConcurrentToolRepositoryConfigSerializer().validateDict({\n tool_repository_type: ConcurrentToolRepositoryConfigSerializer.default_strategy,\n })),\n\n tool_search_strategy: z.any()\n .transform((val) => {\n if (typeof val === 'object' && val !== null && 'tool_search_strategy_type' in val) {\n return new ToolSearchStrategyConfigSerializer().validateDict(val as Record<string, unknown>);\n }\n return val as ToolSearchStrategy;\n })\n .optional()\n .default(new ToolSearchStrategyConfigSerializer().validateDict({\n tool_search_strategy_type: ToolSearchStrategyConfigSerializer.default_strategy,\n })),\n\n post_processing: z.array(z.any())\n .transform((val) => {\n return val.map(item => {\n if (typeof item === 'object' && item !== null && 'tool_post_processor_type' in item) {\n return new ToolPostProcessorConfigSerializer().validateDict(item as Record<string, unknown>);\n }\n return item as ToolPostProcessor;\n });\n })\n .optional()\n .default([]),\n\n manual_call_templates: z.array(CallTemplateSchema)\n .transform((val) => {\n return val.map(item => {\n if (typeof item === 'object' && item !== null && 'call_template_type' in item) {\n return new CallTemplateSerializer().validateDict(item as Record<string, unknown>);\n }\n return item as CallTemplate;\n });\n })\n .optional()\n .default([]),\n}).strict();\n\n/**\n * REQUIRED\n * Serializer for UTCP client configurations.\n *\n * Defines the contract for serializers that convert UTCP client configurations to and from\n * dictionaries for storage or transmission. Serializers are responsible for:\n * - Converting UTCP client configurations to dictionaries for storage or transmission\n * - Converting dictionaries back to UTCP client configurations\n * - Ensuring data consistency during serialization and deserialization\n */\nexport class UtcpClientConfigSerializer extends Serializer<UtcpClientConfig> {\n /**\n * REQUIRED\n * Convert a UtcpClientConfig object to a dictionary.\n *\n * @param obj The UtcpClientConfig object to convert.\n * @returns The dictionary converted from the UtcpClientConfig object.\n */\n toDict(obj: UtcpClientConfig): Record<string, unknown> {\n return {\n variables: obj.variables,\n load_variables_from: obj.load_variables_from === null ? null : \n obj.load_variables_from?.map(item => new VariableLoaderSerializer().toDict(item)),\n tool_repository: new ConcurrentToolRepositoryConfigSerializer().toDict(obj.tool_repository),\n tool_search_strategy: new ToolSearchStrategyConfigSerializer().toDict(obj.tool_search_strategy),\n post_processing: obj.post_processing.map(item => new ToolPostProcessorConfigSerializer().toDict(item)),\n manual_call_templates: obj.manual_call_templates.map(item => new CallTemplateSerializer().toDict(item)),\n };\n }\n \n /**\n * REQUIRED\n * Validate a dictionary and convert it to a UtcpClientConfig object.\n *\n * @param data The dictionary to validate and convert.\n * @returns The UtcpClientConfig object converted from the dictionary.\n * @throws Error if validation fails\n */\n validateDict(data: Record<string, unknown>): UtcpClientConfig {\n try {\n return UtcpClientConfigSchema.parse(data) as UtcpClientConfig;\n } catch (e: any) {\n throw new Error(`Invalid UtcpClientConfig: ${e.message}\\n${e.stack || ''}`);\n }\n }\n}","// packages/core/src/data/auth.ts\r\nimport { z } from 'zod';\r\nimport { Serializer } from '../interfaces/serializer';\r\n\r\nexport interface Auth {\r\n /** Authentication type identifier */\r\n auth_type: string;\r\n}\r\n\r\nexport class AuthSerializer extends Serializer<Auth> {\r\n private static serializers: Record<string, Serializer<Auth>> = {};\r\n\r\n // No need for the whole plugin registry. Plugins just need to call this to register a new auth type\r\n static registerAuth(\r\n authType: string,\r\n serializer: Serializer<Auth>,\r\n override = false\r\n ): boolean {\r\n if (!override && AuthSerializer.serializers[authType]) {\r\n return false;\r\n }\r\n AuthSerializer.serializers[authType] = serializer;\r\n return true;\r\n }\r\n\r\n toDict(obj: Auth): Record<string, unknown> {\r\n const serializer = AuthSerializer.serializers[obj.auth_type];\r\n if (!serializer) {\r\n throw new Error(`No serializer found for auth_type: ${obj.auth_type}`);\r\n }\r\n return serializer.toDict(obj);\r\n }\r\n\r\n validateDict(obj: Record<string, unknown>): Auth {\r\n const serializer = AuthSerializer.serializers[obj.auth_type as string];\r\n if (!serializer) {\r\n throw new Error(`Invalid auth_type: ${obj.auth_type}`);\r\n }\r\n return serializer.validateDict(obj);\r\n }\r\n}\r\n\r\nexport const AuthSchema = z\r\n .custom<Auth>((obj) => {\r\n try {\r\n // Use the centralized serializer to validate & return the correct subtype\r\n const validated = new AuthSerializer().validateDict(obj as Record<string, unknown>);\r\n return validated;\r\n } catch (e) {\r\n return false; // z.custom treats false as validation failure\r\n }\r\n }, {\r\n message: \"Invalid Auth object\",\r\n });","import { Auth } from \"../auth\";\r\nimport { Serializer } from \"../../interfaces/serializer\";\r\nimport { z } from \"zod\";\r\n\r\nexport interface ApiKeyAuth extends Auth {\r\n auth_type: \"api_key\";\r\n api_key: string;\r\n var_name: string; // header, query param, etc.\r\n location: \"header\" | \"query\" | \"cookie\";\r\n}\r\n\r\nexport class ApiKeyAuthSerializer extends Serializer<ApiKeyAuth> {\r\n toDict(obj: ApiKeyAuth): { [key: string]: any } {\r\n return { ...obj };\r\n }\r\n\r\n validateDict(obj: { [key: string]: any }): ApiKeyAuth {\r\n return ApiKeyAuthSchema.parse(obj);\r\n }\r\n}\r\n \r\nconst ApiKeyAuthSchema = z.object({\r\n auth_type: z.literal(\"api_key\"),\r\n api_key: z.string(),\r\n var_name: z.string().default(\"X-Api-Key\"),\r\n location: z.enum([\"header\", \"query\", \"cookie\"]).default(\"header\"),\r\n});","// packages/core/src/data/auth.ts\r\nimport { z } from 'zod';\r\nimport { Serializer } from '../../interfaces/serializer';\r\nimport { Auth } from '../auth';\r\n\r\n/**\r\n * Interface for Basic authentication details.\r\n */\r\nexport interface BasicAuth extends Auth {\r\n auth_type: 'basic';\r\n username: string;\r\n password: string;\r\n}\r\n\r\nexport class BasicAuthSerializer extends Serializer<BasicAuth> {\r\n toDict(obj: BasicAuth): { [key: string]: any } {\r\n // Just spread the object since it's already validated\r\n return { ...obj };\r\n }\r\n\r\n validateDict(obj: { [key: string]: any }): BasicAuth {\r\n return BasicAuthSchema.parse(obj);\r\n }\r\n}\r\n\r\n/**\r\n * Authentication using HTTP Basic Authentication.\r\n * Credentials typically contain variable placeholders for substitution.\r\n */\r\nconst BasicAuthSchema: z.ZodType<BasicAuth> = z.object({\r\n auth_type: z.literal('basic'),\r\n username: z.string().describe('The username for basic authentication. Recommended to use injected variables.'),\r\n password: z.string().describe('The password for basic authentication. Recommended to use injected variables.'),\r\n}).strict() as z.ZodType<BasicAuth>;\r\n","// packages/core/src/data/auth.ts\r\nimport { z } from 'zod';\r\nimport { Serializer } from '../../interfaces/serializer';\r\nimport { Auth } from '../auth';\r\n\r\n/**\r\n * Interface for OAuth2 authentication details (Client Credentials Flow).\r\n */\r\nexport interface OAuth2Auth extends Auth {\r\n auth_type: 'oauth2';\r\n token_url: string;\r\n client_id: string;\r\n client_secret: string;\r\n scope?: string;\r\n}\r\n\r\nexport class OAuth2AuthSerializer extends Serializer<OAuth2Auth> {\r\n toDict(obj: OAuth2Auth): { [key: string]: any } {\r\n // Just spread the object since it's already validated\r\n return { ...obj };\r\n }\r\n\r\n validateDict(obj: { [key: string]: any }): OAuth2Auth {\r\n return OAuth2AuthSchema.parse(obj);\r\n }\r\n}\r\n\r\n/**\r\n * Authentication using OAuth2 client credentials flow.\r\n * The client automatically handles token acquisition and refresh.\r\n */\r\nconst OAuth2AuthSchema: z.ZodType<OAuth2Auth> = z.object({\r\n auth_type: z.literal('oauth2'),\r\n token_url: z.string().describe('The URL to fetch the OAuth2 access token from. Recommended to use injected variables.'),\r\n client_id: z.string().describe('The OAuth2 client ID. Recommended to use injected variables.'),\r\n client_secret: z.string().describe('The OAuth2 client secret. Recommended to use injected variables.'),\r\n scope: z.string().optional().describe('Optional scope parameter to limit the access token\\'s permissions.'),\r\n}).strict() as z.ZodType<OAuth2Auth>;\r\n","// packages/core/src/implementations/in_mem_concurrent_tool_repository.ts\r\nimport { CallTemplate } from '../data/call_template';\r\nimport { Tool } from '../data/tool';\r\nimport { UtcpManual } from '../data/utcp_manual';\r\nimport { ConcurrentToolRepository } from '../interfaces/concurrent_tool_repository';\r\nimport { Serializer } from '../interfaces/serializer';\r\nimport { z } from 'zod';\r\n\r\n/**\r\n * An in-memory implementation of the ConcurrentToolRepository.\r\n * Stores tools, manuals, and manual call templates in local maps.\r\n * Uses an `AsyncMutex` to serialize write operations, ensuring data consistency\r\n * across concurrent asynchronous calls, and returns deep copies to prevent\r\n * external modification of internal state.\r\n */\r\nexport class InMemConcurrentToolRepository implements ConcurrentToolRepository {\r\n public readonly tool_repository_type: string = \"in_memory\";\r\n private _config: InMemConcurrentToolRepositoryConfig; // Store the config to return in toDict\r\n\r\n private _toolsByName: Map<string, Tool> = new Map();\r\n private _manuals: Map<string, UtcpManual> = new Map();\r\n private _manualCallTemplates: Map<string, CallTemplate> = new Map();\r\n private _writeMutex: AsyncMutex = new AsyncMutex();\r\n\r\n /**\r\n * The constructor must accept the config type to match the factory signature, \r\n * even if the implementation does not use it.\r\n */\r\n constructor(config: InMemConcurrentToolRepositoryConfig = { tool_repository_type: 'in_memory' }) {\r\n this._config = config;\r\n // Intentionally left empty as all state is initialized via field initializers.\r\n }\r\n\r\n /**\r\n * Converts the repository instance's configuration to a dictionary.\r\n */\r\n public toDict(): InMemConcurrentToolRepositoryConfig {\r\n return this._config;\r\n }\r\n\r\n /**\r\n * Saves a manual's call template and its associated tools in the repository.\r\n * This operation replaces any existing manual with the same name.\r\n * @param manualCallTemplate The call template associated with the manual to save.\r\n * @param manual The complete UTCP Manual object to save.\r\n */\r\n public async saveManual(manualCallTemplate: CallTemplate, manual: UtcpManual): Promise<void> {\r\n const release = await this._writeMutex.acquire();\r\n try {\r\n const manualName = manualCallTemplate.name!;\r\n const oldManual = this._manuals.get(manualName);\r\n if (oldManual) {\r\n for (const tool of oldManual.tools) {\r\n this._toolsByName.delete(tool.name);\r\n }\r\n }\r\n this._manualCallTemplates.set(manualName, { ...manualCallTemplate });\r\n this._manuals.set(manualName, { ...manual, tools: manual.tools.map(t => ({ ...t })) });\r\n for (const tool of manual.tools) {\r\n this._toolsByName.set(tool.name, { ...tool });\r\n }\r\n } finally {\r\n release();\r\n }\r\n }\r\n\r\n /**\r\n * Removes a manual and its tools from the repository.\r\n * @param manualName The name of the manual to remove.\r\n * @returns True if the manual was removed, False otherwise.\r\n */\r\n public async removeManual(manualName: string): Promise<boolean> {\r\n const release = await this._writeMutex.acquire();\r\n try {\r\n const oldManual = this._manuals.get(manualName);\r\n if (!oldManual) {\r\n return false;\r\n }\r\n\r\n for (const tool of oldManual.tools) {\r\n this._toolsByName.delete(tool.name);\r\n }\r\n\r\n this._manuals.delete(manualName);\r\n this._manualCallTemplates.delete(manualName);\r\n return true;\r\n } finally {\r\n release();\r\n }\r\n }\r\n\r\n /**\r\n * Removes a specific tool from the repository.\r\n * Note: This also attempts to remove the tool from any associated manual.\r\n * @param toolName The full namespaced name of the tool to remove.\r\n * @returns True if the tool was removed, False otherwise.\r\n */\r\n public async removeTool(toolName: string): Promise<boolean> {\r\n const release = await this._writeMutex.acquire();\r\n try {\r\n const toolRemoved = this._toolsByName.delete(toolName);\r\n if (!toolRemoved) {\r\n return false;\r\n }\r\n\r\n const manualName = toolName.split('.')[0];\r\n if (manualName) {\r\n const manual = this._manuals.get(manualName);\r\n if (manual) {\r\n manual.tools = manual.tools.filter(t => t.name !== toolName);\r\n }\r\n }\r\n return true;\r\n } finally {\r\n release();\r\n }\r\n }\r\n\r\n /**\r\n * Retrieves a tool by its full namespaced name.\r\n * @param toolName The full namespaced name of the tool to retrieve.\r\n * @returns The tool if found, otherwise undefined.\r\n */\r\n public async getTool(toolName: string): Promise<Tool | undefined> {\r\n const tool = this._toolsByName.get(toolName);\r\n return tool ? { ...tool } : undefined;\r\n }\r\n\r\n /**\r\n * Retrieves all tools from the repository.\r\n * @returns A list of all registered tools.\r\n */\r\n public async getTools(): Promise<Tool[]> {\r\n return Array.from(this._toolsByName.values()).map(t => ({ ...t }));\r\n }\r\n\r\n /**\r\n * Retrieves all tools associated with a specific manual.\r\n * @param manualName The name of the manual.\r\n * @returns A list of tools associated with the manual, or undefined if the manual is not found.\r\n */\r\n public async getToolsByManual(manualName: string): Promise<Tool[] | undefined> {\r\n const manual = this._manuals.get(manualName);\r\n return manual ? manual.tools.map(t => ({ ...t })) : undefined;\r\n }\r\n\r\n /**\r\n * Retrieves a complete UTCP Manual object by its name.\r\n * @param manualName The name of the manual to retrieve.\r\n * @returns The manual if found, otherwise undefined.\r\n */\r\n public async getManual(manualName: string): Promise<UtcpManual | undefined> {\r\n const manual = this._manuals.get(manualName);\r\n return manual ? { ...manual, tools: manual.tools.map(t => ({ ...t })) } : undefined;\r\n }\r\n\r\n /**\r\n * Retrieves all registered manuals from the repository.\r\n * @returns A list of all registered UtcpManual objects.\r\n */\r\n public async getManuals(): Promise<UtcpManual[]> {\r\n return Array.from(this._manuals.values()).map(m => ({ ...m, tools: m.tools.map(t => ({ ...t })) }));\r\n }\r\n\r\n /**\r\n * Retrieves a manual's CallTemplate by its name.\r\n * @param manualCallTemplateName The name of the manual's CallTemplate to retrieve.\r\n * @returns The CallTemplate if found, otherwise undefined.\r\n */\r\n public async getManualCallTemplate(manualCallTemplateName: string): Promise<CallTemplate | undefined> {\r\n const template = this._manualCallTemplates.get(manualCallTemplateName);\r\n return template ? { ...template } : undefined;\r\n }\r\n\r\n /**\r\n * Retrieves all registered manual CallTemplates from the repository.\r\n * @returns A list of all registered CallTemplateBase objects.\r\n */\r\n public async getManualCallTemplates(): Promise<CallTemplate[]> {\r\n return Array.from(this._manualCallTemplates.values()).map(t => ({ ...t }));\r\n }\r\n}\r\n\r\nexport class InMemConcurrentToolRepositorySerializer extends Serializer<InMemConcurrentToolRepository> {\r\n toDict(obj: InMemConcurrentToolRepository): { [key: string]: any } {\r\n return {\r\n tool_repository_type: obj.tool_repository_type\r\n }\r\n }\r\n\r\n validateDict(data: { [key: string]: any }): InMemConcurrentToolRepository {\r\n try {\r\n return new InMemConcurrentToolRepository(InMemConcurrentToolRepositoryConfigSchema.parse(data));\r\n } catch (e) {\r\n if (e instanceof z.ZodError) {\r\n throw new Error(`Invalid configuration: ${e.message}`);\r\n }\r\n throw new Error(\"Unexpected error during validation\");\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * A simple asynchronous mutex to serialize write access to shared resources.\r\n * In a single-threaded JavaScript environment, this primarily ensures that\r\n * compound asynchronous operations on shared state do not interleave incorrectly.\r\n */\r\nclass AsyncMutex {\r\n private queue: (() => void)[] = [];\r\n private locked: boolean = false;\r\n\r\n /**\r\n * Acquires the mutex. If the mutex is already locked, waits until it's released.\r\n * @returns A function to call to release the mutex.\r\n */\r\n async acquire(): Promise<() => void> {\r\n if (!this.locked) {\r\n this.locked = true;\r\n return this._release.bind(this);\r\n } else {\r\n return new Promise<() => void>(resolve => {\r\n this.queue.push(() => {\r\n this.locked = true;\r\n resolve(this._release.bind(this));\r\n });\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Releases the mutex, allowing the next queued operation (if any) to proceed.\r\n */\r\n private _release(): void {\r\n this.locked = false;\r\n if (this.queue.length > 0) {\r\n const next = this.queue.shift();\r\n if (next) {\r\n next();\r\n }\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Schema for the InMemConcurrentToolRepository configuration.\r\n */\r\nconst InMemConcurrentToolRepositoryConfigSchema = z.object({\r\n tool_repository_type: z.literal('in_memory'),\r\n}).passthrough();\r\n\r\ntype InMemConcurrentToolRepositoryConfig = z.infer<typeof InMemConcurrentToolRepositoryConfigSchema>;\r\n","// packages/core/src/interfaces/concurrent_tool_repository.ts\nimport { CallTemplate } from '../data/call_template';\nimport { Tool } from '../data/tool';\nimport { UtcpManual } from '../data/utcp_manual';\nimport { Serializer } from './serializer';\nimport z from 'zod';\n\n\n/**\n * Defines the contract for tool repositories that store and manage UTCP tools\n * and their associated call templates.\n *\n * Repositories are responsible for:\n * - Persisting call template configurations and their associated tools.\n * - Providing efficient lookup and retrieval operations.\n * - Managing relationships between call templates and tools.\n * - Ensuring data consistency across concurrent asynchronous calls.\n */\nexport interface ConcurrentToolRepository {\n /**\n * A string identifying the type of this tool repository (e.g., 'in_memory', 'database').\n * This is used for configuration and plugin lookup.\n */\n tool_repository_type: string;\n\n /**\n * Saves a manual's call template and its associated tools in the repository.\n * This operation replaces any existing manual with the same name.\n *\n * @param manualCallTemplate The call template associated with the manual to save.\n * @param manual The complete UTCP Manual object to save.\n * @returns A Promise that resolves when the operation is complete.\n */\n saveManual(manualCallTemplate: CallTemplate, manual: UtcpManual): Promise<void>;\n\n /**\n * Removes a manual and its tools from the repository.\n *\n * @param manualName The name of the manual (which corresponds to the CallTemplate name) to remove.\n * @returns A Promise resolving to true if the manual was removed, False otherwise.\n */\n removeManual(manualName: string): Promise<boolean>;\n\n /**\n * Removes a specific tool from the repository.\n *\n * @param toolName The full namespaced name of the tool to remove (e.g., \"my_manual.my_tool\").\n * @returns A Promise resolving to true if the tool was removed, False otherwise.\n */\n removeTool(toolName: string): Promise<boolean>;\n\n /**\n * Retrieves a tool by its full namespaced name.\n *\n * @param toolName The full namespaced name of the tool to retrieve.\n * @returns A Promise resolving to the tool if found, otherwise undefined.\n */\n getTool(toolName: string): Promise<Tool | undefined>;\n\n /**\n * Retrieves all tools from the repository.\n *\n * @returns A Promise resolving to a list of all registered tools.\n */\n getTools(): Promise<Tool[]>;\n\n /**\n * Retrieves all tools associated with a specific manual.\n *\n * @param manualName The name of the manual.\n * @returns A Promise resolving to a list of tools associated with the manual, or undefined if the manual is not found.\n */\n getToolsByManual(manualName: string): Promise<Tool[] | undefined>;\n\n /**\n * Retrieves a complete UTCP Manual object by its name.\n *\n * @param manualName The name of the manual to retrieve.\n * @returns A Promise resolving to the manual if found, otherwise undefined.\n */\n getManual(manualName: string): Promise<UtcpManual | undefined>;\n\n /**\n * Retrieves all registered manuals from the repository.\n *\n * @returns A Promise resolving to a list of all registered UtcpManual objects.\n */\n getManuals(): Promise<UtcpManual[]>;\n\n /**\n * Retrieves a manual's CallTemplate by its name.\n *\n * @param manualCallTemplateName The name of the manual's CallTemplate to retrieve.\n * @returns A Promise resolving to the CallTemplate if found, otherwise undefined.\n */\n getManualCallTemplate(manualCallTemplateName: string): Promise<CallTemplate | undefined>;\n\n /**\n * Retrieves all registered manual CallTemplates from the repository.\n *\n * @returns A Promise resolving to a list of all registered CallTemplateBase objects.\n */\n getManualCallTemplates(): Promise<CallTemplate[]>;\n}\n\nexport class ConcurrentToolRepositoryConfigSerializer extends Serializer<ConcurrentToolRepository> {\n private static implementations: Record<string, Serializer<ConcurrentToolRepository>> = {};\n static default_strategy = \"in_memory\";\n\n // No need for the whole plugin registry. Plugins just need to call this to register a new repository\n static registerRepository(type: string, serializer: Serializer<ConcurrentToolRepository>, override = false): boolean {\n if (!override && ConcurrentToolRepositoryConfigSerializer.implementations[type]) {\n return false;\n }\n ConcurrentToolRepositoryConfigSerializer.implementations[type] = serializer;\n return true;\n }\n\n toDict(obj: ConcurrentToolRepository): Record<string, unknown> {\n const serializer = ConcurrentToolRepositoryConfigSerializer.implementations[obj.tool_repository_type];\n if (!serializer) throw new Error(`No serializer for type: ${obj.tool_repository_type}`);\n return serializer.toDict(obj);\n }\n\n validateDict(data: Record<string, unknown>): ConcurrentToolRepository {\n const serializer = ConcurrentToolRepositoryConfigSerializer.implementations[data[\"tool_repository_type\"] as string];\n if (!serializer) throw new Error(`Invalid tool repository type: ${data[\"tool_repository_type\"]}`);\n return serializer.validateDict(data);\n }\n}\n\nexport const ConcurrentToolRepositorySchema = z\n .custom<ConcurrentToolRepository>((obj) => {\n try {\n // Use the centralized serializer to validate & return the correct subtype\n const validated = new ConcurrentToolRepositoryConfigSerializer().validateDict(obj as Record<string, unknown>);\n return validated;\n } catch (e) {\n return false; // z.custom treats false as validation failure\n }\n }, {\n message: \"Invalid ConcurrentToolRepository object\",\n });","// packages/core/src/implementations/tag_search_strategy.ts\r\nimport { Tool } from '../data/tool';\r\nimport { ConcurrentToolRepository } from '../interfaces/concurrent_tool_repository';\r\nimport { ToolSearchStrategy } from '../interfaces/tool_search_strategy';\r\nimport { z } from 'zod';\r\nimport { Serializer } from '../interfaces/serializer';\r\n\r\n/**\r\n * Implements a tool search strategy based on tag and description matching.\r\n * Tools are scored based on the occurrence of query words in their tags and description.\r\n */\r\nexport class TagSearchStrategy implements ToolSearchStrategy {\r\n public readonly tool_search_strategy_type: 'tag_and_description_word_match' = 'tag_and_description_word_match';\r\n public readonly descriptionWeight: number;\r\n public readonly tagWeight: number;\r\n private readonly _config: TagSearchStrategyConfig; \r\n\r\n /**\r\n * Creates an instance of TagSearchStrategy.\r\n *\r\n * @param descriptionWeight The weight to apply to words found in the tool's description.\r\n * @param tagWeight The weight to apply to words found in the tool's tags.\r\n */\r\n constructor(config: TagSearchStrategyConfig) {\r\n this._config = TagSearchStrategyConfigSchema.parse(config);\r\n this.descriptionWeight = this._config.description_weight;\r\n this.tagWeight = this._config.tag_weight;\r\n }\r\n\r\n /**\r\n * Converts the search strategy instance's configuration to a dictionary.\r\n */\r\n public toDict(): TagSearchStrategyConfig {\r\n return this._config;\r\n }\r\n\r\n /**\r\n * Searches for tools by matching tags and description content