refactor(workflow): unify extraction behind ExtractFn

Route createExtract through reactExtract with plain-JSON correction retry.

Remove WorkflowFnOptions.llmProvider, ExtractMode, RoleDefinition.extractMode, ResolveRoleMetaFn.

Runtime createWorkflow calls options.extract directly; engine passes extract only.

Update templates, CLI skill docs, and tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Scott Wei
2026-05-08 15:35:12 +08:00
parent 44fb0694aa
commit 34f5e655d1
25 changed files with 66 additions and 271 deletions
@@ -1,3 +1,5 @@
import type * as z from "zod/v4";
import type { CasStore } from "../cas/types.js";
import {
type AgentBinding,
@@ -6,7 +8,6 @@ import {
END,
type ExtractContext,
type ModeratorContext,
type ResolveRoleMetaFn,
type RoleDefinition,
type RoleMeta,
type RoleOutput,
@@ -55,7 +56,6 @@ type AdvanceOutcome<M extends RoleMeta> =
async function advanceOneRound<M extends RoleMeta>(
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
binding: AgentBinding,
resolveRoleMeta: ResolveRoleMetaFn<M>,
params: {
start: ModeratorContext<M>["start"];
steps: RoleStep<M>[];
@@ -97,10 +97,10 @@ async function advanceOneRound<M extends RoleMeta>(
agentContent: raw,
};
const meta = await resolveRoleMeta(
roleDef as unknown as RoleDefinition<Record<string, unknown>>,
extractCtx,
options,
const meta = await options.extract(
roleDef.schema as unknown as z.ZodType<Record<string, unknown>>,
roleDef.extractPrompt,
extractCtx as unknown as ExtractContext,
);
const contentHash = await putContentBlob(options.cas, raw);
@@ -131,13 +131,14 @@ async function advanceOneRound<M extends RoleMeta>(
/**
* Binds pure role definitions + moderator to runtime agents.
* Assign with `export const run = createWorkflow(def, binding)` via `@uncaged/workflow-runtime`,
* which supplies {@link ResolveRoleMetaFn}.
* Assign with `export const run = createWorkflow(def, binding)`.
*
* Structured meta extraction is delegated to {@link WorkflowFnOptions.extract}, which the
* engine resolves from the workflow registry's `extract` scene.
*/
export function createWorkflow<M extends RoleMeta>(
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
binding: AgentBinding,
resolveRoleMeta: ResolveRoleMetaFn<M>,
): WorkflowFn {
return async function* workflowLoop(
input: ThreadInput,
@@ -168,7 +169,7 @@ export function createWorkflow<M extends RoleMeta>(
};
}
const outcome = await advanceOneRound(def, binding, resolveRoleMeta, {
const outcome = await advanceOneRound(def, binding, {
start,
steps,
options,
-2
View File
@@ -12,11 +12,9 @@ export type {
AgentContext,
AgentFn,
ExtractContext,
ExtractMode,
LlmProvider,
Moderator,
ModeratorContext,
ResolveRoleMetaFn,
RoleDefinition,
RoleMeta,
RoleOutput,
-13
View File
@@ -17,9 +17,6 @@ export type LlmProvider = {
model: string;
};
/** How the engine runs meta extraction for a role after the agent phase. */
export type ExtractMode = "single" | "react";
/** What each generator yield produces — one role's output (engine adds `timestamp` when persisting). */
export type RoleOutput = {
role: string;
@@ -57,8 +54,6 @@ export type WorkflowFnOptions = {
cas: CasStore;
/** Structured meta extraction; resolved from workflow.yaml `extract` scene by the engine. */
extract: ExtractFn;
/** Provider for `extractMode: "react"` roles; same backing config as `extract`. */
llmProvider: LlmProvider | null;
};
/** Bundle contract — named export `run` is a function returning an AsyncGenerator. */
@@ -129,7 +124,6 @@ export type RoleDefinition<Meta extends Record<string, unknown>> = {
schema: z.ZodType<Meta>;
/** When non-null, produces CAS hashes to persist on this role's steps (see `RoleOutput.refs`). */
extractRefs: ((meta: Meta) => string[]) | null;
extractMode: ExtractMode;
};
/**
@@ -148,10 +142,3 @@ export type WorkflowDefinition<M extends RoleMeta> = {
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
moderator: Moderator<M>;
};
/** Engine-injected meta extraction for workflow loops (single + react modes). */
export type ResolveRoleMetaFn<M extends RoleMeta = RoleMeta> = (
roleDef: RoleDefinition<Record<string, unknown>>,
extractCtx: ExtractContext<M>,
options: WorkflowFnOptions,
) => Promise<Record<string, unknown>>;