Phase 2: Migrate LLM/Hermes/Cursor adapters to createAgentAdapter #261

Closed
opened 2026-05-14 10:15:40 +00:00 by xiaoju · 0 comments
Owner

目标

将 LLM / Hermes / Cursor adapter 从 createTextAdapter(TextProducerFn) 迁移到 createAgentAdapter<Opt>(AgentFn<Opt>, ExtractOptionsFn<Opt>)

React adapter 不动 — 它走 reactor 模式,不是 text producer。

迁移细节

LLM adapter (workflow-agent-llm)

// 当前
export function createLlmAdapter(provider: LlmProvider): AdapterFn {
  return createTextAdapter(async (ctx, prompt, _runtime) => {
    // ... chatCompletionText with prompt as system message
  });
}

// 迁移后
type LlmAgentOpt = { prompt: string };

function createLlmAgent(provider: LlmProvider): AgentFn<LlmAgentOpt> {
  return async (ctx, { prompt }) => {
    const result = await chatCompletionText({
      provider,
      messages: [
        { role: "system", content: prompt },
        { role: "user", content: ctx.start.content },
      ],
    });
    if (!result.ok) throw new Error(`llm: ${formatLlmChatError(result.error)}`);
    return result.value;
  };
}

export function createLlmAdapter(provider: LlmProvider): AdapterFn {
  return createAgentAdapter(
    createLlmAgent(provider),
    async (_ctx, prompt) => ({ prompt }),
  );
}

公开 API 签名 createLlmAdapter(provider): AdapterFn 不变。chatCompletionText 和相关类型不变。

Hermes adapter (workflow-agent-hermes)

// 迁移后
type HermesAgentOpt = { prompt: string };

function createHermesAgentFn(config: HermesAgentConfig): AgentFn<HermesAgentOpt> {
  return async (ctx, { prompt }) => {
    // ... validate, buildThreadInput, spawnCli
  };
}

export function createHermesAgent(config: HermesAgentConfig): AdapterFn {
  return createAgentAdapter(
    createHermesAgentFn(config),
    async (_ctx, prompt) => ({ prompt }),
  );
}

Cursor adapter (workflow-agent-cursor)

// 迁移后
type CursorAgentOpt = { prompt: string; workspace: string };

function createCursorAgentFn(config: CursorAgentConfig): AgentFn<CursorAgentOpt> {
  return async (ctx, { prompt, workspace }) => {
    // ... buildThreadInput, spawnCli with workspace
    // 不再需要 extractWorkspacePath
  };
}

export function createCursorAgent(config: CursorAgentConfig): AdapterFn {
  return createAgentAdapter(
    createCursorAgentFn(config),
    async (ctx, prompt, runtime) => {
      const workspace = config.workspace ?? await extractWorkspacePath(ctx, runtime, logger);
      if (workspace === null) throw new Error("cursor-agent: failed to extract workspace path");
      return { prompt, workspace };
    },
  );
}

Cursor 的 extractWorkspacePath 逻辑移入 extract 函数,AgentFn 本身不再关心 workspace 怎么来的。

不动的部分

  • createTextAdapter / TextProducerFn 保留(其他消费者可能还在用)
  • React adapter 不动
  • workflowAdapter 不动(直接实现 AdapterFn,不走 agent 模式)
  • 所有公开 API 签名不变

完成标准

  • 三个 adapter 迁移完成
  • bun run build 通过
  • bun run test 通过
  • biome check 对改动文件无新增 error
## 目标 将 LLM / Hermes / Cursor adapter 从 `createTextAdapter(TextProducerFn)` 迁移到 `createAgentAdapter<Opt>(AgentFn<Opt>, ExtractOptionsFn<Opt>)`。 React adapter 不动 — 它走 reactor 模式,不是 text producer。 ## 迁移细节 ### LLM adapter (`workflow-agent-llm`) ```typescript // 当前 export function createLlmAdapter(provider: LlmProvider): AdapterFn { return createTextAdapter(async (ctx, prompt, _runtime) => { // ... chatCompletionText with prompt as system message }); } // 迁移后 type LlmAgentOpt = { prompt: string }; function createLlmAgent(provider: LlmProvider): AgentFn<LlmAgentOpt> { return async (ctx, { prompt }) => { const result = await chatCompletionText({ provider, messages: [ { role: "system", content: prompt }, { role: "user", content: ctx.start.content }, ], }); if (!result.ok) throw new Error(`llm: ${formatLlmChatError(result.error)}`); return result.value; }; } export function createLlmAdapter(provider: LlmProvider): AdapterFn { return createAgentAdapter( createLlmAgent(provider), async (_ctx, prompt) => ({ prompt }), ); } ``` 公开 API 签名 `createLlmAdapter(provider): AdapterFn` 不变。`chatCompletionText` 和相关类型不变。 ### Hermes adapter (`workflow-agent-hermes`) ```typescript // 迁移后 type HermesAgentOpt = { prompt: string }; function createHermesAgentFn(config: HermesAgentConfig): AgentFn<HermesAgentOpt> { return async (ctx, { prompt }) => { // ... validate, buildThreadInput, spawnCli }; } export function createHermesAgent(config: HermesAgentConfig): AdapterFn { return createAgentAdapter( createHermesAgentFn(config), async (_ctx, prompt) => ({ prompt }), ); } ``` ### Cursor adapter (`workflow-agent-cursor`) ```typescript // 迁移后 type CursorAgentOpt = { prompt: string; workspace: string }; function createCursorAgentFn(config: CursorAgentConfig): AgentFn<CursorAgentOpt> { return async (ctx, { prompt, workspace }) => { // ... buildThreadInput, spawnCli with workspace // 不再需要 extractWorkspacePath }; } export function createCursorAgent(config: CursorAgentConfig): AdapterFn { return createAgentAdapter( createCursorAgentFn(config), async (ctx, prompt, runtime) => { const workspace = config.workspace ?? await extractWorkspacePath(ctx, runtime, logger); if (workspace === null) throw new Error("cursor-agent: failed to extract workspace path"); return { prompt, workspace }; }, ); } ``` Cursor 的 `extractWorkspacePath` 逻辑移入 extract 函数,AgentFn 本身不再关心 workspace 怎么来的。 ### 不动的部分 - `createTextAdapter` / `TextProducerFn` 保留(其他消费者可能还在用) - React adapter 不动 - `workflowAdapter` 不动(直接实现 AdapterFn,不走 agent 模式) - 所有公开 API 签名不变 ## 完成标准 - [ ] 三个 adapter 迁移完成 - [ ] `bun run build` 通过 - [ ] `bun run test` 通过 - [ ] biome check 对改动文件无新增 error
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: uncaged/workflow#261