feat(agent): add workflow-agent-office with generate/edit AdapterFn

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
jiayiyan
2026-05-18 13:19:32 +08:00
parent 4285b8b180
commit bee3911f3f
4 changed files with 100 additions and 1 deletions
@@ -0,0 +1,27 @@
import { describe, expect, test } from "bun:test";
import { packageDescriptor } from "../src/package-descriptor.js";
import { createOfficeAgent } from "../src/agent.js";
describe("createOfficeAgent", () => {
test("returns an AdapterFn (function)", () => {
const agent = createOfficeAgent({ outputDir: "/tmp", command: null, timeout: null });
expect(typeof agent).toBe("function");
});
test("AdapterFn returns a RoleFn (function)", () => {
const agent = createOfficeAgent({ outputDir: "/tmp", command: null, timeout: null });
const roleFn = agent("", expect.anything() as never);
expect(typeof roleFn).toBe("function");
});
});
describe("packageDescriptor", () => {
test("has correct name", () => {
expect(packageDescriptor.name).toBe("@uncaged/workflow-agent-office");
});
test("has outputDir in configSchema required", () => {
const schema = packageDescriptor.configSchema as { required: string[] };
expect(schema.required).toContain("outputDir");
});
});
@@ -0,0 +1,44 @@
import * as z from "zod/v4";
import type { AdapterFn, RoleResult, ThreadContext, WorkflowRuntime } from "@uncaged/workflow-runtime";
import { createLogger } from "@uncaged/workflow-util";
import { editDocument, generateDocument } from "./runner.js";
import type { OfficeAgentConfig } from "./types.js";
const log = createLogger({ sink: { kind: "stderr" } });
type ParsedInput = { prompt: string; inputDocx: string | null };
function parseStartInput(content: string): ParsedInput {
try {
const parsed = JSON.parse(content) as Record<string, unknown>;
if (typeof parsed.prompt === "string") {
return {
prompt: parsed.prompt,
inputDocx: typeof parsed.inputDocx === "string" ? parsed.inputDocx : null,
};
}
} catch {
// not JSON — treat whole content as prompt, generate mode
}
return { prompt: content, inputDocx: null };
}
export function createOfficeAgent(config: OfficeAgentConfig): AdapterFn {
return <T>(_systemPrompt: string, schema: z.ZodType<T>) =>
async (ctx: ThreadContext, _runtime: WorkflowRuntime): Promise<RoleResult<T>> => {
const { prompt, inputDocx } = parseStartInput(ctx.start.content);
log("8FQKP3NV", `office-agent: mode=${inputDocx === null ? "generate" : "edit"} thread=${ctx.threadId}`);
let raw: string;
if (inputDocx === null) {
const result = await generateDocument(config, ctx.threadId, prompt);
raw = JSON.stringify({ mode: "generate", outputDocx: result.outputDocx, sourceDocx: null });
} else {
const result = await editDocument(config, ctx.threadId, prompt, inputDocx);
raw = JSON.stringify({ mode: "edit", outputDocx: result.outputDocx, sourceDocx: result.sourceDocx });
}
const meta = schema.parse(JSON.parse(raw)) as T;
return { meta, childThread: null };
};
}
+3 -1
View File
@@ -1 +1,3 @@
export {}; export { createOfficeAgent } from "./agent.js";
export { packageDescriptor } from "./package-descriptor.js";
export type { OfficeAgentConfig } from "./types.js";
@@ -0,0 +1,26 @@
import type { PackageDescriptor } from "@uncaged/workflow-runtime";
export const packageDescriptor: PackageDescriptor = {
name: "@uncaged/workflow-agent-office",
version: "0.1.0",
capabilities: ["office-agent-cli", "docx-generate", "docx-edit"],
configSchema: {
type: "object",
required: ["outputDir"],
properties: {
outputDir: {
type: "string",
description: "Root directory for workflow outputs; subdirs are created per threadId.",
},
command: {
anyOf: [{ type: "string" }, { type: "null" }],
description: "Path to office-agent CLI binary; null uses PATH.",
},
timeout: {
anyOf: [{ type: "number" }, { type: "null" }],
description: "Timeout in milliseconds; null means no limit.",
},
},
additionalProperties: false,
},
};