forked from EduCraft/curriculum-project-hub
refactor(hub): 手搓 agent loop 换成 Vercel AI SDK
将 provider/runner/tools 三件手搓轮子替换为 AI SDK v7 的 generateText + tool:
- 删 openrouter-provider.ts(翻译层消失)、provider.ts 自定义消息类型
- runner.ts 循环体改为 generateText({stopWhen: stepCountIs}),保留 RunRequest/RunResult 公共签名
- tools.ts ToolRegistry 改为 ToolFactory + build(ctx, names?) 按 role 白名单构建 tools record
- workspace/cph/feishu 工具改写为 tool() 定义,execute 闭包捕获 per-run ToolContext
- transcript.ts 改为 ModelMessage[] JSONL(0.0.0 cutover,无迁移)
- server.ts/trigger.ts 接 modelFactory + toolWhitelist
- 测试:helpers 写 MockLanguageModel implements LanguageModelV4;5 个测试改写 + 新增 runner.test.ts
- 移除未使用的 openai 依赖
- ADR-0017 Consequences 同步:loop 委托给 AI SDK,provider seam 是 LanguageModel
tsc 干净,54 tests 全过。
This commit is contained in:
+12
-12
@@ -2,8 +2,8 @@
|
||||
* Hub entry — Fastify server + Feishu WebSocket listener.
|
||||
*
|
||||
* Wires the full trigger path: lark ws receives `im.message.receive_v1` →
|
||||
* trigger handler resolves chat→project (ADR-0001), creates AgentRun + acquires
|
||||
* the project lock (ADR-0002), runs the provider-agnostic agent loop
|
||||
* trigger handler resolves chat->project (ADR-0001), creates AgentRun + acquires
|
||||
* the project lock (ADR-0002), runs the AI SDK-backed agent runner
|
||||
* (ADR-0017), and releases the lock on finish. Feishu context reads inside the
|
||||
* loop are ADR-0003-authorized (chat must match binding).
|
||||
*
|
||||
@@ -13,7 +13,7 @@
|
||||
import "dotenv/config";
|
||||
import Fastify from "fastify";
|
||||
import { prisma } from "./db.js";
|
||||
import { OpenRouterProvider } from "./agent/openrouter-provider.js";
|
||||
import { createModelFactory } from "./agent/provider.js";
|
||||
import { InMemoryModelRegistry } from "./agent/models.js";
|
||||
import { ToolRegistry, feishuContextTool } from "./agent/tools.js";
|
||||
import { readFileTool, writeFileTool, listFilesTool } from "./agent/workspace.js";
|
||||
@@ -34,7 +34,7 @@ function requireEnv(name: string): string {
|
||||
async function main(): Promise<void> {
|
||||
const databaseUrl = requireEnv("DATABASE_URL");
|
||||
void databaseUrl; // prisma reads DATABASE_URL from env directly
|
||||
const openrouterKey = requireEnv("OPENROUTER_API_KEY");
|
||||
requireEnv("OPENROUTER_API_KEY");
|
||||
const feishuAppId = requireEnv("FEISHU_APP_ID");
|
||||
const feishuAppSecret = requireEnv("FEISHU_APP_SECRET");
|
||||
const feishuBotOpenId = requireEnv("FEISHU_BOT_OPEN_ID");
|
||||
@@ -45,7 +45,7 @@ async function main(): Promise<void> {
|
||||
app.get("/api/healthz", async () => ({ ok: true, ts: Date.now() }));
|
||||
|
||||
// --- Agent seam wiring ---
|
||||
const provider = new OpenRouterProvider({ apiKey: openrouterKey });
|
||||
const modelFactory = createModelFactory();
|
||||
const models = new InMemoryModelRegistry(
|
||||
[
|
||||
{ id: "z-ai/glm-4.6", label: "GLM-4.6", toolCapable: true },
|
||||
@@ -79,17 +79,17 @@ async function main(): Promise<void> {
|
||||
const tools = new ToolRegistry();
|
||||
// ADR-0003: the handler enforces chat==boundChat before any API call;
|
||||
// real read wraps @larksuiteoapi/node-sdk's im.v1.message get/list.
|
||||
tools.register(feishuContextTool(readFeishuContext, feishuRt));
|
||||
tools.register("feishu_read_context", feishuContextTool(readFeishuContext, feishuRt));
|
||||
// Workspace file tools (ADR-0007 directory tree, path-confined).
|
||||
tools.register(readFileTool());
|
||||
tools.register(writeFileTool());
|
||||
tools.register(listFilesTool());
|
||||
tools.register("read_file", readFileTool);
|
||||
tools.register("write_file", writeFileTool);
|
||||
tools.register("list_files", listFilesTool);
|
||||
// cph CLI tools (ADR-0016 version contract enforced by cph itself).
|
||||
tools.register(cphCheckTool());
|
||||
tools.register(cphBuildTool());
|
||||
tools.register("cph_check", cphCheckTool);
|
||||
tools.register("cph_build", cphBuildTool);
|
||||
|
||||
// --- Feishu listener (reuses the same lark client) ---
|
||||
const trigger = makeTriggerHandler({ prisma, provider, tools, models, logger: app.log });
|
||||
const trigger = makeTriggerHandler({ prisma, modelFactory, tools, models, logger: app.log });
|
||||
startFeishuListenerWithClient(feishuConfig, larkClient, app.log, trigger);
|
||||
|
||||
app.listen({ port, host: "0.0.0.0" }, (err, address) => {
|
||||
|
||||
Reference in New Issue
Block a user