60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
import { stat } from "node:fs/promises";
|
|
import { dirname, join } from "node:path";
|
|
import { spawnCli } from "@uncaged/workflow-util-agent";
|
|
import type { AdapterFn, RoleResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime";
|
|
import * as z from "zod/v4";
|
|
|
|
export type DifferAdapterConfig = {
|
|
command: string | null;
|
|
timeout: number | null;
|
|
};
|
|
|
|
type SpawnCliFn = typeof spawnCli;
|
|
|
|
export function createDifferAdapter(
|
|
config: DifferAdapterConfig,
|
|
spawnCliFn: SpawnCliFn = spawnCli,
|
|
): AdapterFn {
|
|
const command = config.command ?? "docx-diff";
|
|
return <T>(_systemPrompt: string, schema: z.ZodType<T>) =>
|
|
async (ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
|
|
const officeStep = ctx.steps.find((s) => s.role === "office");
|
|
if (officeStep === undefined) {
|
|
throw new Error("differ: office step not found in ctx.steps (invariant violation)");
|
|
}
|
|
|
|
const officeMeta = officeStep.meta as { outputDocx: string; sourceDocx: string };
|
|
|
|
const { outputDocx, sourceDocx } = officeMeta;
|
|
const reportPath = join(dirname(outputDocx), "diff_report.html");
|
|
|
|
const result = await spawnCliFn(
|
|
command,
|
|
[sourceDocx, outputDocx, "-o", "html", "--out-file", reportPath],
|
|
{ cwd: null, timeoutMs: config.timeout },
|
|
);
|
|
|
|
if (!result.ok) {
|
|
const e = result.error;
|
|
if (e.kind === "non_zero_exit" && e.exitCode === 1) {
|
|
} else if (e.kind === "non_zero_exit") {
|
|
throw new Error(`differ: docx-diff failed (exit ${e.exitCode}): ${e.stderr}`);
|
|
} else if (e.kind === "timeout") {
|
|
throw new Error("differ: timed out");
|
|
} else {
|
|
throw new Error(`differ: spawn failed: ${(e as { message: string }).message}`);
|
|
}
|
|
}
|
|
|
|
try {
|
|
await stat(reportPath);
|
|
} catch {
|
|
throw new Error(`differ: report file not found: ${reportPath}`);
|
|
}
|
|
|
|
const raw = JSON.stringify({ outputDocx, sourceDocx, diffReport: reportPath });
|
|
const meta = schema.parse(JSON.parse(raw)) as T;
|
|
return { meta, childThread: null };
|
|
};
|
|
}
|