mirror of
https://github.com/VibedByKaKi/t3-code-android-nightly.git
synced 2026-10-09 11:51:15 +02:00
788 lines
24 KiB
TypeScript
788 lines
24 KiB
TypeScript
import {
|
|
USAGE_CONTRACT_VERSION,
|
|
USAGE_MERGE_COMPATIBLE_SINCE,
|
|
type EnvironmentId,
|
|
type UsageBucket,
|
|
type UsageDay,
|
|
type UsageProviderKind,
|
|
UsageSummary,
|
|
} from "@t3tools/contracts";
|
|
import { describe, expect, it } from "vite-plus/test";
|
|
import * as Schema from "effect/Schema";
|
|
|
|
import { isModelCostUnknown, mergeUsage, type EnvironmentUsage } from "./usageMerge.ts";
|
|
|
|
const decodeSummary = Schema.decodeUnknownSync(UsageSummary);
|
|
const encodeSummary = Schema.encodeSync(UsageSummary);
|
|
|
|
function bucket(overrides: Partial<UsageBucket> = {}): UsageBucket {
|
|
return {
|
|
day: "2026-08-07" as UsageDay,
|
|
provider: "claude",
|
|
model: "claude-fable-5",
|
|
totals: {
|
|
uncachedInputTokens: 100,
|
|
cachedInputTokens: 1000,
|
|
cacheCreationTokens: 10,
|
|
outputTokens: 50,
|
|
reasoningTokens: 0,
|
|
},
|
|
costUsd: 10,
|
|
cacheSavingsUsd: 2,
|
|
costSource: "modelPriced",
|
|
records: 5,
|
|
unpricedRecords: 0,
|
|
sessions: 1,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function summary(
|
|
buckets: readonly UsageBucket[],
|
|
sources: readonly {
|
|
provider: UsageProviderKind;
|
|
hostId: string;
|
|
homePath: string;
|
|
volumeId?: string;
|
|
distinctSessions?: number;
|
|
}[],
|
|
contractVersion: number = USAGE_CONTRACT_VERSION,
|
|
): UsageSummary {
|
|
return {
|
|
contractVersion,
|
|
readAt: "2026-08-07T00:00:00.000Z",
|
|
timeZone: "UTC",
|
|
sinceDay: "2026-08-01" as UsageDay,
|
|
untilDay: "2026-08-31" as UsageDay,
|
|
buckets,
|
|
sources: sources.map((source) => ({
|
|
fingerprint: {
|
|
hostId: source.hostId,
|
|
provider: source.provider,
|
|
resolvedHomePath: source.homePath,
|
|
volumeId: source.volumeId ?? `vol-${source.hostId}`,
|
|
},
|
|
status: "ok" as const,
|
|
scannedFiles: 1,
|
|
skippedFiles: 0,
|
|
malformedRecords: 0,
|
|
distinctSessions: source.distinctSessions ?? 1,
|
|
message: null,
|
|
})),
|
|
pricing: { status: "fresh", source: "litellm", fetchedAt: null, knownModels: 10 },
|
|
scanDurationMs: 1,
|
|
};
|
|
}
|
|
|
|
function environment(id: string, usageSummary: UsageSummary): EnvironmentUsage {
|
|
return { environmentId: id as EnvironmentId, label: id, summary: usageSummary };
|
|
}
|
|
|
|
describe("mergeUsage", () => {
|
|
it("counts a Cursor account once across servers while retaining each server's other providers", () => {
|
|
const account = {
|
|
provider: "cursor" as const,
|
|
hostId: "cursor.com",
|
|
homePath: "cursor-account:account-hash",
|
|
volumeId: "account-hash",
|
|
};
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"mac",
|
|
summary([bucket({ provider: "cursor", sourcePath: account.homePath })], [account]),
|
|
),
|
|
environment(
|
|
"linux",
|
|
summary(
|
|
[
|
|
bucket({ provider: "cursor", sourcePath: account.homePath }),
|
|
bucket({ provider: "opencode", sourcePath: "/opencode" }),
|
|
],
|
|
[account, { provider: "opencode", hostId: "linux", homePath: "/opencode" }],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
expect(
|
|
merged.providers.map((provider) => [provider.provider, provider.costUsd]).sort(),
|
|
).toEqual([
|
|
["cursor", 10],
|
|
["opencode", 10],
|
|
]);
|
|
expect(merged.duplicateSources).toHaveLength(1);
|
|
});
|
|
|
|
it("sums environments that read different transcript directories", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary([bucket()], [{ provider: "claude", hostId: "mac", homePath: "/a/.claude" }]),
|
|
),
|
|
environment(
|
|
"env-b",
|
|
summary([bucket()], [{ provider: "claude", hostId: "linux", homePath: "/b/.claude" }]),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(20);
|
|
expect(merged.records).toBe(10);
|
|
expect(merged.duplicateSources).toHaveLength(0);
|
|
});
|
|
|
|
it("counts a shared transcript directory once", () => {
|
|
// Two worktree servers on one machine resolve the same provider home.
|
|
const shared = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" };
|
|
const merged = mergeUsage(
|
|
[
|
|
environment("env-a", summary([bucket()], [shared])),
|
|
environment("env-b", summary([bucket()], [shared])),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.records).toBe(5);
|
|
expect(merged.sessions).toBe(1);
|
|
expect(merged.duplicateSources).toHaveLength(1);
|
|
expect(merged.contributingEnvironments).toEqual(["env-a"]);
|
|
});
|
|
|
|
it("drops only the duplicated provider, keeping the environment's other one", () => {
|
|
const sharedClaude = {
|
|
provider: "claude" as const,
|
|
hostId: "mac",
|
|
homePath: "/home/theo/.claude",
|
|
};
|
|
const merged = mergeUsage(
|
|
[
|
|
environment("env-a", summary([bucket()], [sharedClaude])),
|
|
environment(
|
|
"env-b",
|
|
summary(
|
|
[bucket(), bucket({ provider: "codex", model: "gpt-5.6-sol", costUsd: 4 })],
|
|
[sharedClaude, { provider: "codex", hostId: "mac", homePath: "/home/theo/.codex" }],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
// env-b's claude bucket is dropped, its codex bucket survives.
|
|
expect(merged.costUsd).toBe(14);
|
|
expect(merged.providers.map((provider) => provider.provider).sort()).toEqual([
|
|
"claude",
|
|
"codex",
|
|
]);
|
|
expect(merged.sessions).toBe(2);
|
|
expect(
|
|
Object.fromEntries(
|
|
merged.providers.map((provider) => [provider.provider, provider.sessions]),
|
|
),
|
|
).toEqual({ claude: 1, codex: 1 });
|
|
});
|
|
|
|
it("counts overlapping provider roots once while keeping each environment's unique root", () => {
|
|
const source = (homePath: string) => ({
|
|
provider: "opencode" as const,
|
|
hostId: "host",
|
|
homePath,
|
|
});
|
|
const usage = (sourcePath: string, costUsd: number) =>
|
|
bucket({ provider: "opencode", sourcePath, costUsd });
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary([usage("/shared", 10), usage("/a", 2)], [source("/shared"), source("/a")]),
|
|
),
|
|
environment(
|
|
"env-b",
|
|
summary([usage("/shared", 10), usage("/b", 3)], [source("/shared"), source("/b")]),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
expect(merged.costUsd).toBe(15);
|
|
expect(merged.sessions).toBe(3);
|
|
});
|
|
|
|
it("uses the newest scan when environments share the same transcript directory", () => {
|
|
const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" };
|
|
const environments = [
|
|
environment("env-a", summary([bucket({ costUsd: 4, records: 2 })], [source])),
|
|
environment("env-b", {
|
|
...summary([bucket()], [source]),
|
|
readAt: "2026-08-07T01:00:00.000Z",
|
|
}),
|
|
];
|
|
|
|
for (const ordered of [environments, environments.toReversed()]) {
|
|
const merged = mergeUsage(ordered, USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.records).toBe(5);
|
|
expect(merged.sessions).toBe(1);
|
|
expect(merged.contributingEnvironments).toEqual(["env-b"]);
|
|
expect(merged.duplicateSources).toEqual(["env-a: /home/theo/.claude"]);
|
|
}
|
|
});
|
|
|
|
it("prefers a complete scan over a newer partial scan of the same directory", () => {
|
|
const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" };
|
|
const incomplete = summary([bucket({ costUsd: 4, records: 2 })], [source]);
|
|
const partial = environment("new", {
|
|
...incomplete,
|
|
readAt: "2026-08-07T01:00:00.000Z",
|
|
sources: incomplete.sources.map((entry) => ({ ...entry, status: "partial" as const })),
|
|
});
|
|
const complete = environment("old", summary([bucket()], [source]));
|
|
|
|
for (const ordered of [
|
|
[partial, complete],
|
|
[complete, partial],
|
|
]) {
|
|
const merged = mergeUsage(ordered, USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.contributingEnvironments).toEqual(["old"]);
|
|
expect(merged.duplicateSources).toEqual(["new: /home/theo/.claude"]);
|
|
}
|
|
expect(mergeUsage([partial], USAGE_CONTRACT_VERSION).costUsd).toBe(4);
|
|
});
|
|
|
|
it("keeps new cells from a later partial scan without recounting older cells", () => {
|
|
const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" };
|
|
const complete = environment(
|
|
"old",
|
|
summary([bucket()], [source], USAGE_MERGE_COMPATIBLE_SINCE),
|
|
);
|
|
const partialSummary = summary(
|
|
[
|
|
bucket({ sourcePath: source.homePath, costUsd: 4, records: 2 }),
|
|
bucket({
|
|
day: "2026-08-08" as UsageDay,
|
|
sourcePath: source.homePath,
|
|
costUsd: 3,
|
|
records: 1,
|
|
}),
|
|
],
|
|
[{ ...source, distinctSessions: 2 }],
|
|
);
|
|
const partial = environment("new", {
|
|
...partialSummary,
|
|
readAt: "2026-08-08T01:00:00.000Z",
|
|
sources: partialSummary.sources.map((entry) => ({ ...entry, status: "partial" as const })),
|
|
});
|
|
|
|
for (const ordered of [
|
|
[complete, partial],
|
|
[partial, complete],
|
|
]) {
|
|
const merged = mergeUsage(ordered, USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(13);
|
|
expect(merged.records).toBe(6);
|
|
expect(merged.sessions).toBe(2);
|
|
expect(merged.daily.map(({ day, costUsd }) => [day, costUsd])).toEqual([
|
|
["2026-08-07", 10],
|
|
["2026-08-08", 3],
|
|
]);
|
|
expect(merged.contributingEnvironments).toEqual(
|
|
ordered.map(({ environmentId }) => environmentId),
|
|
);
|
|
expect(merged.duplicateSources).toEqual(["new: /home/theo/.claude"]);
|
|
}
|
|
});
|
|
|
|
it("retains a complete cell when a larger partial cell may have skipped old records", () => {
|
|
const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" };
|
|
const complete = environment("old", summary([bucket()], [source]));
|
|
const partialSummary = summary(
|
|
[
|
|
bucket({
|
|
costUsd: 4,
|
|
records: 6,
|
|
totals: {
|
|
uncachedInputTokens: 80,
|
|
cachedInputTokens: 500,
|
|
cacheCreationTokens: 10,
|
|
outputTokens: 30,
|
|
reasoningTokens: 0,
|
|
},
|
|
}),
|
|
],
|
|
[{ ...source, distinctSessions: 2 }],
|
|
);
|
|
const partial = environment("new", {
|
|
...partialSummary,
|
|
readAt: "2026-08-07T01:00:00.000Z",
|
|
sources: partialSummary.sources.map((entry) => ({ ...entry, status: "partial" as const })),
|
|
});
|
|
|
|
const merged = mergeUsage([complete, partial], USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.totalTokens).toBe(1160);
|
|
expect(merged.records).toBe(5);
|
|
expect(merged.sessions).toBe(1);
|
|
expect(merged.contributingEnvironments).toEqual(["old"]);
|
|
});
|
|
|
|
it("identifies an environment reporting an older contract version", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary([bucket()], [{ provider: "claude", hostId: "mac", homePath: "/a" }]),
|
|
),
|
|
environment(
|
|
"env-b",
|
|
summary(
|
|
[bucket()],
|
|
[{ provider: "claude", hostId: "linux", homePath: "/b" }],
|
|
USAGE_MERGE_COMPATIBLE_SINCE - 1,
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.contractMismatches).toEqual([
|
|
{
|
|
environmentId: "env-b",
|
|
direction: "serverBehind",
|
|
contractVersion: USAGE_MERGE_COMPATIBLE_SINCE - 1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("identifies an environment reporting a newer contract version", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[bucket()],
|
|
[{ provider: "claude", hostId: "mac", homePath: "/a" }],
|
|
USAGE_CONTRACT_VERSION + 1,
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(0);
|
|
expect(merged.contractMismatches).toEqual([
|
|
{
|
|
environmentId: "env-a",
|
|
direction: "clientBehind",
|
|
contractVersion: USAGE_CONTRACT_VERSION + 1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("keeps the previous compatible contract version so additive provider expansions still merge", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[bucket({ costUsd: 10 })],
|
|
[{ provider: "claude", hostId: "mac", homePath: "/a" }],
|
|
),
|
|
),
|
|
environment(
|
|
"env-b",
|
|
summary(
|
|
[bucket({ costUsd: 4, provider: "codex", model: "gpt-5.6-sol" })],
|
|
[{ provider: "codex", hostId: "linux", homePath: "/b" }],
|
|
USAGE_CONTRACT_VERSION - 1,
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(14);
|
|
expect(merged.contractMismatches).toEqual([]);
|
|
});
|
|
|
|
it("keeps known usage when newer providers and bucket variants cannot be decoded", () => {
|
|
const known = summary([bucket()], [{ provider: "claude", hostId: "mac", homePath: "/a" }]);
|
|
const decoded = decodeSummary({
|
|
...known,
|
|
buckets: [
|
|
...known.buckets,
|
|
{ ...bucket(), provider: "future-provider", costUsd: 100 },
|
|
{ ...bucket(), costSource: "future-pricing", costUsd: 200 },
|
|
],
|
|
sources: [
|
|
...known.sources,
|
|
{
|
|
...known.sources[0],
|
|
fingerprint: {
|
|
...known.sources[0]?.fingerprint,
|
|
provider: "future-provider",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
expect(decoded).toEqual(known);
|
|
const merged = mergeUsage([environment("env-a", decoded)], USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.totalTokens).toBe(1160);
|
|
expect(merged.contractMismatches).toEqual([]);
|
|
});
|
|
|
|
it("normalizes model names during decoding before grouping usage", () => {
|
|
const decoded = decodeSummary(
|
|
summary(
|
|
[
|
|
bucket({ provider: "codex", model: " gpt-5 " }),
|
|
bucket({ provider: "codex", model: "gpt-5" }),
|
|
],
|
|
[{ provider: "codex", hostId: "mac", homePath: "/a" }],
|
|
),
|
|
);
|
|
|
|
expect(decoded.buckets.map((entry) => entry.model)).toEqual(["gpt-5", "gpt-5"]);
|
|
const merged = mergeUsage([environment("env-a", decoded)], USAGE_CONTRACT_VERSION);
|
|
expect(merged.models).toHaveLength(1);
|
|
expect(merged.models[0]).toMatchObject({ model: "gpt-5", costUsd: 20, totalTokens: 2320 });
|
|
});
|
|
|
|
it("keeps all supported providers when encoding a response", () => {
|
|
const current = summary(
|
|
[bucket(), bucket({ provider: "grok" })],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a" },
|
|
{ provider: "grok", hostId: "mac", homePath: "/b" },
|
|
],
|
|
);
|
|
expect(encodeSummary(current)).toEqual(current);
|
|
expect(decodeSummary(encodeSummary(current))).toEqual(current);
|
|
});
|
|
|
|
it("still rejects a malformed summary envelope", () => {
|
|
expect(() => decodeSummary({ ...summary([], []), buckets: null })).toThrow();
|
|
});
|
|
|
|
it("excludes a future incompatible contract even when its buckets still decode", () => {
|
|
const decoded = decodeSummary(
|
|
summary(
|
|
[bucket()],
|
|
[{ provider: "claude", hostId: "mac", homePath: "/a" }],
|
|
USAGE_CONTRACT_VERSION + 1,
|
|
),
|
|
);
|
|
const merged = mergeUsage([environment("env-a", decoded)], USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(0);
|
|
expect(merged.contractMismatches).toEqual([
|
|
{
|
|
environmentId: "env-a",
|
|
direction: "clientBehind",
|
|
contractVersion: USAGE_CONTRACT_VERSION + 1,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it("derives provider shares and cost quality", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({ costUsd: 75 }),
|
|
bucket({ provider: "codex", model: "gpt-5.6-sol", costUsd: 25, unpricedRecords: 5 }),
|
|
],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a/.claude" },
|
|
{ provider: "codex", hostId: "mac", homePath: "/a/.codex" },
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.providers[0]?.provider).toBe("claude");
|
|
expect(merged.providers[0]?.costShare).toBeCloseTo(0.75, 5);
|
|
expect(merged.costQuality.unpricedShare).toBeCloseTo(0.5, 5);
|
|
expect(merged.costQuality.cacheSavingsUsd).toBe(4);
|
|
});
|
|
|
|
it("derives model token shares independently of their cost shares", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({ costUsd: 90 }),
|
|
bucket({
|
|
provider: "codex",
|
|
model: "gpt-5.6-sol",
|
|
costUsd: 10,
|
|
totals: {
|
|
uncachedInputTokens: 3 * 1160,
|
|
cachedInputTokens: 0,
|
|
cacheCreationTokens: 0,
|
|
outputTokens: 0,
|
|
reasoningTokens: 0,
|
|
},
|
|
}),
|
|
],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a/.claude" },
|
|
{ provider: "codex", hostId: "mac", homePath: "/a/.codex" },
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
const byModel = Object.fromEntries(merged.models.map((model) => [model.model, model]));
|
|
expect(byModel["claude-fable-5"]?.costShare).toBeCloseTo(0.9, 5);
|
|
expect(byModel["claude-fable-5"]?.tokenShare).toBeCloseTo(0.25, 5);
|
|
expect(byModel["gpt-5.6-sol"]?.costShare).toBeCloseTo(0.1, 5);
|
|
expect(byModel["gpt-5.6-sol"]?.tokenShare).toBeCloseTo(0.75, 5);
|
|
});
|
|
|
|
it("marks a model with no known rates as unpriced rather than free", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({ costUsd: 75 }),
|
|
bucket({
|
|
provider: "codex",
|
|
model: "unknown-model",
|
|
costUsd: 0,
|
|
costSource: "unpriced",
|
|
unpricedRecords: 5,
|
|
}),
|
|
],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a/.claude" },
|
|
{ provider: "codex", hostId: "mac", homePath: "/a/.codex" },
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.models.find((model) => model.model === "unknown-model")?.unpricedRecords).toBe(5);
|
|
expect(merged.models.filter(isModelCostUnknown).map((model) => model.model)).toEqual([
|
|
"unknown-model",
|
|
]);
|
|
});
|
|
|
|
it("splits cost by category and speed, counting older servers as unsplit standard cost", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({
|
|
costUsd: 10,
|
|
categoryCostUsd: { input: 1, cacheRead: 2, cacheWrite: 3, output: 4 },
|
|
fastCostUsd: 6,
|
|
speedPremiumUsd: 3,
|
|
}),
|
|
bucket({
|
|
provider: "codex",
|
|
model: "unknown-model",
|
|
costUsd: 0,
|
|
costSource: "unpriced",
|
|
unpricedRecords: 5,
|
|
}),
|
|
// Reported cost on one record, no rates for the other four.
|
|
bucket({
|
|
provider: "codex",
|
|
model: "partly-reported",
|
|
costUsd: 0,
|
|
unpricedRecords: 4,
|
|
}),
|
|
],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a/.claude" },
|
|
{ provider: "codex", hostId: "mac", homePath: "/a/.codex" },
|
|
],
|
|
),
|
|
),
|
|
environment(
|
|
"env-b",
|
|
summary(
|
|
[bucket({ costUsd: 5 })],
|
|
[{ provider: "claude", hostId: "linux", homePath: "/b/.claude" }],
|
|
USAGE_MERGE_COMPATIBLE_SINCE,
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.categoryCost).toEqual({
|
|
input: 1,
|
|
cacheRead: 2,
|
|
cacheWrite: 3,
|
|
output: 4,
|
|
unsplit: 5,
|
|
});
|
|
expect(merged.speedCost).toEqual({ standard: 9, fast: 6, ultrafast: 0, premium: 3 });
|
|
expect(merged.models.map(({ model, unpricedTokens }) => [model, unpricedTokens])).toEqual([
|
|
["claude-fable-5", 0],
|
|
["unknown-model", 1160],
|
|
["partly-reported", 928],
|
|
]);
|
|
});
|
|
|
|
it("orders models by cost descending", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({ provider: "claude", model: "lower-cost", costUsd: 4 }),
|
|
bucket({ provider: "codex", model: "higher-cost", costUsd: 9 }),
|
|
],
|
|
[
|
|
{ provider: "claude", hostId: "mac", homePath: "/a/.claude" },
|
|
{ provider: "codex", hostId: "mac", homePath: "/a/.codex" },
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.models.map((model) => model.model)).toEqual(["higher-cost", "lower-cost"]);
|
|
});
|
|
|
|
it("keeps two machines apart when hostname and home path collide", () => {
|
|
// Every Mac resolves /Users/theo/.claude, so a hostname clash used to make
|
|
// one machine's usage vanish. Filesystem identity separates them.
|
|
const shape = { provider: "claude" as const, hostId: "mac", homePath: "/Users/theo/.claude" };
|
|
const merged = mergeUsage(
|
|
[
|
|
environment("env-a", summary([bucket()], [{ ...shape, volumeId: "16777220:1234" }])),
|
|
environment("env-b", summary([bucket()], [{ ...shape, volumeId: "16777221:9999" }])),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(20);
|
|
expect(merged.duplicateSources).toHaveLength(0);
|
|
});
|
|
|
|
it("still collapses two servers reading the same directory", () => {
|
|
const same = {
|
|
provider: "claude" as const,
|
|
hostId: "mac",
|
|
homePath: "/Users/theo/.claude",
|
|
volumeId: "16777220:1234",
|
|
};
|
|
const merged = mergeUsage(
|
|
[
|
|
environment("env-a", summary([bucket()], [same])),
|
|
environment("env-b", summary([bucket()], [same])),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.costUsd).toBe(10);
|
|
expect(merged.duplicateSources).toHaveLength(1);
|
|
});
|
|
|
|
it("totals sessions from per-directory distinct counts, not per-bucket sums", () => {
|
|
// One session that spans two days appears in two buckets. Summing bucket
|
|
// sessions would say 2; the source's distinct count says 1.
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[bucket({ day: "2026-08-06" as UsageDay }), bucket({ day: "2026-08-07" as UsageDay })],
|
|
[
|
|
{
|
|
provider: "claude",
|
|
hostId: "mac",
|
|
homePath: "/a/.claude",
|
|
distinctSessions: 1,
|
|
},
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.sessions).toBe(1);
|
|
expect(merged.providers[0]?.sessions).toBe(1);
|
|
});
|
|
|
|
it("returns empty totals with no environments", () => {
|
|
const merged = mergeUsage([], USAGE_CONTRACT_VERSION);
|
|
expect(merged.costUsd).toBe(0);
|
|
expect(merged.daily).toHaveLength(0);
|
|
expect(merged.hourly).toHaveLength(0);
|
|
});
|
|
|
|
it("omits providers with no sessions or usage", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[],
|
|
[
|
|
{
|
|
provider: "claude",
|
|
hostId: "mac",
|
|
homePath: "/a/.claude",
|
|
distinctSessions: 0,
|
|
},
|
|
],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.providers).toEqual([]);
|
|
});
|
|
|
|
it("derives hourly totals without losing the daily rollup", () => {
|
|
const merged = mergeUsage(
|
|
[
|
|
environment(
|
|
"env-a",
|
|
summary(
|
|
[
|
|
bucket({ hourStart: "2026-08-07T09:37:00.000Z", costUsd: 3 }),
|
|
bucket({ hourStart: "2026-08-07T10:37:00.000Z", costUsd: 7 }),
|
|
],
|
|
[{ provider: "claude", hostId: "mac", homePath: "/a/.claude" }],
|
|
),
|
|
),
|
|
],
|
|
USAGE_CONTRACT_VERSION,
|
|
);
|
|
|
|
expect(merged.hourly.map((hour) => [hour.hourStart, hour.costUsd])).toEqual([
|
|
["2026-08-07T09:37:00.000Z", 3],
|
|
["2026-08-07T10:37:00.000Z", 7],
|
|
]);
|
|
expect(merged.daily).toHaveLength(1);
|
|
expect(merged.daily[0]?.costUsd).toBe(10);
|
|
});
|
|
});
|