| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- import type {
- Message,
- MessagePart,
- PipelineStage,
- SessionMessage,
- SessionRuntime,
- SessionSummary,
- ToolExecution,
- } from "../../types";
- const NULL_BOOK_KEY = "__null__";
- const AGENT_LABELS: Record<string, string> = {
- architect: "建书",
- writer: "写作",
- auditor: "审计",
- reviser: "修订",
- exporter: "导出",
- };
- const TOOL_LABELS: Record<string, string> = {
- read: "读取文件",
- edit: "编辑文件",
- grep: "搜索",
- ls: "列目录",
- };
- export function bookKey(bookId: string | null | undefined): string {
- return bookId ?? NULL_BOOK_KEY;
- }
- export function extractErrorMessage(error: string | { code?: string; message?: string }): string {
- if (typeof error === "string") return error;
- return error.message ?? "Unknown error";
- }
- export function resolveToolLabel(tool: string, agent?: string): string {
- if (tool === "sub_agent" && agent) return AGENT_LABELS[agent] ?? agent;
- return TOOL_LABELS[tool] ?? tool;
- }
- export function summarizeResult(result: unknown): string {
- if (typeof result === "string") return result.slice(0, 200);
- if (result && typeof result === "object") {
- const record = result as Record<string, unknown>;
- if (typeof record.content === "string") return record.content.slice(0, 200);
- }
- return String(result).slice(0, 200);
- }
- export function extractToolError(result: unknown): string {
- if (typeof result === "string") return result.slice(0, 500);
- if (result && typeof result === "object") {
- const record = result as Record<string, unknown>;
- if (typeof record.content === "string") return record.content.slice(0, 500);
- if (record.content && Array.isArray(record.content)) {
- const textPart = record.content.find((content: any) => content.type === "text");
- if (textPart) return (textPart as any).text?.slice(0, 500) ?? "";
- }
- }
- return String(result).slice(0, 500);
- }
- export function getOrCreateStream(
- messages: ReadonlyArray<Message>,
- streamTs: number,
- ): [ReadonlyArray<Message>, Message] {
- const last = messages[messages.length - 1];
- if (last?.timestamp === streamTs && last.role === "assistant") {
- return [messages, last];
- }
- const message: Message = { role: "assistant", content: "", timestamp: streamTs, parts: [] };
- return [[...messages, message], message];
- }
- export function replaceLast(
- messages: ReadonlyArray<Message>,
- updated: Message,
- ): ReadonlyArray<Message> {
- return [...messages.slice(0, -1), updated];
- }
- export function findRunningToolPart(
- parts: MessagePart[],
- ): (MessagePart & { type: "tool" }) | undefined {
- for (let i = parts.length - 1; i >= 0; i -= 1) {
- const part = parts[i];
- if (part.type === "tool" && part.execution.status === "running") {
- return part as MessagePart & { type: "tool" };
- }
- }
- return undefined;
- }
- export function deriveFlat(
- parts: MessagePart[],
- ): { content: string; thinking?: string; thinkingStreaming?: boolean; toolExecutions?: ToolExecution[] } {
- let content = "";
- let thinking = "";
- let thinkingStreaming = false;
- const toolExecutions: ToolExecution[] = [];
- for (const part of parts) {
- if (part.type === "thinking") {
- if (thinking) thinking += "\n\n---\n\n";
- thinking += part.content;
- if (part.streaming) thinkingStreaming = true;
- continue;
- }
- if (part.type === "text") {
- content += part.content;
- continue;
- }
- toolExecutions.push(part.execution);
- }
- return {
- content,
- ...(thinking ? { thinking } : {}),
- ...(thinkingStreaming ? { thinkingStreaming: true } : {}),
- ...(toolExecutions.length > 0 ? { toolExecutions } : {}),
- };
- }
- export function createSessionRuntime(input: {
- sessionId: string;
- bookId: string | null;
- title: string | null;
- messages?: ReadonlyArray<Message>;
- isDraft?: boolean;
- }): SessionRuntime {
- return {
- sessionId: input.sessionId,
- bookId: input.bookId,
- title: input.title,
- messages: input.messages ?? [],
- stream: null,
- isStreaming: false,
- lastError: null,
- pendingBookArgs: null,
- isDraft: input.isDraft ?? false,
- };
- }
- export function deserializeMessages(
- msgs: ReadonlyArray<SessionMessage>,
- ): ReadonlyArray<Message> {
- return msgs
- .filter((message) => message.role === "user" || message.role === "assistant")
- .map((message) => {
- const toolExecutions = (message as any).toolExecutions as ToolExecution[] | undefined;
- const parts: MessagePart[] = [];
- if (message.thinking) parts.push({ type: "thinking", content: message.thinking, streaming: false });
- if (toolExecutions) {
- for (const execution of toolExecutions) {
- parts.push({ type: "tool", execution });
- }
- }
- if (message.content) parts.push({ type: "text", content: message.content });
- return {
- role: message.role as "user" | "assistant",
- content: message.content,
- thinking: message.thinking,
- toolExecutions,
- timestamp: message.timestamp,
- parts: parts.length > 0 ? parts : undefined,
- };
- });
- }
- export function updateSession(
- sessions: Record<string, SessionRuntime>,
- sessionId: string,
- updater: (session: SessionRuntime) => Partial<SessionRuntime>,
- ): Record<string, SessionRuntime> {
- const existing = sessions[sessionId];
- if (!existing) return sessions;
- return {
- ...sessions,
- [sessionId]: {
- ...existing,
- ...updater(existing),
- },
- };
- }
- export function upsertSessionSummary(
- sessions: Record<string, SessionRuntime>,
- summary: Pick<SessionSummary, "sessionId" | "bookId" | "title">,
- ): Record<string, SessionRuntime> {
- const existing = sessions[summary.sessionId];
- return {
- ...sessions,
- [summary.sessionId]: existing
- ? { ...existing, bookId: summary.bookId, title: summary.title }
- : createSessionRuntime(summary),
- };
- }
- export function mergeSessionIds(
- existing: ReadonlyArray<string> | undefined,
- incoming: ReadonlyArray<string>,
- ): ReadonlyArray<string> {
- if (!existing?.length) return [...incoming];
- const seen = new Set(existing);
- const appended = incoming.filter((id) => !seen.has(id));
- if (appended.length === 0) return existing as string[];
- return [...existing, ...appended];
- }
- export function sessionMatchesEvent(sessionId: string, data: unknown): boolean {
- if (!data || typeof data !== "object") return false;
- return (data as { sessionId?: unknown }).sessionId === sessionId;
- }
|