import fs from "node:fs/promises"; import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core"; import { detectMime } from "../../media/mime.js"; import { sanitizeToolResultImages } from "../tool-images.js"; // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-agent-core uses a different module instance. export type AnyAgentTool = AgentTool; export type StringParamOptions = { required?: boolean; trim?: boolean; label?: string; }; export function readStringParam( params: Record, key: string, options: StringParamOptions & { required: true }, ): string; export function readStringParam( params: Record, key: string, options?: StringParamOptions, ): string | undefined; export function readStringParam( params: Record, key: string, options: StringParamOptions = {}, ) { const { required = false, trim = true, label = key } = options; const raw = params[key]; if (typeof raw !== "string") { if (required) throw new Error(`${label} required`); return undefined; } const value = trim ? raw.trim() : raw; if (!value) { if (required) throw new Error(`${label} required`); return undefined; } return value; } export function readStringArrayParam( params: Record, key: string, options: StringParamOptions & { required: true }, ): string[]; export function readStringArrayParam( params: Record, key: string, options?: StringParamOptions, ): string[] | undefined; export function readStringArrayParam( params: Record, key: string, options: StringParamOptions = {}, ) { const { required = false, label = key } = options; const raw = params[key]; if (Array.isArray(raw)) { const values = raw .filter((entry) => typeof entry === "string") .map((entry) => entry.trim()) .filter(Boolean); if (values.length === 0) { if (required) throw new Error(`${label} required`); return undefined; } return values; } if (typeof raw === "string") { const value = raw.trim(); if (!value) { if (required) throw new Error(`${label} required`); return undefined; } return [value]; } if (required) throw new Error(`${label} required`); return undefined; } export function jsonResult(payload: unknown): AgentToolResult { return { content: [ { type: "text", text: JSON.stringify(payload, null, 2), }, ], details: payload, }; } export async function imageResult(params: { label: string; path: string; base64: string; mimeType: string; extraText?: string; details?: Record; }): Promise> { const content: AgentToolResult["content"] = [ { type: "text", text: params.extraText ?? `MEDIA:${params.path}`, }, { type: "image", data: params.base64, mimeType: params.mimeType, }, ]; const result: AgentToolResult = { content, details: { path: params.path, ...params.details }, }; return await sanitizeToolResultImages(result, params.label); } export async function imageResultFromFile(params: { label: string; path: string; extraText?: string; details?: Record; }): Promise> { const buf = await fs.readFile(params.path); const mimeType = (await detectMime({ buffer: buf.slice(0, 256) })) ?? "image/png"; return await imageResult({ label: params.label, path: params.path, base64: buf.toString("base64"), mimeType, extraText: params.extraText, details: params.details, }); }