UNPKG

@utcp/sdk

Version:

Universal Tool Calling Protocol (UTCP) client library for TypeScript

248 lines 11.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.UtcpClient = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); const dotenv_1 = require("dotenv"); const provider_1 = require("../shared/provider"); const in_mem_tool_repository_1 = require("./tool-repositories/in-mem-tool-repository"); const tag_search_1 = require("./tool-search-strategies/tag-search"); const utcp_client_config_1 = require("./utcp-client-config"); const http_transport_1 = require("./transport-interfaces/http-transport"); const text_transport_1 = require("./transport-interfaces/text-transport"); /** * The main client for interacting with the Universal Tool Calling Protocol (UTCP). */ class UtcpClient { constructor(config, toolRepository, searchStrategy) { this.config = config; this.toolRepository = toolRepository; this.searchStrategy = searchStrategy; this.transports = { 'http': new http_transport_1.HttpClientTransport(), 'text': new text_transport_1.TextTransport(), }; } /** * Creates and initializes a new instance of the UtcpClient. * * @param config The configuration for the client. Can be a partial config object or a complete UtcpClientConfig. * @param toolRepository The tool repository to use. Defaults to InMemToolRepository. * @param searchStrategy The tool search strategy to use. Defaults to TagSearchStrategy. * @returns A promise that resolves to a new, initialized instance of UtcpClient. */ static async create(config = {}, toolRepository = new in_mem_tool_repository_1.InMemToolRepository(), searchStrategy) { let validatedConfig; if ('toJSON' in config && typeof config.toJSON === 'function') { // It's already a UtcpClientConfig instance validatedConfig = config; } else { validatedConfig = utcp_client_config_1.UtcpClientConfigSchema.parse(config); } const finalSearchStrategy = searchStrategy ?? new tag_search_1.TagSearchStrategy(toolRepository); const client = new UtcpClient(validatedConfig, toolRepository, finalSearchStrategy); // If a providers file is used, configure TextTransport to resolve relative paths from its directory if (client.config.providers_file_path) { const providersDir = path.dirname(path.resolve(client.config.providers_file_path)); client.transports['text'] = new text_transport_1.TextTransport(providersDir); } if (client.config.variables) { const configWithoutVars = { ...client.config }; configWithoutVars.variables = {}; client.config.variables = await client.replaceVarsInObj(client.config.variables); } await client.loadVariables(); await client.loadProvidersFromFile(); return client; } async register_tool_provider(manual_provider) { const processedProvider = await this.substituteProviderVariables(manual_provider); processedProvider.name = processedProvider.name.replace('.', '_'); if (!this.transports[processedProvider.provider_type]) { throw new Error(`Provider type not supported: ${processedProvider.provider_type}`); } const tools = await this.transports[processedProvider.provider_type].register_tool_provider(processedProvider); // Ensure tool names are prefixed with the provider name for (const tool of tools) { if (!tool.name.startsWith(`${processedProvider.name}.`)) { tool.name = `${processedProvider.name}.${tool.name}`; } } await this.toolRepository.saveProviderWithTools(processedProvider, tools); return tools; } async deregister_tool_provider(provider_name) { const provider = await this.toolRepository.getProvider(provider_name); if (!provider) { throw new Error(`Provider not found: ${provider_name}`); } if (this.transports[provider.provider_type]) { await this.transports[provider.provider_type].deregister_tool_provider(provider); } await this.toolRepository.removeProvider(provider_name); } async call_tool(tool_name, args) { const provider_name = tool_name.split('.')[0]; if (!provider_name) { throw new Error('Invalid tool name format. Expected provider_name.tool_name'); } const provider = await this.toolRepository.getProvider(provider_name); if (!provider) { throw new Error(`Provider not found: ${provider_name}`); } const tools = await this.toolRepository.getToolsByProvider(provider_name); const tool = tools?.find(t => t.name === tool_name); if (!tool) { throw new Error(`Tool not found: ${tool_name}`); } const processed_provider = await this.substituteProviderVariables(tool.tool_provider); if (!this.transports[processed_provider.provider_type]) { throw new Error(`Transport for provider type ${processed_provider.provider_type} not found.`); } return this.transports[processed_provider.provider_type].call_tool(tool_name, args, processed_provider); } search_tools(query, limit = 10) { return this.searchStrategy.searchTools(query, limit); } /** * Load providers from the file specified in the configuration. * * @returns A promise that resolves to a list of registered Provider objects. * @throws FileNotFoundError if the providers file doesn't exist. * @throws Error if the providers file contains invalid JSON. * @throws UtcpVariableNotFoundError if a variable referenced in the provider configuration is not found. */ async loadProvidersFromFile() { if (!this.config.providers_file_path) { return; } const filePath = path.resolve(this.config.providers_file_path); try { const fileContent = await fs.readFile(filePath, 'utf-8'); const providersData = JSON.parse(fileContent); if (!Array.isArray(providersData)) { throw new Error('Providers file must contain a JSON array at the root level.'); } for (const providerData of providersData) { try { const provider = provider_1.ProviderUnionSchema.parse(providerData); await this.register_tool_provider(provider); console.log(`Successfully registered provider '${provider.name}'`); } catch (error) { const providerName = providerData?.name || 'unknown'; console.error(`Error registering provider '${providerName}':`, error); } } } catch (error) { console.error(`Failed to load providers from ${filePath}:`, error); throw error; } } async loadVariables() { if (!this.config.load_variables_from) { return; } for (const varConfig of this.config.load_variables_from) { if (varConfig.type === 'dotenv') { try { const envContent = await fs.readFile(varConfig.env_file_path, 'utf-8'); const envVars = (0, dotenv_1.parse)(envContent); this.config.variables = { ...envVars, ...this.config.variables }; } catch (e) { console.warn(`Could not load .env file from ${varConfig.env_file_path}`); } } } } async substituteProviderVariables(provider) { const providerDict = { ...provider }; const processedDict = await this.replaceVarsInObj(providerDict); return provider_1.ProviderUnionSchema.parse(processedDict); } async getVariable(key) { // 1. Check config.variables if (this.config.variables && key in this.config.variables) { return this.config.variables[key]; } // 2. Check process.env if (process.env[key]) { return process.env[key]; } throw new utcp_client_config_1.UtcpVariableNotFoundError(key); } async replaceVarsInObj(obj) { if (typeof obj === 'string') { // Support both ${VAR} and $VAR formats like Python version const regex = /\$\{([^}]+)\}|\$(\w+)/g; let result = obj; let match; while ((match = regex.exec(obj)) !== null) { // The first group that is not undefined is the one that matched const varName = match[1] || match[2]; if (!varName) continue; try { const varValue = await this.getVariable(varName); result = result.replace(match[0], varValue); } catch (error) { // Continue with other variables even if one fails console.warn(`Variable not found: ${varName}`); } } return result; } if (Array.isArray(obj)) { return Promise.all(obj.map(item => this.replaceVarsInObj(item))); } if (obj !== null && typeof obj === 'object') { const newObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = await this.replaceVarsInObj(obj[key]); } } return newObj; } return obj; } } exports.UtcpClient = UtcpClient; //# sourceMappingURL=utcp-client.js.map