import type { UnifiedToolDefinition } from "@/lib/ai/core/types"; /** * Built-in, provider-neutral tools executed server-side. Each is a JSON-schema function * the model may call; PolyLLM runs it and feeds the result back (max N rounds). * They are deliberately side-effect free (no network, no filesystem). */ export interface BuiltinTool { id: string; label: string; description: string; definition: UnifiedToolDefinition; run: (args: Record) => Promise; } function safeCalc(expr: string): number { if (!/^[\d\s+\-*/().,%^eE]+$/.test(expr) || expr.length > 200) throw new Error("Only numbers and + - * / ( ) ^ % are allowed"); // shunting-yard would be overkill; use Function on a validated arithmetic-only string. const normalized = expr.replace(/\^/g, "**").replace(/,/g, ""); const result = Function(`"use strict"; return (${normalized});`)() as unknown; if (typeof result !== "number" || !Number.isFinite(result)) throw new Error("Expression did not evaluate to a finite number"); return result; } export const BUILTIN_TOOLS: BuiltinTool[] = [ { id: "calculator", label: "Calculator", description: "Evaluate arithmetic expressions precisely.", definition: { name: "calculator", description: "Evaluate an arithmetic expression (numbers, + - * / ^ % and parentheses) and return the exact result. Use it for any non-trivial math.", parameters: { type: "object", properties: { expression: { type: "string", description: "The arithmetic expression, e.g. (1234 * 5678) / 3" } }, required: ["expression"], additionalProperties: false }, strict: true, }, run: async (args) => { const expression = String(args.expression ?? ""); return { expression, result: safeCalc(expression) }; }, }, { id: "clock", label: "Clock", description: "Current date and time in any IANA time zone.", definition: { name: "current_datetime", description: "Get the current date and time. Optionally in a specific IANA time zone (e.g. America/Toronto).", parameters: { type: "object", properties: { timeZone: { type: "string", description: "IANA time zone, defaults to UTC" } }, required: ["timeZone"], additionalProperties: false }, strict: true, }, run: async (args) => { const tz = typeof args.timeZone === "string" && args.timeZone ? args.timeZone : "UTC"; const now = new Date(); let local: string; try { local = new Intl.DateTimeFormat("en-US", { timeZone: tz, dateStyle: "full", timeStyle: "long" }).format(now); } catch { throw new Error(`Unknown time zone: ${tz}`); } return { iso: now.toISOString(), timeZone: tz, local, unixMs: now.getTime() }; }, }, { id: "random", label: "Random", description: "Cryptographically secure random integers.", definition: { name: "random_integer", description: "Return a cryptographically secure random integer between min and max (inclusive).", parameters: { type: "object", properties: { min: { type: "integer" }, max: { type: "integer" } }, required: ["min", "max"], additionalProperties: false }, strict: true, }, run: async (args) => { const { randomInt } = await import("node:crypto"); const min = Math.trunc(Number(args.min)); const max = Math.trunc(Number(args.max)); if (!Number.isFinite(min) || !Number.isFinite(max) || max <= min) throw new Error("max must be greater than min"); return { value: randomInt(min, max + 1), min, max }; }, }, ]; export function resolveTools(ids: string[] | undefined): BuiltinTool[] { if (!ids?.length) return []; return BUILTIN_TOOLS.filter((t) => ids.includes(t.id)); } export async function runBuiltinTool(name: string, args: Record): Promise<{ result: unknown; isError: boolean; durationMs: number }> { const t0 = Date.now(); const tool = BUILTIN_TOOLS.find((t) => t.definition.name === name); if (!tool) return { result: { error: `Unknown tool ${name}` }, isError: true, durationMs: 0 }; try { const result = await tool.run(args); return { result, isError: false, durationMs: Date.now() - t0 }; } catch (e) { return { result: { error: (e as Error).message }, isError: true, durationMs: Date.now() - t0 }; } }