@stackone/ai
Version:
Tools for agents to perform actions on your SaaS
367 lines (365 loc) • 10.8 kB
JavaScript
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