UNPKG

@stackone/ai

Version:

Tools for agents to perform actions on your SaaS

367 lines (365 loc) 10.8 kB
import { StackOneError } from "./utils/errors.js"; import { RequestBuilder } from "./modules/requestBuilder.js"; import * as orama from "@orama/orama"; import { jsonSchema } from "ai"; //#region src/tool.ts /** * Base class for all tools. Provides common functionality for executing API calls * and converting to various formats (OpenAI, AI SDK) */ var BaseTool = class { name; description; parameters; executeConfig; requestBuilder; experimental_preExecute; constructor(name, description, parameters, executeConfig, headers, experimental_preExecute) { this.name = name; this.description = description; this.parameters = parameters; this.executeConfig = executeConfig; this.requestBuilder = new RequestBuilder(executeConfig, headers); this.experimental_preExecute = experimental_preExecute; } /** * Set headers for this tool */ setHeaders(headers) { this.requestBuilder.setHeaders(headers); return this; } /** * Get the current headers */ getHeaders() { return this.requestBuilder.getHeaders(); } /** * Execute the tool with the provided parameters */ async execute(inputParams, options) { try { if (inputParams !== void 0 && typeof inputParams !== "string" && typeof inputParams !== "object") throw new StackOneError(`Invalid parameters type. Expected object or string, got ${typeof inputParams}. Parameters: ${JSON.stringify(inputParams)}`); const params = typeof inputParams === "string" ? JSON.parse(inputParams) : inputParams || {}; let processedParams = params; if (this.experimental_preExecute) processedParams = await this.experimental_preExecute(params); return await this.requestBuilder.execute(processedParams, options); } catch (error) { if (error instanceof StackOneError) throw error; throw new StackOneError(`Error executing tool: ${error instanceof Error ? error.message : String(error)}`); } } /** * Convert the tool to OpenAI format */ toOpenAI() { return { type: "function", function: { name: this.name, description: this.description, parameters: { type: "object", properties: this.parameters.properties, required: this.parameters.required } } }; } /** * Convert the tool to AI SDK format */ toAISDK(options = { executable: true }) { const schema = { type: "object", properties: this.parameters.properties || {}, required: this.parameters.required || [], additionalProperties: false }; return { [this.name]: { parameters: jsonSchema(schema), description: this.description, ...options.executable && { execute: async (args) => { try { return await this.execute(args); } catch (error) { return `Error executing tool: ${error instanceof Error ? error.message : String(error)}`; } } } } }; } }; /** * StackOne-specific tool class with additional functionality */ var StackOneTool = class extends BaseTool { /** * Get the current account ID */ getAccountId() { return this.getHeaders()["x-account-id"]; } /** * Set the account ID for this tool */ setAccountId(accountId) { this.setHeaders({ "x-account-id": accountId }); return this; } }; /** * Collection of tools with utility methods */ var Tools = class Tools { tools; constructor(tools) { this.tools = tools; } /** * Get the number of tools in the collection */ get length() { return this.tools.length; } /** * Get a tool by name */ getTool(name, options) { const originalTool = this.tools.find((tool) => tool.name === name); if (!originalTool) return void 0; if (!options?.experimental_schemaOverride && !options?.experimental_preExecute) return originalTool; let parameters = originalTool.parameters; if (options.experimental_schemaOverride) parameters = options.experimental_schemaOverride(originalTool.parameters); if (originalTool instanceof StackOneTool) { const newTool$1 = new StackOneTool(originalTool.name, originalTool.description, parameters, originalTool.executeConfig, originalTool.getHeaders(), options.experimental_preExecute); return newTool$1; } const newTool = new BaseTool(originalTool.name, originalTool.description, parameters, originalTool.executeConfig, originalTool.getHeaders(), options.experimental_preExecute); return newTool; } /** * Get a StackOne tool by name */ getStackOneTool(name) { const tool = this.getTool(name); if (tool instanceof StackOneTool) return tool; throw new StackOneError(`Tool ${name} is not a StackOne tool`); } /** * Check if a tool is a StackOne tool */ isStackOneTool(tool) { return tool instanceof StackOneTool; } /** * Get all StackOne tools in the collection */ getStackOneTools() { return this.tools.filter((tool) => tool instanceof StackOneTool); } /** * Convert all tools to OpenAI format */ toOpenAI() { return this.tools.map((tool) => tool.toOpenAI()); } /** * Convert all tools to AI SDK format */ toAISDK() { const result = {}; for (const tool of this.tools) Object.assign(result, tool.toAISDK()); return result; } /** * Filter tools by a predicate function */ filter(predicate) { return new Tools(this.tools.filter(predicate)); } /** * Return meta tools for tool discovery and execution * @beta This feature is in beta and may change in future versions */ async metaTools() { const oramaDb = await initializeOramaDb(this.tools); const baseTools = [metaSearchRelevantTools(oramaDb, this.tools), metaExecuteTool(this)]; const tools = new Tools(baseTools); return tools; } /** * Iterator implementation */ [Symbol.iterator]() { let index = 0; const tools = this.tools; return { next() { if (index < tools.length) return { value: tools[index++], done: false }; return { value: void 0, done: true }; } }; } /** * Convert to array */ toArray() { return [...this.tools]; } /** * Map tools to a new array */ map(mapper) { return this.tools.map(mapper); } /** * Execute a function for each tool */ forEach(callback) { this.tools.forEach(callback); } }; /** * Initialize Orama database with BM25 algorithm for tool search * Using Orama's BM25 scoring algorithm for relevance ranking * @see https://docs.orama.com/open-source/usage/create * @see https://docs.orama.com/open-source/usage/search/bm25-algorithm/ */ async function initializeOramaDb(tools) { const oramaDb = orama.create({ schema: { name: "string", description: "string", category: "string", tags: "string[]" }, components: { tokenizer: { stemming: true } } }); for (const tool of tools) { const parts = tool.name.split("_"); const category = parts[0]; const actionTypes = [ "create", "update", "delete", "get", "list", "search" ]; const actions = parts.filter((p) => actionTypes.includes(p)); orama.insert(oramaDb, { name: tool.name, description: tool.description, category, tags: [...parts, ...actions] }); } return oramaDb; } function metaSearchRelevantTools(oramaDb, allTools) { const name = "meta_search_tools"; const description = "Searches for relevant tools based on a natural language query. This tool should be called first to discover available tools before executing them."; const parameters = { type: "object", properties: { query: { type: "string", description: "Natural language query describing what tools you need (e.g., \"tools for managing employees\", \"create time off request\")" }, limit: { type: "number", description: "Maximum number of tools to return (default: 5)", default: 5 }, minScore: { type: "number", description: "Minimum relevance score (0-1) for results (default: 0.3)", default: .3 } }, required: ["query"] }; const executeConfig = { method: "LOCAL", url: "local://get-relevant-tools", bodyType: "json", params: [] }; const tool = new BaseTool(name, description, parameters, executeConfig); tool.execute = async (inputParams) => { try { if (inputParams !== void 0 && typeof inputParams !== "string" && typeof inputParams !== "object") throw new StackOneError(`Invalid parameters type. Expected object or string, got ${typeof inputParams}. Parameters: ${JSON.stringify(inputParams)}`); const params = typeof inputParams === "string" ? JSON.parse(inputParams) : inputParams || {}; const results = await orama.search(oramaDb, { term: params.query || "", limit: params.limit || 5 }); const minScore = params.minScore ?? .3; const filteredResults = results.hits.filter((hit) => hit.score >= minScore); const toolConfigs = filteredResults.map((hit) => { const doc = hit.document; const tool$1 = allTools.find((t) => t.name === doc.name); if (!tool$1) return null; const result = { name: tool$1.name, description: tool$1.description, parameters: tool$1.parameters, score: hit.score }; return result; }).filter(Boolean); return { tools: toolConfigs }; } catch (error) { if (error instanceof StackOneError) throw error; throw new StackOneError(`Error executing tool: ${error instanceof Error ? error.message : String(error)}`); } }; return tool; } function metaExecuteTool(tools) { const name = "meta_execute_tool"; const description = "Executes a specific tool by name with the provided parameters. Use this after discovering tools with meta_search_tools."; const parameters = { type: "object", properties: { toolName: { type: "string", description: "Name of the tool to execute" }, params: { type: "object", description: "Parameters to pass to the tool" } }, required: ["toolName", "params"] }; const executeConfig = { method: "LOCAL", url: "local://execute-tool", bodyType: "json", params: [] }; const tool = new BaseTool(name, description, parameters, executeConfig); tool.execute = async (inputParams, options) => { try { if (inputParams !== void 0 && typeof inputParams !== "string" && typeof inputParams !== "object") throw new StackOneError(`Invalid parameters type. Expected object or string, got ${typeof inputParams}. Parameters: ${JSON.stringify(inputParams)}`); const params = typeof inputParams === "string" ? JSON.parse(inputParams) : inputParams || {}; const { toolName, params: toolParams } = params; const toolToExecute = tools.getTool(toolName); if (!toolToExecute) throw new StackOneError(`Tool ${toolName} not found`); return await toolToExecute.execute(toolParams, options); } catch (error) { if (error instanceof StackOneError) throw error; throw new StackOneError(`Error executing tool: ${error instanceof Error ? error.message : String(error)}`); } }; return tool; } //#endregion export { BaseTool, StackOneTool, Tools }; //# sourceMappingURL=tool.js.map