feat(protocol): add AdapterFn/RoleFn/AdapterBinding, refactor createWorkflow to use AdapterBinding (#222)
- Add RoleFn<T>, AdapterFn, AdapterBinding types to workflow-protocol - Mark AgentFn, AgentFnResult, AgentBinding as @deprecated - Refactor createWorkflow to accept AdapterBinding instead of AgentBinding - Adapter returns typed meta directly — no more extract call in workflow loop - Add buildThreadInput (ThreadContext-based), keep buildAgentPrompt as deprecated wrapper - Update template bundle-entries to wrap AgentFn as AdapterFn - Update solve-issue tests to use AdapterFn directly
This commit is contained in:
@@ -3,11 +3,9 @@ import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js"
|
||||
import type * as z from "zod/v4";
|
||||
|
||||
import {
|
||||
type AdapterBinding,
|
||||
type AdapterFn,
|
||||
type AdvanceOutcome,
|
||||
type AgentBinding,
|
||||
type AgentContext,
|
||||
type AgentFn,
|
||||
type AgentFnResult,
|
||||
END,
|
||||
type ModeratorContext,
|
||||
type RoleDefinition,
|
||||
@@ -51,28 +49,18 @@ function mergeUniqueHashes(a: readonly string[], b: readonly string[]): string[]
|
||||
return out;
|
||||
}
|
||||
|
||||
function normalizeAgentResult(result: AgentFnResult): {
|
||||
output: string;
|
||||
childThread: string | null;
|
||||
} {
|
||||
if (typeof result === "string") {
|
||||
return { output: result, childThread: null };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function agentForRole(binding: AgentBinding, roleName: string): AgentFn {
|
||||
function adapterForRole(binding: AdapterBinding, roleName: string): AdapterFn {
|
||||
const overrides = binding.overrides;
|
||||
const overrideFn: AgentFn | undefined =
|
||||
const overrideFn: AdapterFn | undefined =
|
||||
overrides !== null ? overrides[roleName as keyof typeof overrides] : undefined;
|
||||
return overrideFn !== undefined ? overrideFn : binding.agent;
|
||||
return overrideFn !== undefined ? overrideFn : binding.adapter;
|
||||
}
|
||||
|
||||
async function advanceOneRound<M extends RoleMeta>(
|
||||
def: Pick<WorkflowDefinition<M>, "roles"> & {
|
||||
pickNext: (ctx: ModeratorContext<M>) => (keyof M & string) | typeof END;
|
||||
},
|
||||
binding: AgentBinding,
|
||||
binding: AdapterBinding,
|
||||
params: {
|
||||
thread: ModeratorContext<M>;
|
||||
runtime: WorkflowRuntime;
|
||||
@@ -94,37 +82,23 @@ async function advanceOneRound<M extends RoleMeta>(
|
||||
return { kind: "complete", completion: { returnCode: 1, summary: `unknown role: ${next}` } };
|
||||
}
|
||||
|
||||
const agentCtx: AgentContext<M> = {
|
||||
...modCtx,
|
||||
currentRole: { name: next, systemPrompt: roleDef.systemPrompt },
|
||||
};
|
||||
|
||||
const agent = agentForRole(binding, next);
|
||||
const agentResult = normalizeAgentResult(await agent(agentCtx as unknown as AgentContext));
|
||||
|
||||
const agentContentHash = await putContentNodeWithRefs(runtime.cas, agentResult.output, []);
|
||||
|
||||
const extracted = await runtime.extract(
|
||||
roleDef.schema as z.ZodType<Record<string, unknown>>,
|
||||
agentContentHash,
|
||||
);
|
||||
const adapter = adapterForRole(binding, next);
|
||||
const roleFn = adapter(roleDef.systemPrompt, roleDef.schema as z.ZodType<Record<string, unknown>>);
|
||||
const meta = await roleFn(modCtx as unknown as ThreadContext, runtime);
|
||||
|
||||
const refsFromMeta = resolveExtractedRefs(
|
||||
roleDef as unknown as RoleDefinition<Record<string, unknown>>,
|
||||
extracted.meta,
|
||||
meta,
|
||||
);
|
||||
const artifactRefs = mergeUniqueHashes(extracted.refs, refsFromMeta);
|
||||
|
||||
const contentHash =
|
||||
artifactRefs.length === 0
|
||||
? agentContentHash
|
||||
: await putContentNodeWithRefs(runtime.cas, extracted.contentPayload, artifactRefs);
|
||||
const refs = artifactRefs.includes(contentHash) ? artifactRefs : [...artifactRefs, contentHash];
|
||||
const contentPayload = JSON.stringify(meta);
|
||||
const contentHash = await putContentNodeWithRefs(runtime.cas, contentPayload, refsFromMeta);
|
||||
const refs = refsFromMeta.length === 0 ? [contentHash] : [...refsFromMeta, contentHash];
|
||||
|
||||
const step = {
|
||||
role: next,
|
||||
contentHash,
|
||||
meta: extracted.meta,
|
||||
meta,
|
||||
refs,
|
||||
timestamp: Date.now(),
|
||||
} as RoleStep<M>;
|
||||
@@ -136,22 +110,22 @@ async function advanceOneRound<M extends RoleMeta>(
|
||||
contentHash: step.contentHash,
|
||||
meta: step.meta,
|
||||
refs: step.refs,
|
||||
childThread: agentResult.childThread,
|
||||
childThread: null,
|
||||
},
|
||||
step,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds pure role definitions + moderator table to runtime agents.
|
||||
* Binds pure role definitions + moderator table to an adapter.
|
||||
* Assign with `export const run = createWorkflow(def, binding)`.
|
||||
*
|
||||
* Structured meta extraction is delegated to {@link WorkflowRuntime.extract}, which the
|
||||
* engine resolves from the workflow registry's `extract` scene.
|
||||
* The adapter is responsible for returning typed meta directly — no separate
|
||||
* extract call is needed.
|
||||
*/
|
||||
export function createWorkflow<M extends RoleMeta>(
|
||||
def: Pick<WorkflowDefinition<M>, "roles" | "table">,
|
||||
binding: AgentBinding,
|
||||
binding: AdapterBinding,
|
||||
): WorkflowFn {
|
||||
const pickNext = tableToModerator(def.table);
|
||||
const loopDef = { roles: def.roles, pickNext };
|
||||
|
||||
@@ -2,6 +2,8 @@ export { buildThreadContext } from "./build-context.js";
|
||||
export { createWorkflow } from "./create-workflow.js";
|
||||
export { err, ok } from "./result.js";
|
||||
export type {
|
||||
AdapterBinding,
|
||||
AdapterFn,
|
||||
AgentBinding,
|
||||
AgentContext,
|
||||
AgentFn,
|
||||
@@ -17,6 +19,7 @@ export type {
|
||||
ModeratorTransition,
|
||||
Result,
|
||||
RoleDefinition,
|
||||
RoleFn,
|
||||
RoleMeta,
|
||||
RoleOutput,
|
||||
RoleStep,
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
// imports from "@uncaged/workflow-runtime" continues to work.
|
||||
|
||||
export type {
|
||||
AdapterBinding,
|
||||
AdapterFn,
|
||||
AdvanceOutcome,
|
||||
AgentBinding,
|
||||
AgentContext,
|
||||
@@ -21,6 +23,7 @@ export type {
|
||||
ResolvedModel,
|
||||
Result,
|
||||
RoleDefinition,
|
||||
RoleFn,
|
||||
RoleMeta,
|
||||
RoleOutput,
|
||||
RoleStep,
|
||||
|
||||
Reference in New Issue
Block a user