80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
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<string, unknown>;
|
|
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<string, unknown>;
|
|
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<string, unknown>,
|
|
): 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;
|
|
}
|