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:
@@ -10,23 +10,22 @@
|
||||
* ADR-0003-authorized tool (chat must match binding).
|
||||
* 6. On finish/fail/timeout: release the lock, post a status card.
|
||||
*
|
||||
* The agent provider/model come from the ModelRegistry (ADR-0017 role-based
|
||||
* The agent model id comes from the ModelRegistry (ADR-0017 role-based
|
||||
* routing, role-as-data). The trigger does not pick a model directly.
|
||||
*/
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
import type { FastifyBaseLogger } from "fastify";
|
||||
import { sendText, sendCard, buildRunStatusCard, type FeishuRuntime, type MessageReceiveEvent } from "./client.js";
|
||||
import type { AgentProvider } from "../agent/provider.js";
|
||||
import type { ToolRegistry } from "../agent/tools.js";
|
||||
import type { ModelRegistry } from "../agent/models.js";
|
||||
import { runAgent, type ProjectContext } from "../agent/runner.js";
|
||||
import { runAgent, type ModelFactory, type ProjectContext } from "../agent/runner.js";
|
||||
import { acquireLock, currentLockRunId, releaseLock } from "../lock.js";
|
||||
import { canTriggerAgent, canTriggerRole } from "../permission.js";
|
||||
import { transcriptPath } from "../agent/transcript.js";
|
||||
|
||||
interface TriggerDeps {
|
||||
readonly prisma: PrismaClient;
|
||||
readonly provider: AgentProvider;
|
||||
readonly modelFactory: ModelFactory;
|
||||
readonly tools: ToolRegistry;
|
||||
readonly models: ModelRegistry;
|
||||
readonly logger: FastifyBaseLogger;
|
||||
@@ -150,13 +149,11 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
}
|
||||
const role = deps.models.role(roleId);
|
||||
const model = deps.models.resolve(undefined, roleId);
|
||||
const providerId = deps.provider.id;
|
||||
const providerId = "openrouter";
|
||||
// Per-role bundle (ADR-0017): systemPrompt seeds persona; tools whitelist
|
||||
// is enforced by constructing a per-run registry subset. `tools: undefined`
|
||||
// means the full registered set (unrestricted role).
|
||||
// is enforced inside runAgent when it builds the SDK tools record. `tools:
|
||||
// undefined` means the full registered set (unrestricted role).
|
||||
const systemPrompt = role?.systemPrompt;
|
||||
const runTools =
|
||||
role?.tools !== undefined ? deps.tools.subset(role.tools) : deps.tools;
|
||||
|
||||
// ADR-0017: provider+model-bound session. Reuse if one exists for this
|
||||
// (project, provider, model) triple; otherwise create.
|
||||
@@ -220,11 +217,12 @@ export function makeTriggerHandler(deps: TriggerDeps) {
|
||||
|
||||
// Run the agent — fire-and-forget from the ws handler's perspective.
|
||||
// Per-run tools + systemPrompt come from the role bundle (ADR-0017).
|
||||
runAgent(deps.provider, runTools, {
|
||||
runAgent(deps.modelFactory, deps.tools, {
|
||||
prompt: cleanPrompt,
|
||||
model,
|
||||
project: projectCtx,
|
||||
systemPrompt,
|
||||
toolWhitelist: role?.tools,
|
||||
transcriptPath: transcriptPath(project.workspaceDir, session.id),
|
||||
})
|
||||
.then(async (result) => {
|
||||
|
||||
Reference in New Issue
Block a user