import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { mirrorCodexAppServerTranscript } from "./transcript-mirror.js"; let tempDir: string; describe("mirrorCodexAppServerTranscript", () => { beforeEach(async () => { tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-codex-transcript-")); }); afterEach(async () => { await fs.rm(tempDir, { recursive: true, force: true }); }); it("mirrors user and assistant messages into the PI transcript", async () => { const sessionFile = path.join(tempDir, "session.jsonl"); await mirrorCodexAppServerTranscript({ sessionFile, sessionKey: "agent:main:session-1", messages: [ { role: "user", content: "hello", timestamp: 1 }, { role: "assistant", content: [{ type: "text", text: "hi" }], api: "openai-codex-responses", provider: "openai-codex", model: "gpt-5.4-codex", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop", timestamp: 2, }, ], }); const records = (await fs.readFile(sessionFile, "utf8")) .trim() .split("\n") .map((line) => JSON.parse(line) as { type?: string; message?: { role?: string } }); expect(records[0]?.type).toBe("session"); expect(records.slice(1).map((record) => record.message?.role)).toEqual(["user", "assistant"]); }); it("deduplicates app-server turn mirrors by idempotency scope", async () => { const sessionFile = path.join(tempDir, "session.jsonl"); const messages = [ { role: "user" as const, content: "hello", timestamp: 1 }, { role: "assistant" as const, content: [{ type: "text" as const, text: "hi" }], api: "openai-codex-responses", provider: "openai-codex", model: "gpt-5.4-codex", usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, }, stopReason: "stop" as const, timestamp: 2, }, ]; await mirrorCodexAppServerTranscript({ sessionFile, messages, idempotencyScope: "codex-app-server:thread-1:turn-1", }); await mirrorCodexAppServerTranscript({ sessionFile, messages, idempotencyScope: "codex-app-server:thread-1:turn-1", }); const records = (await fs.readFile(sessionFile, "utf8")) .trim() .split("\n") .map((line) => JSON.parse(line) as { message?: { role?: string; idempotencyKey?: string } }); expect(records.slice(1).map((record) => record.message?.role)).toEqual(["user", "assistant"]); expect(records.slice(1).map((record) => record.message?.idempotencyKey)).toEqual([ "codex-app-server:thread-1:turn-1:user:0", "codex-app-server:thread-1:turn-1:assistant:1", ]); }); });