feat(cross-cutting): centralized model compatibility handling with catalog resolvers

- Added catalog-level host/model predicates and compat resolvers.
- Extended compatibility types and model schema with timeout and replay flags.
- Replaced provider-specific heuristics with shared resolver-based checks.
- Updated host/identity and resolver tests to validate the new behavior.
This commit is contained in:
can1357
2026-06-10 04:30:19 +02:00
parent 7572e4f04c
commit e14a63da6d
33 changed files with 740 additions and 396 deletions
+41 -27
View File
@@ -45,8 +45,8 @@ function makeReport(provider: string, email: string, limits: UsageReport["limits
describe("buildRedactionMap", () => {
it("masks everything past a two-char anchor when the anchor is unique", () => {
const map = buildRedactionMap(["alpha@example.test", "bravo@example.test"]);
expect(map.get("alpha@example.test")).toBe("an*");
expect(map.get("bravo@example.test")).toBe("ha*");
expect(map.get("alpha@example.test")).toBe("al*");
expect(map.get("bravo@example.test")).toBe("br*");
});
it("reveals a minimal middle-out differentiator instead of growing the prefix", () => {
@@ -56,32 +56,32 @@ describe("buildRedactionMap", () => {
// Masks must be pairwise distinct so accounts stay tellable-apart.
expect(new Set(masks).size).toBe(masks.length);
for (const mask of masks) {
// Never leak the local part the way prefix growth would ("can.boluk@*").
expect(mask).not.toContain("boluk");
// Never leak the whole local part the way prefix growth would ("dummy@*").
expect(mask).not.toContain("dummy");
// anchor + at most a two-char differentiator.
expect(mask).toMatch(/^ca\*(.{1,2}\*)?$/);
expect(mask).toMatch(/^du\*(.{1,2}\*)?$/);
}
// The "89" account is distinguished by a digit only it contains.
expect(map.get("dum.my9@example.net")).toBe("ca*9*");
expect(map.get("dum.my9@example.net")).toBe("du*9*");
});
it("gives duplicate identities the same mask", () => {
const map = buildRedactionMap(["user@example.test", "user@example.test"]);
expect(map.size).toBe(1);
expect(map.get("user@example.test")).toBe("me*");
expect(map.get("user@example.test")).toBe("us*");
});
});
describe("computeProviderWindowStats", () => {
it("buckets by window duration, binds each account to its worst meter, and ceils the need", () => {
it("buckets by window duration, binds each account to its worst meter, and reports remaining capacity", () => {
const reports = [
makeReport("anthropic", "a@x", [
makeReport("anthropic", "account-a@example.test", [
makeLimit({ id: "5h", usedFraction: 0.9, durationMs: FIVE_HOURS, windowId: "5h" }),
makeLimit({ id: "7d", usedFraction: 0.1, durationMs: SEVEN_DAYS, windowId: "7d" }),
// Tiered meter on the same window: higher burn must bind.
makeLimit({ id: "7d-opus", usedFraction: 0.4, durationMs: SEVEN_DAYS, windowId: "7d", tier: "opus" }),
]),
makeReport("anthropic", "b@x", [
makeReport("anthropic", "account-b@example.test", [
makeLimit({ id: "5h", usedFraction: 0.4, durationMs: FIVE_HOURS, windowId: "5h" }),
makeLimit({ id: "7d", usedFraction: 0.2, durationMs: SEVEN_DAYS, windowId: "7d" }),
]),
@@ -93,15 +93,15 @@ describe("computeProviderWindowStats", () => {
expect(fiveHour.window).toBe("5h");
expect(fiveHour.accounts).toBe(2);
expect(fiveHour.usedAccounts).toBeCloseTo(1.3);
expect(fiveHour.needed).toBe(2);
expect(fiveHour.remainingAccounts).toBeCloseTo(0.7);
expect(sevenDay.window).toBe("7d");
expect(sevenDay.usedAccounts).toBeCloseTo(0.6); // 0.4 (opus binds) + 0.2
expect(sevenDay.needed).toBe(1);
expect(sevenDay.remainingAccounts).toBeCloseTo(1.4);
});
it("ignores limits without a resolvable fraction", () => {
const reports = [
makeReport("anthropic", "a@x", [
makeReport("anthropic", "account-a@example.test", [
{
id: "mystery",
label: "mystery",
@@ -116,23 +116,23 @@ describe("computeProviderWindowStats", () => {
describe("collectUnreportedAccounts", () => {
const accounts: UsageAccountIdentity[] = [
{ provider: "anthropic", type: "oauth", email: "seen@x.com" },
{ provider: "anthropic", type: "oauth", email: "missing@x.com" },
{ provider: "anthropic", type: "oauth", email: "seen@example.test" },
{ provider: "anthropic", type: "oauth", email: "missing@example.test" },
{ provider: "anthropic", type: "api_key" },
{ provider: "cerebras", type: "api_key" },
];
const reports = [makeReport("anthropic", "seen@x.com", [])];
const reports = [makeReport("anthropic", "seen@example.test", [])];
it("flags providers without reports and identified accounts missing from reports", () => {
const unreported = collectUnreportedAccounts(reports, accounts);
expect(unreported).toEqual([
{ provider: "anthropic", type: "oauth", email: "missing@x.com" },
{ provider: "anthropic", type: "oauth", email: "missing@example.test" },
{ provider: "cerebras", type: "api_key" },
]);
});
it("does not claim unattributable credentials are missing when reports carry no identity", () => {
const anonymous = [{ ...makeReport("anthropic", "seen@x.com", []), metadata: {} }];
const anonymous = [{ ...makeReport("anthropic", "seen@example.test", []), metadata: {} }];
const unreported = collectUnreportedAccounts(anonymous, accounts);
expect(unreported).toEqual([{ provider: "cerebras", type: "api_key" }]);
});
@@ -140,33 +140,47 @@ describe("collectUnreportedAccounts", () => {
describe("formatUsageBreakdown", () => {
const reports = [
makeReport("anthropic", "dum.my9@example.net", [
makeReport("anthropic", "dummy.primary@example.test", [
makeLimit({ id: "Claude 5 Hour", usedFraction: 0.84, durationMs: FIVE_HOURS, windowId: "5h" }),
]),
makeReport("anthropic", "dummy@example.net", [
makeReport("anthropic", "dummy.secondary@example.test", [
makeLimit({ id: "Claude 5 Hour", usedFraction: 0.5, durationMs: FIVE_HOURS, windowId: "5h" }),
]),
];
const accounts: UsageAccountIdentity[] = [
{ provider: "anthropic", type: "oauth", email: "dum.my9@example.net" },
{ provider: "anthropic", type: "oauth", email: "dummy@example.net" },
{ provider: "anthropic", type: "oauth", email: "dummy.primary@example.test" },
{ provider: "anthropic", type: "oauth", email: "dummy.secondary@example.test" },
{ provider: "cerebras", type: "api_key" },
];
it("renders every account: reported ones with limits, credential-only ones as no-data rows", () => {
const text = stripVTControlCharacters(formatUsageBreakdown(reports, accounts, Date.now()));
expect(text).toContain("dum.my9@example.net");
expect(text).toContain("dummy.primary@example.test");
expect(text).toContain("84.0% used");
expect(text).toContain("Cerebras");
expect(text).toContain("API key — no usage data");
expect(text).toContain("need: 5h → 2 of 2 accounts");
expect(text).toContain("capacity: 5h → 1.34/2 accounts used (0.66× quota left)");
});
it("keeps near-exhausted capacity fractional instead of rounding it to an exact need", () => {
const nearReports = [
makeReport("anthropic", "near-a@example.test", [
makeLimit({ id: "Claude 5 Hour", usedFraction: 1, durationMs: FIVE_HOURS, windowId: "5h" }),
]),
makeReport("anthropic", "near-b@example.test", [
makeLimit({ id: "Claude 5 Hour", usedFraction: 0.99, durationMs: FIVE_HOURS, windowId: "5h" }),
]),
];
const text = stripVTControlCharacters(formatUsageBreakdown(nearReports, [], Date.now()));
expect(text).toContain("capacity: 5h → 1.99/2 accounts used (0.01× quota left)");
expect(text).not.toContain("need:");
});
it("redacts account labels through the provided map without leaking the originals", () => {
const redaction = buildRedactionMap(["dum.my9@example.net", "dummy@example.net"]);
const redaction = buildRedactionMap(["dummy.primary@example.test", "dummy.secondary@example.test"]);
const text = stripVTControlCharacters(formatUsageBreakdown(reports, accounts, Date.now(), redaction));
expect(text).not.toContain("dum.my9@example.net");
expect(text).not.toContain("dummy@example.net");
expect(text).not.toContain("dummy.primary@example.test");
expect(text).not.toContain("dummy.secondary@example.test");
for (const mask of redaction.values()) expect(text).toContain(mask);
});
});