You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

105 lines
2.7 KiB

/**
* 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";
}