feat: add differ adapter — wraps docx-diff CLI, handles exit code 0/1/2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
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 { mode: string; outputDocx: string; sourceDocx: string | null };
|
||||
if (officeMeta.mode !== "edit" || officeMeta.sourceDocx === null) {
|
||||
throw new Error("differ: office step is not in edit mode");
|
||||
}
|
||||
|
||||
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) {
|
||||
// exit 1 means "documents differ" — the normal success case for docx-diff
|
||||
} 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 };
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user