forked from bai/curriculum-project-hub
a2c8fa8eaf
将 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 全过。
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import { mkdtemp, rm, readFile, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { readTranscript, appendTranscript, transcriptPath, TRANSCRIPT_DIR } from "../../src/agent/transcript.js";
|
|
import type { ModelMessage } from "ai";
|
|
|
|
const sample: ModelMessage = { role: "user", content: "hello" };
|
|
const reply: ModelMessage = { role: "assistant", content: "hi" };
|
|
|
|
describe("transcript JSONL", () => {
|
|
let dir: string;
|
|
let path: string;
|
|
|
|
beforeEach(async () => {
|
|
dir = await mkdtemp(join(tmpdir(), "hub-transcript-"));
|
|
path = join(dir, "session.jsonl");
|
|
});
|
|
afterEach(async () => {
|
|
await rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("readTranscript returns empty array for nonexistent file", async () => {
|
|
expect(await readTranscript(path)).toEqual([]);
|
|
});
|
|
|
|
it("appendTranscript creates parent dirs and writes JSONL", async () => {
|
|
await appendTranscript(path, [sample]);
|
|
const content = await readFile(path, "utf8");
|
|
expect(content.trim()).toBe(JSON.stringify(sample));
|
|
});
|
|
|
|
it("readTranscript reads back appended messages", async () => {
|
|
await appendTranscript(path, [sample, reply]);
|
|
const messages = await readTranscript(path);
|
|
expect(messages).toHaveLength(2);
|
|
expect(messages[0]).toEqual(sample);
|
|
expect(messages[1]).toEqual(reply);
|
|
});
|
|
|
|
it("multiple appends accumulate", async () => {
|
|
await appendTranscript(path, [sample]);
|
|
await appendTranscript(path, [reply]);
|
|
const messages = await readTranscript(path);
|
|
expect(messages).toHaveLength(2);
|
|
});
|
|
|
|
it("skips corrupt lines (partial write recovery)", async () => {
|
|
await appendTranscript(path, [sample]);
|
|
// Append a corrupt line (simulating a crash mid-write).
|
|
await writeFile(path, '{"broken json\n', { flag: "a" });
|
|
const messages = await readTranscript(path);
|
|
expect(messages).toHaveLength(1); // only the valid line
|
|
});
|
|
|
|
it("transcriptPath builds the expected path", () => {
|
|
const p = transcriptPath("/ws", "sess-1");
|
|
expect(p).toBe(join("/ws", TRANSCRIPT_DIR, "sess-1.jsonl"));
|
|
});
|
|
});
|