// Pi → CodeBuddy message conversion helpers. import type { Message as PiMessage } from "@earendil-works/pi-ai"; import type { CbMessage } from "./cb-session-io.js"; import { pascalCase } from "change-case"; export const PROVIDER_ID = "codebuddy"; export const PI_TO_SDK_TOOL_NAME: Record = { read: "Read", write: "Write", edit: "Edit", bash: "Bash", }; export function sanitizeToolId(id: string, cache: Map): string { const existing = cache.get(id); if (existing) return existing; const clean = id.replace(/[^a-zA-Z0-9_-]/g, "_"); cache.set(id, clean); return clean; } export function mapPiToolNameToSdk(name: string, customToolNameToSdk?: Map): string { if (!name) return ""; const normalized = name.toLowerCase(); if (customToolNameToSdk) { const mapped = customToolNameToSdk.get(name) ?? customToolNameToSdk.get(normalized); if (mapped) return mapped; } if (PI_TO_SDK_TOOL_NAME[normalized]) return PI_TO_SDK_TOOL_NAME[normalized]; return pascalCase(name); } export function messageContentToText( content: string | Array<{ type: string; text?: string; data?: string; mimeType?: string }>, ): string { if (typeof content === "string") return content; if (!Array.isArray(content)) return ""; const parts = []; let hasText = false; for (const block of content) { if (block.type === "text" && block.text) { parts.push(block.text); hasText = true; } else if (block.type !== "text" && block.type !== "image") { parts.push(`[${block.type}]`); } } return hasText ? parts.join("\n") : ""; } /** Convert pi message array to session-import format. */ export function convertPiMessages( messages: PiMessage[], customToolNameToSdk?: Map, ): { anthropicMessages: Array<{ role: "user" | "assistant"; content: string | Array }>; sanitizedIds: Map } { const anthropicMessages: Array<{ role: "user" | "assistant"; content: string | Array }> = []; const sanitizedIds = new Map(); for (const msg of messages) { if (msg.role === "user") { if (typeof msg.content === "string") { anthropicMessages.push({ role: "user", content: msg.content || "[empty]" }); } else if (Array.isArray(msg.content)) { const parts = []; let hadInvalidImage = false; for (const block of msg.content) { if (block.type === "text" && block.text) parts.push({ type: "text", text: block.text }); else if (block.type === "image" && block.data && block.mimeType) { parts.push({ type: "image", source: { type: "base64", media_type: block.mimeType, data: block.data } }); } else if (block.type === "image") hadInvalidImage = true; } anthropicMessages.push({ role: "user", content: parts.length ? parts : hadInvalidImage ? [{ type: "text", text: "[invalid image omitted]" }] : "[image]" }); } else { anthropicMessages.push({ role: "user", content: "[empty]" }); } } else if (msg.role === "assistant") { const content = Array.isArray(msg.content) ? msg.content : []; const blocks = []; for (const block of content) { if (block.type === "text" && block.text) { blocks.push({ type: "text", text: block.text }); } else if (block.type === "thinking") { const sig = block.thinkingSignature; const isCodebuddyProvider = msg.provider === PROVIDER_ID || msg.provider === "codebuddy-sdk" || msg.api === "codebuddy-sdk"; if (isCodebuddyProvider && sig) { blocks.push({ type: "thinking", thinking: block.thinking ?? "", signature: sig }); } } else if (block.type === "toolCall") { const toolName = mapPiToolNameToSdk(block.name, customToolNameToSdk); blocks.push({ type: "tool_use", id: sanitizeToolId(block.id, sanitizedIds), name: toolName, input: block.arguments ?? {} }); } } if (!blocks.length) blocks.push({ type: "text", text: "[incompatible content omitted]" }); anthropicMessages.push({ role: "assistant", content: blocks }); } else if (msg.role === "toolResult") { const text = typeof msg.content === "string" ? msg.content : messageContentToText(msg.content); anthropicMessages.push({ role: "user", content: [{ type: "tool_result", tool_use_id: sanitizeToolId(msg.toolCallId, sanitizedIds), content: text || "", is_error: msg.isError }], }); } } return { anthropicMessages, sanitizedIds }; } export type SessionMessage = CbMessage;