@obayd/agentic
Version:
A powerful agent framework for LLMs.
117 lines (100 loc) • 4.07 kB
JavaScript
// src/Tool.js
export class Tool {
name = "";
#description = "";
#params = {};
#rawDescription = null;
#action = async (params, ...args) => {};
constructor(name) {
if (typeof name !== "string" || !name.trim())
throw new Error("Tool name must be a non-empty string");
if (!/^[a-zA-Z0-9_]+$/.test(name))
throw new Error(
`Invalid tool name: "${name}". Use only letters, numbers, and underscores.`
);
this.name = name;
}
static make(name) {
return new this(name);
}
description(description) {
if (typeof description !== "string")
throw new Error("Tool description must be a string");
this.#description = description;
return this;
}
desc(description) {
return this.description(description);
}
param(name, description, options = {}) {
if (typeof name !== "string" || !name.trim())
throw new Error("Param name must be a non-empty string");
if (typeof description !== "string")
throw new Error("Param description must be a string");
let type = "string",
required = true,
enumValues;
if (typeof options === "string") {
type = options;
} else if (typeof options === "object" && options !== null) {
type = options.type ?? type;
required = options.required ?? required;
enumValues = options.enum;
if (enumValues && !Array.isArray(enumValues))
throw new Error(`Enum for param "${name}" must be an array.`);
}
this.#params[name] = { description, type, required, enum: enumValues };
return this;
}
raw(description = "Raw text input for the tool.") {
if (typeof description !== "string")
throw new Error("Raw description must be a string");
this.#rawDescription = description;
return this;
}
action(callback) {
if (typeof callback !== "function")
throw new Error("Tool action must be a function");
this.#action = async (params, ...args) => callback(params, ...args);
return this;
}
buildPromptString() {
const paramStrings = Object.entries(this.#params).map(([name, details]) => {
let parts = [`${name}: ${details.type}`];
if (details.description) parts.push(` // ${details.description}`);
parts.push(details.required ? " (required)" : " (optional)");
if (details.enum) parts.push(` (enum: ${JSON.stringify(details.enum)})`);
return parts.join("");
});
let str = `- ${this.name}: ${this.#description || "No description provided."}`;
if (paramStrings.length > 0) {
str += `\n PARAMS:\n ${paramStrings.join("\n ")}`;
}
if (this.#rawDescription) {
str += `\n RAW INPUT: // ${this.#rawDescription}`;
}
return str;
}
async call(params, raw, args = []) {
const finalParams = { ...params };
if (this.#rawDescription !== null && raw !== undefined && raw !== null) {
finalParams.raw = raw;
}
for (const [pName, pDetails] of Object.entries(this.#params)) {
if (pDetails.required && !(pName in finalParams)) {
if (!(pName === 'raw' && this.#rawDescription !== null && (raw !== undefined && raw !== null))) {
throw new Error(`Missing required parameter: ${pName}`);
}
}
}
try {
return await this.#action(finalParams, ...args);
} catch (error) {
console.error(
`Error during tool action execution for "${this.name}":`,
error
);
return { error: `Tool execution failed: ${error.message}` };
}
}
}