forked from EduCraft/curriculum-project-hub
feat: add agent cost reporting
This commit is contained in:
@@ -73,6 +73,7 @@ function createMockRunAgent(calls: RunRequest[] = []): TestRunner {
|
||||
status: "completed",
|
||||
text: "mock response",
|
||||
usage: { inputTokens: 10, outputTokens: 5 },
|
||||
costUsd: 0.0023,
|
||||
numTurns: 1,
|
||||
sdkSessionId: "sdk-session-1",
|
||||
};
|
||||
@@ -128,7 +129,7 @@ describe("trigger full lifecycle (integration)", () => {
|
||||
msgType: "interactive",
|
||||
replyInThread: undefined,
|
||||
});
|
||||
expect(rt.sentTexts).toContain("mock response");
|
||||
expect(rt.sentTexts.some((text) => text.includes("mock response") && text.includes("本次成本: $0.0023"))).toBe(true);
|
||||
expect(runAgentCalls).toHaveLength(1);
|
||||
expect(runAgentCalls[0]?.providerEnv).toMatchObject({
|
||||
ANTHROPIC_BASE_URL: "https://openrouter.ai/api",
|
||||
@@ -136,6 +137,9 @@ describe("trigger full lifecycle (integration)", () => {
|
||||
ANTHROPIC_API_KEY: "",
|
||||
});
|
||||
expect(runAgentCalls[0]?.maxTurns).toBe(7);
|
||||
const run = await prisma.agentRun.findFirstOrThrow();
|
||||
expect(Number(run.costUsd)).toBeCloseTo(0.0023);
|
||||
expect(run.costSource).toBe("provider_reported");
|
||||
});
|
||||
|
||||
it("batches quick text messages from the same chat and sender into one run", async () => {
|
||||
@@ -228,6 +232,65 @@ describe("trigger full lifecycle (integration)", () => {
|
||||
expect(await prisma.agentRun.findMany()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("/cost reports recorded current-session cost without creating a run", async () => {
|
||||
await seedProject("proj-cost", "chat-cost");
|
||||
const trigger = makeTriggerHandler({ prisma, settings, logger: silentLogger, runAgent, messageBatcherOptions: { maxMessages: 1 } });
|
||||
|
||||
await trigger(makeEvent("chat-cost", "@_user_1 写教案"), rt);
|
||||
await vi.waitFor(async () => {
|
||||
const runs = await prisma.agentRun.findMany();
|
||||
expect(runs).toHaveLength(1);
|
||||
expect(runs[0]?.status).toBe("COMPLETED");
|
||||
});
|
||||
|
||||
await trigger(makeEvent("chat-cost", "@_user_1 /cost"), rt);
|
||||
|
||||
const costText = rt.sentTexts.at(-1) ?? "";
|
||||
expect(costText).toContain("当前会话已记录 agent 成本");
|
||||
expect(costText).toContain("总计: $0.0023");
|
||||
expect(costText).toContain("Runs: 1 已记录");
|
||||
expect(costText).toContain("openrouter / mock-model");
|
||||
expect(runAgentCalls).toHaveLength(1);
|
||||
expect(await prisma.agentRun.findMany()).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("/cost surfaces finished runs without recorded cost", async () => {
|
||||
await seedProject("proj-cost-missing", "chat-cost-missing");
|
||||
const session = await prisma.agentSession.create({
|
||||
data: {
|
||||
projectId: "proj-cost-missing",
|
||||
provider: "openrouter",
|
||||
roleId: "draft",
|
||||
model: "mock-model",
|
||||
metadata: {},
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
await prisma.agentRun.create({
|
||||
data: {
|
||||
projectId: "proj-cost-missing",
|
||||
sessionId: session.id,
|
||||
entrypoint: "FEISHU",
|
||||
status: "COMPLETED",
|
||||
prompt: "old test run",
|
||||
model: "mock-model",
|
||||
provider: "openrouter",
|
||||
inputTokens: 10,
|
||||
outputTokens: 5,
|
||||
metadata: {},
|
||||
finishedAt: new Date(),
|
||||
},
|
||||
});
|
||||
const trigger = makeTriggerHandler({ prisma, settings, logger: silentLogger, runAgent, messageBatcherOptions: { maxMessages: 1 } });
|
||||
|
||||
await trigger(makeEvent("chat-cost-missing", "@_user_1 /cost"), rt);
|
||||
|
||||
const costText = rt.sentTexts.at(-1) ?? "";
|
||||
expect(costText).toContain("还没有任何 run 记录到真实成本");
|
||||
expect(costText).toContain("未记录成本: 1 runs");
|
||||
expect(runAgentCalls).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("/help is built from the current registry on each request", async () => {
|
||||
await seedProject("proj-help-live", "chat-help-live");
|
||||
let currentModels = new InMemoryModelRegistry(
|
||||
|
||||
Reference in New Issue
Block a user