feat(hub): usage fact breakdown API + admin usage/session UI + release v0.0.34 (#10)

Expose UsageFact kind/capability rollups on org and project usage reports, and add admin pages that separate model tokens from external-capability meters.

Co-authored-by: Hong Jiarong <me@jrhim.com>
Co-committed-by: Hong Jiarong <me@jrhim.com>
This commit is contained in:
2026-07-19 01:19:59 +08:00
committed by 洪佳荣
parent ce18740870
commit eb0be43eac
11 changed files with 1047 additions and 134 deletions
+31 -1
View File
@@ -71,7 +71,7 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
occurredAt: new Date(startedAt.getTime() + i * 1000),
kind: f.kind ?? "model_completion",
provider: f.provider ?? "openrouter",
model: f.model ?? "mock-model",
model: f.model !== undefined ? f.model : "mock-model",
inputTokens: f.inputTokens ?? null,
outputTokens: f.outputTokens ?? null,
costUsd: f.costUsd ?? null,
@@ -158,6 +158,32 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
expect(report.totals.inputTokens).toBe(100);
expect(report.totals.outputTokens).toBe(50);
expect(report.totals.costUsd).toBeCloseTo(0.035, 8);
// ADR-0026 breakdown: model tokens vs external non-token meter must not collapse.
expect(report.breakdown).toHaveLength(2);
const modelBucket = report.breakdown.find((b) => b.kind === "model_completion");
const capBucket = report.breakdown.find((b) => b.kind === "external_capability");
expect(modelBucket).toMatchObject({
provider: "openrouter",
model: "mock-model",
capabilityId: null,
inputTokens: 100,
outputTokens: 50,
quantity: null,
costUsd: 0.02,
factCount: 1,
});
expect(capBucket).toMatchObject({
provider: "mineru",
model: null,
capabilityId: "pdf_to_md_bundle",
unit: "pages",
quantity: 12,
inputTokens: 0,
outputTokens: 0,
costUsd: 0.015,
factCount: 1,
});
});
it("a run with no facts at all is runsWithoutCost and contributes nothing", async () => {
@@ -180,6 +206,8 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
expect(row.projectId).toBe("proj-x");
expect(row.runCount).toBe(1);
expect(row.costUsd).toBeCloseTo(0.1, 8);
expect(row.breakdown).toHaveLength(1);
expect(row.breakdown[0]).toMatchObject({ kind: "model_completion", costUsd: 0.1, inputTokens: 1 });
});
it("returns an empty report for an org with no projects", async () => {
@@ -187,5 +215,7 @@ describe("usage rollup from UsageFact (ADR-0026)", () => {
expect(report.projects).toHaveLength(0);
expect(report.totals.runCount).toBe(0);
expect(report.totals.costUsd).toBeNull();
expect(report.breakdown).toEqual([]);
});
});