forked from EduCraft/curriculum-project-hub
feat(hub): provider-agnostic agent seam 骨架(ADR-0001/0003/0017)
hub/ TS 新部件,平级于 spec/。本仓从零 TS 重写(非迁移旧服务),栈: Fastify+Prisma 待补;本提交落 provider-agnostic agent runtime 骨架: - provider.ts: AgentProvider seam,OpenAI-compatible messages+tools+tool_calls 形状(语言通解,非 OpenAI 承诺);@Claude 仅为触发品牌(ADR-0017) - openrouter-provider.ts: openai SDK 指向 OpenRouter 的具体适配器,仅做 Message↔SDK 翻译;agent loop 不 import SDK,换 provider 不改 loop - runner.ts: 自有 agent loop(dispatch tools/循环到 stop/budget),per-run model(ADR-0017 role-based routing,run 带其 model) - tools.ts: 窄工具面 + ADR-0003 McpReadRequest.Authorized 落代码: feishu_read_context 拒绝非本项目绑定 chat 的读取(实测错 chat 抛 UnauthorizedChatRead,对 chat 放行) - models.ts: ModelRegistry seam(role→default 映射策略 OPEN) - server.ts: 接线入口(stub Feishu read,无活凭证即可编译) tsc --noEmit rc=0;ADR-0003 不变式 smoke 通过。
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Hub entry — skeleton.
|
||||
*
|
||||
* Wires the provider-agnostic agent seam end-to-end with a no-op Feishu read, so
|
||||
* the invariant path is exercised without live credentials. Real wiring (Fastify
|
||||
* server, Feishu OAuth/event callbacks, Prisma, admin-web) lands incrementally;
|
||||
* this file proves the seam compiles and the pieces connect.
|
||||
*/
|
||||
import { OpenRouterProvider } from "./agent/openrouter-provider.js";
|
||||
import { InMemoryModelRegistry } from "./agent/models.js";
|
||||
import { ToolRegistry, feishuContextTool } from "./agent/tools.js";
|
||||
import { runAgent } from "./agent/runner.js";
|
||||
|
||||
function main(): void {
|
||||
const apiKey = process.env["OPENROUTER_API_KEY"];
|
||||
if (apiKey === undefined || apiKey === "") {
|
||||
console.error("[hub] OPENROUTER_API_KEY not set; nothing to run. Exiting.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const provider = new OpenRouterProvider({ apiKey });
|
||||
const models = new InMemoryModelRegistry(
|
||||
[
|
||||
{ id: "z-ai/glm-4.6", label: "GLM-4.6", toolCapable: true },
|
||||
{ id: "anthropic/claude-3.5-sonnet", label: "Claude 3.5 Sonnet", toolCapable: true },
|
||||
],
|
||||
{ draft: "z-ai/glm-4.6", review: "anthropic/claude-3.5-sonnet" },
|
||||
);
|
||||
|
||||
const tools = new ToolRegistry();
|
||||
tools.register(
|
||||
feishuContextTool(async (args) => {
|
||||
// Skeleton: no live Feishu calls. Real impl wraps @larksuiteoapi/node-sdk.
|
||||
return JSON.stringify({ stub: true, anchor: args.anchor, id: args.id });
|
||||
}),
|
||||
);
|
||||
|
||||
const model = models.resolve(undefined, "draft");
|
||||
|
||||
runAgent(provider, tools, {
|
||||
prompt: "ping",
|
||||
model,
|
||||
role: "draft",
|
||||
project: {
|
||||
projectId: "proj-skeleton",
|
||||
boundChatId: "chat-skeleton",
|
||||
workspaceDir: "/tmp/cph-skeleton",
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
console.log("[hub] run finished", { status: r.status, model, usage: r.usage });
|
||||
if (r.error !== undefined) console.error("[hub] error:", r.error);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("[hub] run threw:", e);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user