小橘 🍊(NEKO Team) 56ce22fb1b Migrate workflows to WorkflowSpec-style roles (RFC-003)
Replace createCursorRole/createHermesRole with adapter + prompt + zod meta.

Add shared compileRoleSpec, cursor ask adapter, nerve.yaml extract defaults.

Refs #248

Made-with: Cursor
2026-04-29 09:23:55 +00:00

53 lines
1.5 KiB
TypeScript

import type {
Role,
RoleSpec,
StartStep,
WorkflowContext,
WorkflowMessage,
} from "@uncaged/nerve-core";
import type { LlmProvider } from "@uncaged/nerve-workflow-utils";
import { extractMetaOrThrow, type ZodMetaSchema } from "@uncaged/nerve-workflow-utils";
import type { z } from "zod";
export function zodMeta<T>(zod: z.ZodType<T>): ZodMetaSchema<T> {
return { witness: null, zod };
}
export type CompileRoleSpecDeps = {
provider: LlmProvider;
createContext: (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext;
};
/**
* RFC-003 RoleSpec → runtime Role (extract uses global nerve.yaml provider via `provider`;
* dryRun follows each invocation's start frame).
*/
export function compileRoleSpec<M extends Record<string, unknown>>(
spec: RoleSpec<M>,
deps: CompileRoleSpecDeps,
): Role<M> {
return async (start, messages) => {
const ctx = deps.createContext(start, messages);
const promptText =
typeof spec.prompt === "string" ? spec.prompt : await spec.prompt(start, messages);
const raw = await spec.adapter(promptText, ctx);
const zod = (spec.meta as ZodMetaSchema<M>).zod;
const meta = await extractMetaOrThrow(raw, zod, {
provider: deps.provider,
dryRun: start.meta.dryRun,
});
return { content: raw, meta };
};
}
export function defaultAgentCreateContext(
workdir: string,
): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext {
return (start, messages) => ({
start,
messages,
workdir,
signal: new AbortController().signal,
});
}