refactor: extractPrompt out of ExtractContext, into ExtractFn parameter

ExtractFn = (schema, prompt, ctx) => Promise<T>
extractPrompt stays in RoleDefinition (definition layer), not in context (state layer).
Callers pass their own prompt — engine uses roleDef.extractPrompt, cursor agent uses its own.

小橘 <xiaoju@shazhou.work>
This commit is contained in:
2026-05-07 01:27:12 +00:00
parent d472de1247
commit 43e1f82303
5 changed files with 16 additions and 9 deletions
@@ -5,6 +5,7 @@ import { createCursorAgent, validateCursorAgentConfig } from "../src/index.js";
const testExtract: ExtractFn = async <T extends Record<string, unknown>>(
_schema: z.ZodType<T>,
_prompt: string,
_ctx: ExtractContext,
): Promise<T> => ({ workspace: "/tmp" }) as unknown as T;
+5 -3
View File
@@ -48,10 +48,12 @@ export function createCursorAgent(config: CursorAgentConfig): AgentFn {
const extractCtx: ExtractContext = {
...ctx,
agentContent: "",
extractPrompt:
"From the thread context, determine the absolute filesystem path where the project/repository is located.",
};
const { workspace } = await config.extract(cursorWorkspaceSchema, extractCtx);
const { workspace } = await config.extract(
cursorWorkspaceSchema,
"From the thread context, determine the absolute filesystem path where the project/repository is located.",
extractCtx,
);
const fullPrompt = buildAgentPrompt(ctx);
const args = [
"-p",
+5 -2
View File
@@ -88,10 +88,13 @@ export function createWorkflow<M extends RoleMeta>(
const extractCtx: ExtractContext<M> = {
...agentCtx,
agentContent: raw,
extractPrompt: roleDef.extractPrompt,
};
const meta = await extract(roleDef.schema, extractCtx as unknown as ExtractContext);
const meta = await extract(
roleDef.schema,
roleDef.extractPrompt,
extractCtx as unknown as ExtractContext,
);
const ts = Date.now();
const step = {
+4 -2
View File
@@ -5,16 +5,18 @@ import type { ExtractContext, LlmProvider } from "./types.js";
export type ExtractFn = <T extends Record<string, unknown>>(
schema: z.ZodType<T>,
prompt: string,
ctx: ExtractContext,
) => Promise<T>;
/**
* Create an ExtractFn backed by an LLM provider.
* Builds prompt text from {@link ExtractContext} and calls structured extraction.
* Builds prompt text from {@link ExtractContext} plus `prompt` and calls structured extraction.
*/
export function createExtract(provider: LlmProvider): ExtractFn {
return async <T extends Record<string, unknown>>(
schema: z.ZodType<T>,
prompt: string,
ctx: ExtractContext,
): Promise<T> => {
const lines: string[] = [];
@@ -37,7 +39,7 @@ export function createExtract(provider: LlmProvider): ExtractFn {
lines.push(ctx.agentContent);
lines.push("");
lines.push("## Extraction Instruction");
lines.push(ctx.extractPrompt);
lines.push(prompt);
const text = lines.join("\n");
const result = await llmExtractWithRetry({ text, schema, provider });
+1 -2
View File
@@ -73,10 +73,9 @@ export type AgentContext<M extends RoleMeta = RoleMeta> = ModeratorContext<M> &
};
};
/** Phase 3: Extractor runs — has agent output and extract instruction. */
/** Phase 3: Extractor runs — has agent output; the extraction instruction is a separate argument to the extract function. */
export type ExtractContext<M extends RoleMeta = RoleMeta> = AgentContext<M> & {
agentContent: string;
extractPrompt: string;
};
/** Alias — most external consumers see the agent-phase context. */