Files
openclaw/extensions/codex/app-server/transcript-mirror.ts
2026-04-10 21:22:16 +01:00

84 lines
2.5 KiB
TypeScript

import fs from "node:fs/promises";
import path from "node:path";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import { SessionManager } from "@mariozechner/pi-coding-agent";
import {
acquireSessionWriteLock,
emitSessionTranscriptUpdate,
} from "openclaw/plugin-sdk/agent-harness";
export async function mirrorCodexAppServerTranscript(params: {
sessionFile: string;
sessionKey?: string;
messages: AgentMessage[];
idempotencyScope?: string;
}): Promise<void> {
const messages = params.messages.filter(
(message) => message.role === "user" || message.role === "assistant",
);
if (messages.length === 0) {
return;
}
await fs.mkdir(path.dirname(params.sessionFile), { recursive: true });
const lock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
timeoutMs: 10_000,
});
try {
const existingIdempotencyKeys = await readTranscriptIdempotencyKeys(params.sessionFile);
const sessionManager = SessionManager.open(params.sessionFile);
for (const [index, message] of messages.entries()) {
const idempotencyKey = params.idempotencyScope
? `${params.idempotencyScope}:${message.role}:${index}`
: undefined;
if (idempotencyKey && existingIdempotencyKeys.has(idempotencyKey)) {
continue;
}
const transcriptMessage = {
...message,
...(idempotencyKey ? { idempotencyKey } : {}),
} as Parameters<SessionManager["appendMessage"]>[0];
sessionManager.appendMessage(transcriptMessage);
if (idempotencyKey) {
existingIdempotencyKeys.add(idempotencyKey);
}
}
} finally {
await lock.release();
}
if (params.sessionKey) {
emitSessionTranscriptUpdate({ sessionFile: params.sessionFile, sessionKey: params.sessionKey });
} else {
emitSessionTranscriptUpdate(params.sessionFile);
}
}
async function readTranscriptIdempotencyKeys(sessionFile: string): Promise<Set<string>> {
const keys = new Set<string>();
let raw: string;
try {
raw = await fs.readFile(sessionFile, "utf8");
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
throw error;
}
return keys;
}
for (const line of raw.split(/\r?\n/)) {
if (!line.trim()) {
continue;
}
try {
const parsed = JSON.parse(line) as { message?: { idempotencyKey?: unknown } };
if (typeof parsed.message?.idempotencyKey === "string") {
keys.add(parsed.message.idempotencyKey);
}
} catch {
continue;
}
}
return keys;
}