1e9900bed3
- Protocol: AgentFnResult = string | { output, childThread }, RoleOutput.childThread,
ThreadContext.bundleHash for parent state lookup
- Runtime: create-workflow normalizes AgentFnResult, propagates childThread in RoleOutput
- Engine: ExecuteThreadOptions.parentStateHash, appendStateForStep writes childThread,
putStartNode uses parentStateHash
- workflowAsAgent: reads parent head state from threads.json, passes parentStateHash
to child, returns { output, childThread: rootHash }
- Integration test: 4 cases verifying bidirectional Merkle links (306 lines)
Phase 2 of #194 (Merkle Call Stack). Closes #196.
小橘 <xiaoju@shazhou.work>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { type AgentContext, START } from "@uncaged/workflow-runtime";
|
|
|
|
import { createLlmAdapter } from "../src/create-llm-adapter.js";
|
|
|
|
function makeCtx(userContent: string): AgentContext {
|
|
return {
|
|
start: {
|
|
role: START,
|
|
content: userContent,
|
|
meta: {},
|
|
timestamp: 1,
|
|
},
|
|
depth: 0,
|
|
bundleHash: "TESTHASH00001",
|
|
steps: [],
|
|
threadId: "01TEST000000000000000000TR",
|
|
currentRole: { name: "planner", systemPrompt: "system instructions" },
|
|
};
|
|
}
|
|
|
|
describe("createLlmAdapter", () => {
|
|
const originalFetch = globalThis.fetch;
|
|
|
|
test("posts system + user (start.content) and returns assistant text", async () => {
|
|
globalThis.fetch = (() =>
|
|
Promise.resolve(
|
|
new Response(JSON.stringify({ choices: [{ message: { content: "model reply" } }] }), {
|
|
status: 200,
|
|
headers: { "Content-Type": "application/json" },
|
|
}),
|
|
)) as unknown as typeof fetch;
|
|
|
|
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
|
|
const adapter = createLlmAdapter(provider);
|
|
const out = await adapter(makeCtx("trigger text"));
|
|
|
|
globalThis.fetch = originalFetch;
|
|
|
|
expect(out).toBe("model reply");
|
|
});
|
|
|
|
test("throws on non-ok fetch response", async () => {
|
|
globalThis.fetch = (() =>
|
|
Promise.resolve(
|
|
new Response("Internal Server Error", {
|
|
status: 500,
|
|
headers: { "Content-Type": "text/plain" },
|
|
}),
|
|
)) as unknown as typeof fetch;
|
|
|
|
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
|
|
const adapter = createLlmAdapter(provider);
|
|
|
|
await expect(adapter(makeCtx("hi"))).rejects.toThrow("llm:");
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
|
|
test("throws on fetch network failure", async () => {
|
|
globalThis.fetch = (() => Promise.reject(new Error("ECONNREFUSED"))) as unknown as typeof fetch;
|
|
|
|
const provider = { baseUrl: "https://api.example/v1", apiKey: "k", model: "m" };
|
|
const adapter = createLlmAdapter(provider);
|
|
|
|
await expect(adapter(makeCtx("hi"))).rejects.toThrow();
|
|
globalThis.fetch = originalFetch;
|
|
});
|
|
});
|