import { getChannelPlugin, normalizeChannelId } from "../channels/plugins/index.js"; import { truncateUtf16Safe } from "../utils.js"; import { type MessagingToolSend, normalizeTargetForProvider } from "./pi-embedded-messaging.js"; const TOOL_RESULT_MAX_CHARS = 8000; function truncateToolText(text: string): string { if (text.length <= TOOL_RESULT_MAX_CHARS) return text; return `${truncateUtf16Safe(text, TOOL_RESULT_MAX_CHARS)}\n…(truncated)…`; } export function sanitizeToolResult(result: unknown): unknown { if (!result || typeof result !== "object") return result; const record = result as Record; const content = Array.isArray(record.content) ? record.content : null; if (!content) return record; const sanitized = content.map((item) => { if (!item || typeof item !== "object") return item; const entry = item as Record; const type = typeof entry.type === "string" ? entry.type : undefined; if (type === "text" && typeof entry.text === "string") { return { ...entry, text: truncateToolText(entry.text) }; } if (type === "image") { const data = typeof entry.data === "string" ? entry.data : undefined; const bytes = data ? data.length : undefined; const cleaned = { ...entry }; delete cleaned.data; return { ...cleaned, bytes, omitted: true }; } return entry; }); return { ...record, content: sanitized }; } export function isToolResultError(result: unknown): boolean { if (!result || typeof result !== "object") return false; const record = result as { details?: unknown }; const details = record.details; if (!details || typeof details !== "object") return false; const status = (details as { status?: unknown }).status; if (typeof status !== "string") return false; const normalized = status.trim().toLowerCase(); return normalized === "error" || normalized === "timeout"; } export function extractMessagingToolSend( toolName: string, args: Record, ): MessagingToolSend | undefined { // Provider docking: new provider tools must implement plugin.actions.extractToolSend. const action = typeof args.action === "string" ? args.action.trim() : ""; const accountIdRaw = typeof args.accountId === "string" ? args.accountId.trim() : undefined; const accountId = accountIdRaw ? accountIdRaw : undefined; if (toolName === "message") { if (action !== "send" && action !== "thread-reply") return undefined; const toRaw = typeof args.to === "string" ? args.to : undefined; if (!toRaw) return undefined; const providerRaw = typeof args.provider === "string" ? args.provider.trim() : ""; const providerId = providerRaw ? normalizeChannelId(providerRaw) : null; const provider = providerId ?? (providerRaw ? providerRaw.toLowerCase() : "message"); const to = normalizeTargetForProvider(provider, toRaw); return to ? { tool: toolName, provider, accountId, to } : undefined; } const providerId = normalizeChannelId(toolName); if (!providerId) return undefined; const plugin = getChannelPlugin(providerId); const extracted = plugin?.actions?.extractToolSend?.({ args }); if (!extracted?.to) return undefined; const to = normalizeTargetForProvider(providerId, extracted.to); return to ? { tool: toolName, provider: providerId, accountId: extracted.accountId ?? accountId, to, } : undefined; }