/** * A2A 消息格式转换层 * * 负责 A2A 协议类型与内部业务类型之间的双向转换: * - Agent 数据行 ↔ Agent Card * - A2A Message ↔ 纯文本输入(用于 generateText) * - 纯文本输出 ↔ A2A Message * - A2A Part ↔ StoredPart(预留,当前仅 TextPart) */ import type { AgentRow } from "../agent/types"; import type { StoredPart } from "../agent/stored-part"; import type { A2AAgentCard, A2ASkill, A2AMessage, A2APart, A2ATextPart, A2ADataPart, A2AFilePart, } from "./types"; import { A2A_PROTOCOL_VERSION } from "./types"; // ============ Agent Row → Agent Card ============ export function agentRowToCard(agent: AgentRow, baseUrl: string): A2AAgentCard { const url = `${baseUrl}/api/agents/${agent.slug}/a2a`; const skills: A2ASkill[] = [ { id: agent.slug, name: agent.name, description: agent.description ?? agent.systemPrompt.slice(0, 200), tags: agent.isCallable ? ["callable", "agent"] : ["agent"], inputModes: ["text/plain"], outputModes: ["text/plain"], }, ]; return { name: agent.name, description: agent.description ?? "", url, version: "1.0.0", protocolVersion: A2A_PROTOCOL_VERSION, capabilities: { streaming: true, pushNotifications: false, stateTransition: true, }, defaultInputModes: ["text/plain"], defaultOutputModes: ["text/plain"], skills, }; } // ============ A2A Message ↔ 纯文本 ============ export function a2aMessageToText(message: A2AMessage): string { const textParts = message.parts.filter((p): p is A2ATextPart => p.type === "text"); if (textParts.length === 0) return ""; return textParts.map((p) => p.text).join("\n"); } export function textToA2AMessage(text: string, role: "user" | "agent" = "agent"): A2AMessage { return { role, parts: [{ type: "text", text }], }; } // ============ A2A Part ↔ StoredPart(预留) ============ export function a2aPartsToStoredParts(parts: A2APart[]): StoredPart[] { return parts .filter((p): p is A2ATextPart => p.type === "text") .map((p, i) => ({ id: `a2a-part-${i}`, type: "text", text: p.text, })); } export function storedPartsToA2AParts(parts: StoredPart[]): A2APart[] { return parts .filter((p) => p.type === "text" && p.text !== undefined) .map((p) => ({ type: "text" as const, text: p.text!, })); } // ============ Part 类型守卫 ============ export function isTextPart(part: A2APart): part is A2ATextPart { return part.type === "text"; } export function isDataPart(part: A2APart): part is A2ADataPart { return part.type === "data"; } export function isFilePart(part: A2APart): part is A2AFilePart { return part.type === "file"; }