import { describe, expect, test } from "bun:test" import type { SessionMessageInfo } from "@opencode-ai/client/promise" import { normalizeSessionMessages } from "./session-message" describe("normalizeSessionMessages", () => { test("projects current turns into stable legacy rendering records", () => { const source = [ { id: "msg_1", type: "agent-switched", agent: "build", time: { created: 1 } }, { id: "msg_2", type: "model-switched", model: { id: "claude", providerID: "anthropic", variant: "high" }, time: { created: 2 }, }, { id: "msg_3", type: "user", text: "inspect @src/client.ts", files: [ { data: "aGVsbG8=", mime: "text/plain", name: "note.txt", source: { type: "inline" }, }, { data: "ZXhwb3J0IHt9", mime: "text/plain", name: "client.ts", source: { type: "inline" }, mention: { text: "@src/client.ts", start: 8, end: 22 }, }, ], agents: [{ name: "review", mention: { text: "@review", start: 0, end: 7 } }], time: { created: 3 }, }, { id: "msg_4", type: "assistant", agent: "build", model: { id: "claude", providerID: "anthropic", variant: "high" }, content: [ { type: "reasoning", text: "Thinking", time: { created: 4, completed: 5 } }, { type: "text", text: "Result" }, { type: "tool", id: "call_1", name: "read", state: { status: "completed", input: { filePath: "note.txt" }, metadata: { title: "note.txt" }, content: [{ type: "text", text: "hello" }], }, time: { created: 5, ran: 6, completed: 7 }, }, ], cost: 0.1, tokens: { input: 10, output: 5, reasoning: 2, cache: { read: 1, write: 0 } }, time: { created: 4, completed: 7 }, }, { id: "msg_5", type: "compaction", status: "completed", reason: "auto", summary: "summary", recent: "recent", time: { created: 8 }, }, ] satisfies SessionMessageInfo[] const result = normalizeSessionMessages("ses_1", source) expect(result.messages).toHaveLength(2) expect(result.messages[0]).toMatchObject({ id: "msg_3", role: "user", agent: "build", model: { providerID: "anthropic", modelID: "claude", variant: "high" }, }) expect(result.messages[1]).toMatchObject({ id: "msg_4", role: "assistant", parentID: "msg_3", cost: 0.1 }) expect(result.parts.get("msg_3")?.map((part) => part.id)).toEqual([ "msg_3:text:0", "msg_3:file:0", "msg_3:file:1", "msg_3:agent:0", "msg_5:compaction", ]) expect(result.parts.get("msg_3")?.[2]).toMatchObject({ type: "file", source: { type: "file", path: "src/client.ts", text: { value: "@src/client.ts", start: 8, end: 22 }, }, }) expect(result.parts.get("msg_4")?.map((part) => part.id)).toEqual(["msg_4:reasoning:0", "msg_4:text:0", "call_1"]) expect(result.parts.get("msg_4")?.[2]).toMatchObject({ type: "tool", tool: "read", state: { status: "completed", output: "hello" }, }) }) test("does not invent a parent for an assistant-only page", () => { const source = [ { id: "msg_2", type: "assistant", agent: "build", model: { id: "model", providerID: "provider" }, content: [{ type: "text", text: "orphan" }], time: { created: 2 }, }, ] satisfies SessionMessageInfo[] expect(normalizeSessionMessages("ses_1", source).messages).toEqual([]) }) test("projects a current shell message into a renderable standalone turn", () => { const source = [ { id: "msg_shell", type: "shell", shellID: "shell_1", command: "printf hello", status: "exited", exit: 0, output: { output: "hello", cursor: 5, size: 5, truncated: false }, time: { created: 1, completed: 2 }, }, ] satisfies SessionMessageInfo[] const result = normalizeSessionMessages("ses_1", source) expect(result.messages).toEqual([ expect.objectContaining({ id: "msg_shell", role: "user" }), expect.objectContaining({ id: "msg_shell:assistant", role: "assistant", parentID: "msg_shell" }), ]) expect(result.parts.get("msg_shell")).toEqual([expect.objectContaining({ type: "text", text: "printf hello" })]) expect(result.parts.get("msg_shell:assistant")).toEqual([ expect.objectContaining({ type: "tool", tool: "bash", state: expect.objectContaining({ status: "completed", input: { command: "printf hello" }, output: "hello", title: "Shell", }), }), ]) }) test("adapts current edit fields for the legacy edit renderer", () => { const source = [ { id: "msg_user", type: "user", text: "edit it", time: { created: 1 } }, { id: "msg_assistant", type: "assistant", agent: "build", model: { id: "model", providerID: "provider" }, content: [ { type: "tool", id: "call_edit", name: "edit", state: { status: "completed", input: { path: "/repo/README.md", oldString: "old", newString: "new" }, content: [{ type: "text", text: "Edited file successfully" }], metadata: { files: [ { file: "README.md", patch: "@@ -1 +1 @@\n-old\n+new", additions: 1, deletions: 1, status: "modified", }, ], replacements: 1, }, }, time: { created: 2, ran: 3, completed: 4 }, }, ], time: { created: 2, completed: 4 }, }, ] satisfies SessionMessageInfo[] const result = normalizeSessionMessages("ses_1", source) expect(result.parts.get("msg_assistant")).toEqual([ expect.objectContaining({ type: "tool", tool: "edit", state: expect.objectContaining({ status: "completed", input: expect.objectContaining({ path: "/repo/README.md", filePath: "/repo/README.md" }), metadata: expect.objectContaining({ filediff: { file: "README.md", patch: "@@ -1 +1 @@\n-old\n+new", additions: 1, deletions: 1, }, }), }), }), ]) }) })