UNPKG

@picahq/ai

Version:

Pica AI SDK for Vercel AI SDK integration

623 lines (620 loc) 32.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Pica = void 0; const axios_1 = __importDefault(require("axios")); const zod_1 = require("zod"); const ai_1 = require("ai"); const form_data_1 = __importDefault(require("form-data")); const defaultSystem_1 = require("./prompts/defaultSystem"); const defaultSystemWithAuthkit_1 = require("./prompts/defaultSystemWithAuthkit"); const knowledgeAgentSystem_1 = require("./prompts/knowledgeAgentSystem"); const knowledgeAgentWithAuthkitSystem_1 = require("./prompts/knowledgeAgentWithAuthkitSystem"); const utils_1 = require("./utils"); class Pica { constructor(secret, options) { this.baseUrl = "https://api.picaos.com"; this.secret = secret; this.connections = []; this.connectionDefinitions = []; this.systemPromptValue = "Loading connections..."; this.identity = options === null || options === void 0 ? void 0 : options.identity; this.identityType = options === null || options === void 0 ? void 0 : options.identityType; this.useAuthkit = (options === null || options === void 0 ? void 0 : options.authkit) || false; this.useKnowledgeAgent = (options === null || options === void 0 ? void 0 : options.knowledgeAgent) || false; this.knowledgeAgentConfig = (options === null || options === void 0 ? void 0 : options.knowledgeAgentConfig) || { includeEnvironmentVariables: true }; this.options = options; if (options === null || options === void 0 ? void 0 : options.serverUrl) { this.baseUrl = options.serverUrl; } this.getConnectionUrl = `${this.baseUrl}/v1/vault/connections`; this.availableActionsUrl = `${this.baseUrl}/v1/knowledge`; this.getConnectionDefinitionsUrl = `${this.baseUrl}/v1/available-connectors`; this.initialized = this.initialize() .then(() => { var _a, _b, _c; let filteredConnections = this.connections.filter((conn) => conn.active); if (!((_a = options === null || options === void 0 ? void 0 : options.connectors) === null || _a === void 0 ? void 0 : _a.length)) { filteredConnections = []; } const connectionsInfo = filteredConnections.length > 0 ? '\t* ' + filteredConnections .map((conn) => `${conn.platform} - Key: ${conn.key}`) .join('\n\t* ') : 'No connections available'; const availablePlatformsInfo = this.connectionDefinitions.map((def) => `\n\t* ${def.platform} (${def.name})`).join(''); if ((options === null || options === void 0 ? void 0 : options.knowledgeAgentConfig) && !this.useKnowledgeAgent) { throw new Error("Cannot provide Knowledge Agent configuration when Knowledge Agent is disabled. Please set useKnowledgeAgent to true if you want to use the Knowledge Agent."); } // Choose the appropriate system prompt based on options if (this.useAuthkit && this.useKnowledgeAgent) { this.systemPromptValue = (0, knowledgeAgentWithAuthkitSystem_1.getKnowledgeAgentWithAuthkitSystemPrompt)(connectionsInfo, availablePlatformsInfo, (_b = this.knowledgeAgentConfig) === null || _b === void 0 ? void 0 : _b.includeEnvironmentVariables); } else if (this.useAuthkit) { this.systemPromptValue = (0, defaultSystemWithAuthkit_1.getDefaultSystemWithAuthkitPrompt)(connectionsInfo, availablePlatformsInfo); } else if (this.useKnowledgeAgent) { this.systemPromptValue = (0, knowledgeAgentSystem_1.getKnowledgeAgentSystemPrompt)(connectionsInfo, availablePlatformsInfo, (_c = this.knowledgeAgentConfig) === null || _c === void 0 ? void 0 : _c.includeEnvironmentVariables); } else { this.systemPromptValue = (0, defaultSystem_1.getDefaultSystemPrompt)(connectionsInfo, availablePlatformsInfo); } }) .catch(error => { console.error('Error during initialization:', error); this.systemPromptValue = "Error loading connections"; }); } async generateSystemPrompt(userSystemPrompt) { await this.waitForInitialization(); const now = new Date(); const prompt = `${userSystemPrompt ? userSystemPrompt + '\n\n' : ''}=== PICA: INTEGRATION ASSISTANT ===\n Everything below is for Pica (picaos.com), your integration assistant that can instantly connect your AI agents to 100+ APIs.\n Current Time: ${now.toLocaleString('en-US', { timeZone: 'GMT' })} (GMT) --- Tools Information --- ${this.system.trim()} `; return prompt; } async initialize() { var _a; await Promise.all([ this.initializeConnections(undefined, (_a = this.options) === null || _a === void 0 ? void 0 : _a.connectors), this.initializeConnectionDefinitions(), ]); } async waitForInitialization() { await this.initialized; return this.system; } async initializeConnections(platform, connectionKeys) { try { if (!connectionKeys || connectionKeys.length === 0) { this.connections = []; return; } const headers = this.generateHeaders(); let baseUrl = this.getConnectionUrl; let hasQueryParam = false; if (platform) { baseUrl += `?platform=${platform}`; hasQueryParam = true; } if (!connectionKeys.includes("*")) { baseUrl += hasQueryParam ? `&key=${connectionKeys.join(',')}` : `?key=${connectionKeys.join(',')}`; hasQueryParam = true; } if (this.identity) { baseUrl += hasQueryParam ? `&identity=${encodeURIComponent(this.identity)}` : `?identity=${encodeURIComponent(this.identity)}`; hasQueryParam = true; } if (this.identityType) { baseUrl += hasQueryParam ? `&identityType=${encodeURIComponent(this.identityType)}` : `?identityType=${encodeURIComponent(this.identityType)}`; hasQueryParam = true; } const fetchPage = (skip, limit) => axios_1.default.get(`${baseUrl}${hasQueryParam ? '&' : '?'}limit=${limit}&skip=${skip}`, { headers }).then(response => response.data); this.connections = await (0, utils_1.paginateResults)(fetchPage); } catch (error) { console.error("Failed to initialize connections:", error); this.connections = []; } } async initializeConnectionDefinitions() { try { const headers = this.generateHeaders(); let url = this.getConnectionDefinitionsUrl; let hasQueryParam = false; if (this.useAuthkit) { url += `?authkit=true`; hasQueryParam = true; } const fetchPage = (skip, limit) => axios_1.default.get(`${url}${hasQueryParam ? '&' : '?'}limit=${limit}&skip=${skip}`, { headers }).then(response => response.data); this.connectionDefinitions = await (0, utils_1.paginateResults)(fetchPage); } catch (error) { console.error("Failed to initialize connection definitions:", error); this.connectionDefinitions = []; } } get system() { return this.systemPromptValue; } generateHeaders() { var _a; return { "Content-Type": "application/json", "x-pica-secret": this.secret, ...(_a = this.options) === null || _a === void 0 ? void 0 : _a.headers }; } async getAllAvailableActions(platform, actions) { var _a; try { const fetchPage = (skip, limit) => axios_1.default.get(`${this.availableActionsUrl}?supported=true&connectionPlatform=${platform}&skip=${skip}&limit=${limit}`, { headers: this.generateHeaders() }).then(response => response.data); const results = await (0, utils_1.paginateResults)(fetchPage); // Normalize action IDs in the results const normalizedResults = results.map(action => { if (action._id) { action._id = (0, utils_1.normalizeActionId)(action._id); } return action; }); // Filter actions by permissions let filteredByPermissions = normalizedResults; const permissions = (_a = this.options) === null || _a === void 0 ? void 0 : _a.permissions; if (permissions === "read") { // Filter for GET methods only filteredByPermissions = normalizedResults.filter(action => { let method = action.method; return (method === null || method === void 0 ? void 0 : method.toUpperCase()) === "GET"; }); } else if (permissions === "write") { // Filter for POST, PUT, PATCH methods filteredByPermissions = normalizedResults.filter(action => { var _a; let method = (_a = action.method) === null || _a === void 0 ? void 0 : _a.toUpperCase(); return method === "POST" || method === "PUT" || method === "PATCH"; }); } // For "admin" or no permissions set, return all actions (no filtering) // Filter actions if actions array is provided if (actions === null || actions === void 0 ? void 0 : actions.length) { return filteredByPermissions.filter(action => actions.includes(action._id)); } return filteredByPermissions; } catch (error) { console.error("Error fetching all available actions:", error); throw new Error("Failed to fetch all available actions"); } } async getAvailablePicaConnectors() { await this.initializeConnectionDefinitions(); return this.connectionDefinitions; } async getAvailableConnectors(platform) { var _a; await this.initializeConnections(platform, (_a = this.options) === null || _a === void 0 ? void 0 : _a.connectors); return this.connections; } async getSingleAction(actionId) { try { const normalizedActionId = (0, utils_1.normalizeActionId)(actionId); const response = await axios_1.default.get(`${this.availableActionsUrl}?_id=${normalizedActionId}`, { headers: this.generateHeaders() }); if (!response.data.rows || response.data.rows.length === 0) { throw new Error(`Action with ID ${normalizedActionId} not found`); } return response.data.rows[0]; } catch (error) { console.error("Error fetching single action:", error); throw new Error("Failed to fetch action"); } } async getMethodFromKnowledge(actionId) { try { const normalizedActionId = (0, utils_1.normalizeActionId)(actionId); const knowledgeResponse = await axios_1.default.get(`${this.baseUrl}/v1/knowledge?_id=${normalizedActionId}`, { headers: this.generateHeaders() }); if (knowledgeResponse.data.rows && knowledgeResponse.data.rows.length > 0) { return knowledgeResponse.data.rows[0].method; } else { throw new Error(`Method not found for action ${actionId}`); } } catch (error) { console.error("Error fetching method from knowledge API:", error); throw new Error(`Failed to fetch method for action ${actionId}`); } } async getAvailableActions(platform) { var _a; try { const allActions = await this.getAllAvailableActions(platform, (_a = this.options) === null || _a === void 0 ? void 0 : _a.actions); return { total: allActions.length, actions: allActions }; } catch (error) { console.error("Error fetching available actions:", error); throw new Error("Failed to fetch available actions"); } } async executePassthrough(actionId, connectionKey, data, path, method, queryParams, headers, isFormData, isFormUrlEncoded, returnRequestConfigWithoutExecution) { try { const allHeaders = { ...this.generateHeaders(), 'x-pica-connection-key': connectionKey, 'x-pica-action-id': actionId, ...(isFormData ? { 'Content-Type': 'multipart/form-data' } : {}), ...(isFormUrlEncoded ? { 'Content-Type': 'application/x-www-form-urlencoded' } : {}), ...headers }; // Remove Content-Type header if no data is being sent const finalHeaders = !data ? Object.entries(allHeaders) .filter(([key]) => key.toLowerCase() !== 'content-type') .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}) : allHeaders; const url = `${this.baseUrl}/v1/passthrough${path.startsWith('/') ? path : '/' + path}`; const requestConfig = { url, method, headers: finalHeaders, params: queryParams }; if ((method === null || method === void 0 ? void 0 : method.toLowerCase()) !== 'get') { if (isFormData) { const formData = new form_data_1.default(); if (data && typeof data === 'object' && !Array.isArray(data)) { Object.entries(data).forEach(([key, value]) => { if (typeof value === 'object') { formData.append(key, JSON.stringify(value)); } else { formData.append(key, value); } }); } requestConfig.data = formData; Object.assign(requestConfig.headers, formData.getHeaders()); } else if (isFormUrlEncoded) { const params = new URLSearchParams(); if (data && typeof data === 'object' && !Array.isArray(data)) { Object.entries(data).forEach(([key, value]) => { if (typeof value === 'object') { params.append(key, JSON.stringify(value)); } else { params.append(key, String(value)); } }); } requestConfig.data = params; } else { requestConfig.data = data; } } if (returnRequestConfigWithoutExecution) { requestConfig.headers['x-pica-secret'] = "YOUR_PICA_SECRET_KEY_HERE"; return { executed: false, requestConfig }; } const response = await (0, axios_1.default)(requestConfig); requestConfig.headers['x-pica-secret'] = "****REDACTED****"; return { executed: true, responseData: response.data, requestConfig }; } catch (error) { console.error("Error executing passthrough:", error); throw error; } } getPromptToConnectPlatformTool() { return { promptToConnectPlatform: (0, ai_1.tool)({ description: "Prompt the user to connect to a platform that they do not currently have access to", inputSchema: zod_1.z.object({ platformName: zod_1.z.string(), }), outputSchema: zod_1.z.object({ response: zod_1.z.string(), }), execute: async ({ platformName }) => { return { response: platformName }; } }) }; } get intelligenceTool() { const baseTool = { getAvailableActions: this.oneTool.getAvailableActions, getActionKnowledge: this.oneTool.getActionKnowledge, execute: (0, ai_1.tool)({ description: "Return a request config to the Pica Passthrough API without executing the action. Show the user a typescript code block to make an HTTP request to the Pica Passthrough API using the request config.", inputSchema: zod_1.z.object({ platform: zod_1.z.string(), action: zod_1.z.object({ _id: zod_1.z.string(), path: zod_1.z.string() }), method: zod_1.z.string().optional(), connectionKey: zod_1.z.string(), data: zod_1.z.any(), pathVariables: zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean()])).optional(), queryParams: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(), headers: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(), isFormData: zod_1.z.boolean().optional(), isFormUrlEncoded: zod_1.z.boolean().optional(), }), outputSchema: zod_1.z.object({ success: zod_1.z.boolean(), title: zod_1.z.string(), message: zod_1.z.string(), raw: zod_1.z.string() }), execute: async (params) => { var _a; try { if (!this.connections.some(conn => conn.key === params.connectionKey) && this.useAuthkit) { throw new Error(`Connection not found. Please add a ${params.platform} connection first.`); } // Handle path variables const templateVariables = params.action.path.match(/\{\{([^}]+)\}\}/g); let resolvedPath = params.action.path; if (templateVariables) { const requiredVariables = templateVariables.map(v => v.replace(/\{\{|\}\}/g, '')); const combinedVariables = { ...(Array.isArray(params.data) ? {} : (params.data || {})), ...(params.pathVariables || {}) }; const missingVariables = requiredVariables.filter(v => !combinedVariables[v]); if (missingVariables.length > 0) { throw new Error(`Missing required path variables: ${missingVariables.join(', ')}. ` + `Please provide values for these variables.`); } // Clean up data object and prepare path variables if (!Array.isArray(params.data)) { requiredVariables.forEach(v => { if (params.data && params.data[v] && (!params.pathVariables || !params.pathVariables[v])) { if (!params.pathVariables) params.pathVariables = {}; params.pathVariables[v] = params.data[v]; delete params.data[v]; } }); } resolvedPath = (0, utils_1.replacePathVariables)(params.action.path, params.pathVariables || {}); } const normalizedActionId = (0, utils_1.normalizeActionId)(params.action._id); // If method is not provided, fetch it from the knowledge API let method = params.method; if (!method) { method = await this.getMethodFromKnowledge(normalizedActionId); } // Execute the passthrough request with all components const result = await this.executePassthrough(normalizedActionId, params.connectionKey, params.data, resolvedPath, method, params.queryParams, params.headers, params.isFormData, params.isFormUrlEncoded, true); return { success: true, title: "Request config returned", message: "Request config returned without execution", raw: JSON.stringify(result.requestConfig) }; } catch (error) { console.error("Error creating request config:", error); return { success: false, title: "Failed to create request config", message: error.message, raw: JSON.stringify(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) || error) }; } } }) }; // Add the promptToConnectPlatform tool if authkit is enabled if (this.useAuthkit) { return { ...baseTool, ...this.getPromptToConnectPlatformTool() }; } return baseTool; } get oneTool() { const baseTool = { getAvailableActions: (0, ai_1.tool)({ description: "Get available actions for a specific platform", inputSchema: zod_1.z.object({ platform: zod_1.z.string(), }), outputSchema: zod_1.z.object({ success: zod_1.z.boolean(), actions: zod_1.z.array(zod_1.z.object({ _id: zod_1.z.string(), title: zod_1.z.string(), tags: zod_1.z.array(zod_1.z.string()), })), platform: zod_1.z.string(), content: zod_1.z.string() }), execute: async (params) => { var _a; try { const availableActions = await this.getAvailableActions(params.platform); const simplifiedActions = availableActions.actions.map(action => ({ _id: action._id, title: action.title, tags: action.tags, })); return { success: true, actions: simplifiedActions, platform: params.platform, content: `Found ${simplifiedActions.length} available actions for ${params.platform}` }; } catch (error) { console.error("Error getting available actions:", error); return { success: false, title: "Failed to get available actions", message: error.message, raw: JSON.stringify(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) || error) }; } } }), getActionKnowledge: (0, ai_1.tool)({ description: "Get full action details including knowledge documentation for a specific action", inputSchema: zod_1.z.object({ platform: zod_1.z.string(), actionId: zod_1.z.string(), }), outputSchema: zod_1.z.object({ success: zod_1.z.boolean(), action: zod_1.z.object({ _id: zod_1.z.string(), title: zod_1.z.string(), connectionPlatform: zod_1.z.string(), knowledge: zod_1.z.string(), path: zod_1.z.string(), baseUrl: zod_1.z.string(), tags: zod_1.z.array(zod_1.z.string()), method: zod_1.z.string().optional() }), platform: zod_1.z.string(), content: zod_1.z.string() }), execute: async (params) => { var _a; try { const normalizedActionId = (0, utils_1.normalizeActionId)(params.actionId); const action = await this.getSingleAction(normalizedActionId); return { success: true, action, platform: params.platform, content: `Found knowledge for action: ${action.title}` }; } catch (error) { console.error("Error getting action knowledge:", error); return { success: false, title: "Failed to get action knowledge", message: error.message, raw: JSON.stringify(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) || error) }; } } }), execute: (0, ai_1.tool)({ description: "Execute a specific action using the passthrough API", inputSchema: zod_1.z.object({ platform: zod_1.z.string(), action: zod_1.z.object({ _id: zod_1.z.string(), path: zod_1.z.string() }), method: zod_1.z.string().optional(), connectionKey: zod_1.z.string(), data: zod_1.z.any(), pathVariables: zod_1.z.record(zod_1.z.string(), zod_1.z.union([zod_1.z.string(), zod_1.z.number(), zod_1.z.boolean()])).optional(), queryParams: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(), headers: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional(), isFormData: zod_1.z.boolean().optional(), isFormUrlEncoded: zod_1.z.boolean().optional(), }), execute: async (params) => { var _a; try { if (!this.connections.some(conn => conn.key === params.connectionKey)) { throw new Error(`Connection not found. Please add a ${params.platform} connection first.`); } const normalizedActionId = (0, utils_1.normalizeActionId)(params.action._id); const fullAction = await this.getSingleAction(normalizedActionId); // If method is not provided, fetch it from the knowledge API let method = params.method; if (!method) { method = await this.getMethodFromKnowledge(normalizedActionId); } // Handle path variables const templateVariables = params.action.path.match(/\{\{([^}]+)\}\}/g); let resolvedPath = params.action.path; if (templateVariables) { const requiredVariables = templateVariables.map(v => v.replace(/\{\{|\}\}/g, '')); const combinedVariables = { ...(Array.isArray(params.data) ? {} : (params.data || {})), ...(params.pathVariables || {}) }; const missingVariables = requiredVariables.filter(v => !combinedVariables[v]); if (missingVariables.length > 0) { throw new Error(`Missing required path variables: ${missingVariables.join(', ')}. ` + `Please provide values for these variables.`); } // Clean up data object and prepare path variables if (!Array.isArray(params.data)) { requiredVariables.forEach(v => { if (params.data && params.data[v] && (!params.pathVariables || !params.pathVariables[v])) { if (!params.pathVariables) params.pathVariables = {}; params.pathVariables[v] = params.data[v]; delete params.data[v]; } }); } resolvedPath = (0, utils_1.replacePathVariables)(params.action.path, params.pathVariables || {}); } // Execute the passthrough request with all components const result = await this.executePassthrough(normalizedActionId, params.connectionKey, params.data, resolvedPath, method, params.queryParams, params.headers, params.isFormData, params.isFormUrlEncoded, false); return { success: true, data: result.executed ? result.responseData : undefined, connectionKey: params.connectionKey, platform: params.platform, action: fullAction.title, requestConfig: result.requestConfig, content: `Executed ${fullAction.title} via ${params.platform}`, }; } catch (error) { console.error("Error executing action:", error); return { success: false, title: "Failed to execute action", message: error.message, raw: JSON.stringify(((_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.data) || error) }; } } }) }; // Add the promptToConnectPlatform tool if authkit is enabled if (this.useAuthkit) { return { ...baseTool, ...this.getPromptToConnectPlatformTool() }; } return baseTool; } } exports.Pica = Pica;