spb/chat-spboucher-ai
Public
1import "server-only";2import type { ToolDef } from "../ai/types";3import { webSearch, fetchPage } from "./web";4import { runPython } from "./python";5import { searchDocuments } from "./documents";6import { generateImageTool } from "./image";7import { saveMemoryTool } from "./memory";89export type Permission = "auto" | "approval";1011export interface ToolContext { conversationId: string; projectId: string | null; messageId: string; signal?: AbortSignal; allow: Set<string> }12export interface Tool extends ToolDef {13 permission: Permission;14 category: "web" | "code" | "documents" | "media" | "memory";15 run(input: Record<string, unknown>, ctx: ToolContext): Promise<unknown>;16}1718export const TOOLS: Tool[] = [19 {20 name: "web_search", category: "web", permission: "auto",21 description: "Search the web (Tavily). Returns titles, URLs and snippets. Use for current events, facts to verify, sources to cite.",22 inputSchema: { type: "object", properties: { query: { type: "string" }, max_results: { type: "integer", minimum: 1, maximum: 10 }, recency_days: { type: "integer", description: "Only results newer than N days" } }, required: ["query"], additionalProperties: false },23 run: (i) => webSearch(String(i.query), { maxResults: Number(i.max_results ?? 6), recencyDays: i.recency_days ? Number(i.recency_days) : undefined }),24 },25 {26 name: "fetch_page", category: "web", permission: "auto",27 description: "Fetch a public web page and return its readable text (truncated to ~20k chars). Only use on URLs you obtained from search results or the user.",28 inputSchema: { type: "object", properties: { url: { type: "string" } }, required: ["url"], additionalProperties: false },29 run: (i) => fetchPage(String(i.url)),30 },31 {32 name: "python", category: "code", permission: "auto",33 description: "Execute a Python 3 script in an isolated sandbox (no network, 20 s timeout, temp dir). Print results to stdout. numpy/pandas available if installed on the host. Use for calculations, data analysis, simulations.",34 inputSchema: { type: "object", properties: { code: { type: "string" } }, required: ["code"], additionalProperties: false },35 run: (i, ctx) => runPython(String(i.code), ctx.signal),36 },37 {38 name: "search_documents", category: "documents", permission: "auto",39 description: "Semantic + keyword search over the documents attached to this conversation or project. Returns the most relevant passages with file names and pages.",40 inputSchema: { type: "object", properties: { query: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 12 } }, required: ["query"], additionalProperties: false },41 run: (i, ctx) => searchDocuments(String(i.query), ctx, Number(i.limit ?? 6)),42 },43 {44 name: "generate_image", category: "media", permission: "auto",45 description: "Generate an image with OpenAI gpt-image (verified). Returns an attachment id to display. Keep prompts descriptive.",46 inputSchema: { type: "object", properties: { prompt: { type: "string" }, size: { type: "string", enum: ["1024x1024", "1536x1024", "1024x1536"] }, quality: { type: "string", enum: ["low", "medium", "high"] } }, required: ["prompt"], additionalProperties: false },47 run: (i, ctx) => generateImageTool(String(i.prompt), ctx, { size: i.size as string | undefined, quality: i.quality as string | undefined }),48 },49 {50 name: "save_memory", category: "memory", permission: "auto",51 description: "Propose a durable memory about the owner (preference, project fact, infrastructure detail) for future conversations. Never store secrets, passwords or sensitive personal data.",52 inputSchema: { type: "object", properties: { content: { type: "string" }, category: { type: "string", enum: ["preference", "writing", "research", "project", "infrastructure", "objective", "concept", "context"] } }, required: ["content"], additionalProperties: false },53 run: (i, ctx) => saveMemoryTool(String(i.content), String(i.category ?? "context"), ctx),54 },55];5657export function toolByName(name: string): Tool | undefined { return TOOLS.find(t => t.name === name); }58export function toolDefs(names?: string[]): ToolDef[] { return TOOLS.filter(t => !names || names.includes(t.name)).map(({ name, description, inputSchema }) => ({ name, description, inputSchema })); }59