97 lines
5.3 KiB
TypeScript
97 lines
5.3 KiB
TypeScript
import type { UIMessageChunk } from "ai";
|
||
|
||
export type BackendSseEvent = {
|
||
event: string;
|
||
data: Record<string, unknown>;
|
||
};
|
||
|
||
export function backendEventToUiChunk(
|
||
item: BackendSseEvent,
|
||
textId: string,
|
||
sequence = 0,
|
||
): (UIMessageChunk & { id?: string }) | null {
|
||
if (item.event === "text_delta") {
|
||
return { type: "text-delta", id: textId, delta: String(item.data.text || "") };
|
||
}
|
||
if (item.event === "progress") {
|
||
return {
|
||
type: "data-cad-progress",
|
||
id: `progress_${String(item.data.taskId || "task")}_${sequence}`,
|
||
data: { ...item.data, sequence },
|
||
};
|
||
}
|
||
if (["image_observation", "requirements_document_ready", "completion_target_ready", "requirements_compiled", "modeling_plan_ready", "completion_result_ready", "action_selection", "tool_call", "candidate_result", "candidate_review", "final_review", "task_terminal"].includes(item.event)) {
|
||
const review = item.data.review && typeof item.data.review === "object"
|
||
? item.data.review as Record<string, unknown>
|
||
: null;
|
||
const lifecycle = String(item.data.lifecycle || "");
|
||
const status = item.event === "task_terminal"
|
||
? (lifecycle === "failed" ? "error" : lifecycle === "waiting_for_user" || lifecycle === "waiting_retry" ? "waiting" : "success")
|
||
: String((item.data.result as Record<string, unknown> | undefined)?.status || "") === "rejected"
|
||
|| String((item.data.result as Record<string, unknown> | undefined)?.status || "") === "repair"
|
||
? "error"
|
||
: String(item.data.status || "running");
|
||
const taskId = String(item.data.taskId || "task");
|
||
const eventId = String(item.data.eventId || `${taskId}_${sequence}_${item.event}`);
|
||
const metadata = sequence > 0 || item.data.eventId
|
||
? {
|
||
eventId,
|
||
sequence,
|
||
...(review ? { review } : {}),
|
||
}
|
||
: {};
|
||
return {
|
||
type: "data-cad-progress",
|
||
id: `event_${eventId}`,
|
||
data: { step: item.event, label: ({
|
||
image_observation: "参考图片观察", requirements_document_ready: "需求文档已冻结", completion_target_ready: "完成目标已冻结", requirements_compiled: "需求合同已编译", modeling_plan_ready: "建模计划已冻结", completion_result_ready: "完成结果已就绪", action_selection: "动作选择", tool_call: "建模工具", candidate_result: "候选构建", candidate_review: "候选独立复核", final_review: "最终独立复核", task_terminal: "生成任务",
|
||
} as Record<string, string>)[item.event], status, ...metadata, message: String(
|
||
item.data.message || item.data.reason
|
||
|| (Array.isArray(item.data.questions) ? item.data.questions.map(String).filter(Boolean).join(";") : "")
|
||
|| (review?.evidence instanceof Array ? review.evidence.join(";") : "")
|
||
|| (review?.issues instanceof Array ? review.issues.map((issue) => typeof issue === "object" && issue ? String((issue as Record<string, unknown>).message || "") : String(issue)).filter(Boolean).join(";") : ""),
|
||
),
|
||
...(item.data.taskId ? { taskId } : {}),
|
||
...(item.data.nodeId ? { nodeId: String(item.data.nodeId) } : {}),
|
||
...(item.data.lifecycle ? { lifecycle: String(item.data.lifecycle) } : {}),
|
||
...(Array.isArray(item.data.questions) ? { questions: item.data.questions.map(String).filter(Boolean) } : {}),
|
||
...(Array.isArray(item.data.issues) ? { issues: item.data.issues.map(String).filter(Boolean) } : {}),
|
||
...(item.data.blockerType ? { blockerType: String(item.data.blockerType) } : {}),
|
||
...(typeof item.data.userActionRequired === "boolean" ? { userActionRequired: item.data.userActionRequired } : {}),
|
||
...(item.data.verificationStatus ? { verificationStatus: String(item.data.verificationStatus) } : {}),
|
||
...(Array.isArray(item.data.verificationWarnings) ? { verificationWarnings: item.data.verificationWarnings.map(String).filter(Boolean) } : {}),
|
||
...(item.data.clarificationPath ? { clarificationPath: String(item.data.clarificationPath) } : {}),
|
||
...(item.data.timestamp ? { timestamp: String(item.data.timestamp) } : {}),
|
||
...(item.data.markdown ? { markdown: String(item.data.markdown) } : {}),
|
||
...(item.data.tool ? { tool: String(item.data.tool) } : {}),
|
||
...(item.data.invocationId ? { invocationId: String(item.data.invocationId) } : {}),
|
||
...(item.data.arguments && typeof item.data.arguments === "object" ? { arguments: item.data.arguments as Record<string, unknown> } : {}),
|
||
...(item.data.result !== undefined ? { result: item.data.result } : {}),
|
||
...(Array.isArray(item.data.evidence) ? { evidence: item.data.evidence.map(String) } : {}),
|
||
},
|
||
};
|
||
}
|
||
if (item.event === "cad_result") {
|
||
const taskId = String(item.data.taskId || "");
|
||
const revisionId = String(item.data.revisionId || Date.now());
|
||
return {
|
||
type: "data-cad-result",
|
||
// Each revision is a distinct progressive result. Reusing only the task
|
||
// id makes the AI SDK reconcile intermediate revisions into one part.
|
||
id: `result_${taskId}_${revisionId}`,
|
||
data: item.data,
|
||
};
|
||
}
|
||
if (item.event === "cad_error") {
|
||
return {
|
||
type: "data-cad-error",
|
||
id: `error_${Date.now()}`,
|
||
data: {
|
||
...item.data,
|
||
...(Array.isArray(item.data.fieldErrors) ? { fieldErrors: item.data.fieldErrors } : {}),
|
||
},
|
||
};
|
||
}
|
||
return null;
|
||
}
|