feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard. Lanes: a durable unit of parallel agent work, one per working directory, tracked across session restarts. Managed lanes are git worktrees the dashboard provisions and can reset or remove behind a three-check destroy guard and a counted preflight; adopted lanes are directories you already own and are never destroyable. Pipelines: a lane moves through pipeline stages. A stage the agent declares with evidence renders green; a stage inferred from the tool-event stream renders dashed amber and never counts as done. Detection is forward-only within a 30-minute window, and never writes the declared stage. Workspace: one page at /run with a lane grid, the selected lane's pipeline, and a full Claude console behind a disclosure.
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
/**
|
||||
* @file Unit tests for the enhanced pricing calculator and the shared token-usage
|
||||
* normalizer: 5m/1h cache-write split, server-tool surcharges, and the per-bucket
|
||||
* pricing modifiers (fast mode, US data residency, Batch API).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
const { describe, it } = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
|
||||
const { calculateCost } = require("../routes/pricing");
|
||||
const {
|
||||
normalizeSpeed,
|
||||
normalizeGeo,
|
||||
normalizeTier,
|
||||
extractUsageFields,
|
||||
} = require("../lib/token-usage");
|
||||
|
||||
const M = 1_000_000;
|
||||
|
||||
// One Opus-4.8-shaped rule with fast pricing, used across the cost tests.
|
||||
const RULES = [
|
||||
{
|
||||
model_pattern: "claude-opus-4-8%",
|
||||
display_name: "Claude Opus 4.8",
|
||||
input_per_mtok: 5,
|
||||
output_per_mtok: 25,
|
||||
cache_read_per_mtok: 0.5,
|
||||
cache_write_per_mtok: 6.25,
|
||||
cache_write_1h_per_mtok: 10,
|
||||
fast_input_per_mtok: 10,
|
||||
fast_output_per_mtok: 50,
|
||||
},
|
||||
];
|
||||
|
||||
function bucket(extra) {
|
||||
return {
|
||||
model: "claude-opus-4-8",
|
||||
speed: "standard",
|
||||
inference_geo: "global",
|
||||
service_tier: "standard",
|
||||
input_tokens: 0,
|
||||
output_tokens: 0,
|
||||
cache_read_tokens: 0,
|
||||
cache_write_tokens: 0,
|
||||
cache_write_1h_tokens: 0,
|
||||
web_search_requests: 0,
|
||||
web_fetch_requests: 0,
|
||||
code_execution_requests: 0,
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
describe("token-usage normalizer", () => {
|
||||
it("normalizes pricing dimensions, collapsing unknowns to standard/global", () => {
|
||||
assert.equal(normalizeSpeed({ speed: "fast" }), "fast");
|
||||
assert.equal(normalizeSpeed({ speed: "standard" }), "standard");
|
||||
assert.equal(normalizeSpeed({}), "standard");
|
||||
assert.equal(normalizeGeo({ inference_geo: "us" }), "us");
|
||||
assert.equal(normalizeGeo({ inference_geo: "not_available" }), "global");
|
||||
assert.equal(normalizeGeo({}), "global");
|
||||
assert.equal(normalizeTier({ service_tier: "batch" }), "batch");
|
||||
assert.equal(normalizeTier({ service_tier: "priority" }), "standard");
|
||||
});
|
||||
|
||||
it("splits 5m vs 1h cache writes from cache_creation breakdown", () => {
|
||||
const f = extractUsageFields({
|
||||
input_tokens: 100,
|
||||
output_tokens: 200,
|
||||
cache_read_input_tokens: 50,
|
||||
cache_creation_input_tokens: 80,
|
||||
cache_creation: { ephemeral_5m_input_tokens: 30, ephemeral_1h_input_tokens: 50 },
|
||||
server_tool_use: {
|
||||
web_search_requests: 2,
|
||||
web_fetch_requests: 1,
|
||||
code_execution_requests: 3,
|
||||
},
|
||||
});
|
||||
assert.equal(f.input, 100);
|
||||
assert.equal(f.output, 200);
|
||||
assert.equal(f.cacheRead, 50);
|
||||
assert.equal(f.cacheWrite, 80);
|
||||
assert.equal(f.cacheWrite1h, 50);
|
||||
assert.equal(f.webSearch, 2);
|
||||
assert.equal(f.webFetch, 1);
|
||||
assert.equal(f.codeExec, 3);
|
||||
});
|
||||
|
||||
it("treats the old shape (no breakdown / no tool use) as all-5m, zero tools", () => {
|
||||
const f = extractUsageFields({
|
||||
input_tokens: 10,
|
||||
output_tokens: 20,
|
||||
cache_read_input_tokens: 5,
|
||||
cache_creation_input_tokens: 40,
|
||||
});
|
||||
assert.equal(f.cacheWrite, 40);
|
||||
assert.equal(f.cacheWrite1h, 0); // backward compatible: priced at the 5m rate
|
||||
assert.equal(f.webSearch, 0);
|
||||
assert.equal(f.codeExec, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateCost — token rates", () => {
|
||||
it("prices standard input/output/read/5m/1h correctly", () => {
|
||||
const r = calculateCost(
|
||||
[
|
||||
bucket({
|
||||
input_tokens: M,
|
||||
output_tokens: M,
|
||||
cache_read_tokens: M,
|
||||
cache_write_tokens: M,
|
||||
cache_write_1h_tokens: 0,
|
||||
}),
|
||||
],
|
||||
RULES
|
||||
);
|
||||
// 5 + 25 + 0.5 + 6.25(5m) = 36.75
|
||||
assert.equal(r.total_cost, 36.75);
|
||||
});
|
||||
|
||||
it("splits a mixed cache_write into 5m and 1h portions", () => {
|
||||
const r = calculateCost(
|
||||
[bucket({ cache_write_tokens: M, cache_write_1h_tokens: 0.4 * M })],
|
||||
RULES
|
||||
);
|
||||
// 0.6M @ 6.25 + 0.4M @ 10 = 3.75 + 4 = 7.75
|
||||
assert.equal(r.total_cost, 7.75);
|
||||
});
|
||||
|
||||
it("falls back to zero cost when no rule matches and surfaces the unpriced model", () => {
|
||||
const r = calculateCost([bucket({ model: "gpt-4o", input_tokens: M })], RULES);
|
||||
assert.equal(r.total_cost, 0);
|
||||
assert.equal(r.breakdown[0].matched_rule, null);
|
||||
assert.equal(r.unpriced_models.length, 1);
|
||||
assert.equal(r.unpriced_models[0].model, "gpt-4o");
|
||||
assert.equal(r.unpriced_models[0].input_tokens, M);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateCost — modifiers", () => {
|
||||
it("applies fast-mode premium (input/output) and scales cache from fast input", () => {
|
||||
const r = calculateCost(
|
||||
[
|
||||
bucket({
|
||||
speed: "fast",
|
||||
input_tokens: M,
|
||||
output_tokens: M,
|
||||
cache_write_tokens: M,
|
||||
cache_write_1h_tokens: M,
|
||||
}),
|
||||
],
|
||||
RULES
|
||||
);
|
||||
// fast input 10, output 50, 1h-write = 10 * (10/5) = 20 => 80
|
||||
assert.equal(r.total_cost, 80);
|
||||
});
|
||||
|
||||
it("applies the US data-residency 1.1x multiplier", () => {
|
||||
const r = calculateCost(
|
||||
[
|
||||
bucket({
|
||||
inference_geo: "us",
|
||||
input_tokens: M,
|
||||
output_tokens: M,
|
||||
cache_write_tokens: M,
|
||||
cache_write_1h_tokens: M,
|
||||
}),
|
||||
],
|
||||
RULES
|
||||
);
|
||||
// (5 + 25 + 10) * 1.1 = 44
|
||||
assert.equal(r.total_cost, 44);
|
||||
});
|
||||
|
||||
it("applies the Batch API 50% discount", () => {
|
||||
const r = calculateCost(
|
||||
[
|
||||
bucket({
|
||||
service_tier: "batch",
|
||||
input_tokens: M,
|
||||
output_tokens: M,
|
||||
cache_write_tokens: M,
|
||||
cache_write_1h_tokens: M,
|
||||
}),
|
||||
],
|
||||
RULES
|
||||
);
|
||||
// (5 + 25 + 10) * 0.5 = 20
|
||||
assert.equal(r.total_cost, 20);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateCost — server-tool surcharges", () => {
|
||||
it("charges web search at $10 / 1,000 searches", () => {
|
||||
const r = calculateCost([bucket({ web_search_requests: 2500 })], RULES);
|
||||
assert.equal(r.total_cost, 25);
|
||||
assert.equal(r.feature_costs.web_search_cost, 25);
|
||||
});
|
||||
|
||||
it("charges nothing for web fetch", () => {
|
||||
const r = calculateCost([bucket({ web_fetch_requests: 9999 })], RULES);
|
||||
assert.equal(r.total_cost, 0);
|
||||
assert.equal(r.feature_costs.web_fetch_cost, 0);
|
||||
});
|
||||
|
||||
it("treats code execution as free under the monthly allowance", () => {
|
||||
const r = calculateCost([bucket({ code_execution_requests: 100 })], RULES);
|
||||
assert.equal(r.feature_costs.code_execution_cost, 0); // well under 1550 free hours
|
||||
assert.ok(r.feature_costs.code_execution_hours_estimated > 0);
|
||||
});
|
||||
|
||||
it("treats code execution as free when used alongside web search", () => {
|
||||
const r = calculateCost(
|
||||
[bucket({ code_execution_requests: 1000000, web_search_requests: 1 })],
|
||||
RULES
|
||||
);
|
||||
// free-with-search => 0 estimated hours despite huge request count (search surcharge only)
|
||||
assert.equal(r.feature_costs.code_execution_hours_estimated, 0);
|
||||
assert.equal(r.feature_costs.code_execution_cost, 0);
|
||||
});
|
||||
|
||||
it("charges code execution beyond the free allowance", () => {
|
||||
// 12 requests/hour at the 5-min minimum; exceed 1550 free hours to force a charge.
|
||||
const requests = (1550 + 100) * 12; // 100 billable hours over the allowance
|
||||
const r = calculateCost([bucket({ code_execution_requests: requests })], RULES);
|
||||
assert.equal(r.feature_costs.code_execution_cost, 5); // 100 hrs * $0.05
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateCost — model_pattern matching (dated ids, no cross-match)", () => {
|
||||
// Sonnet-5 alongside Sonnet-4.6 + Opus, mirroring the seeded DEFAULT_PRICING.
|
||||
const FAMILY = [
|
||||
{ model_pattern: "claude-opus-4-8%", input_per_mtok: 5, output_per_mtok: 25 },
|
||||
{ model_pattern: "claude-sonnet-5%", input_per_mtok: 3, output_per_mtok: 15 },
|
||||
{ model_pattern: "claude-sonnet-4-6%", input_per_mtok: 3, output_per_mtok: 15 },
|
||||
];
|
||||
const priceOf = (model) => {
|
||||
const r = calculateCost([bucket({ model, output_tokens: M })], FAMILY);
|
||||
return { cost: r.total_cost, unpriced: r.unpriced_models.map((u) => u.model) };
|
||||
};
|
||||
|
||||
it("prices bare claude-sonnet-5 (not $0, not unpriced)", () => {
|
||||
const { cost, unpriced } = priceOf("claude-sonnet-5");
|
||||
assert.equal(cost, 15); // 1M output * $15
|
||||
assert.deepEqual(unpriced, []);
|
||||
});
|
||||
|
||||
it("prices a dated claude-sonnet-5-YYYYMMDD via the % suffix", () => {
|
||||
const { cost, unpriced } = priceOf("claude-sonnet-5-20260615");
|
||||
assert.equal(cost, 15);
|
||||
assert.deepEqual(unpriced, []);
|
||||
});
|
||||
|
||||
it("does not cross-match sonnet-5 ↔ sonnet-4.x (both stay priced by their own rule)", () => {
|
||||
// If claude-sonnet-5 wrongly matched the 4.6 rule (or vice versa) via a
|
||||
// greedy/short pattern, one of these would resolve to the wrong row. Both
|
||||
// are $3/$15 here, so the real guard is that neither is left UNPRICED and
|
||||
// the sonnet-4.5 (absent) case IS surfaced as unpriced.
|
||||
assert.deepEqual(priceOf("claude-sonnet-4-6").unpriced, []);
|
||||
assert.deepEqual(priceOf("claude-sonnet-5").unpriced, []);
|
||||
// A model with no rule (sonnet-4-5 not in FAMILY) must be reported unpriced,
|
||||
// proving sonnet-5%/sonnet-4-6% don't greedily swallow it.
|
||||
assert.deepEqual(priceOf("claude-sonnet-4-5").unpriced, ["claude-sonnet-4-5"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("calculateCost — date-effective (intro) pricing", () => {
|
||||
// Sonnet-5-shaped rule: intro $2/$10 through 2026-08-31, standard $3/$15 after.
|
||||
const INTRO = [
|
||||
{
|
||||
model_pattern: "claude-sonnet-5%",
|
||||
input_per_mtok: 3,
|
||||
output_per_mtok: 15,
|
||||
cache_read_per_mtok: 0.3,
|
||||
cache_write_per_mtok: 3.75,
|
||||
cache_write_1h_per_mtok: 6,
|
||||
intro_input_per_mtok: 2,
|
||||
intro_output_per_mtok: 10,
|
||||
intro_cache_read_per_mtok: 0.2,
|
||||
intro_cache_write_per_mtok: 2.5,
|
||||
intro_cache_write_1h_per_mtok: 4,
|
||||
intro_until: "2026-08-31",
|
||||
},
|
||||
];
|
||||
// 1M output → intro $10, standard $15.
|
||||
const cost = (asOf, rowDate) =>
|
||||
calculateCost(
|
||||
[{ ...bucket({ model: "claude-sonnet-5", output_tokens: M }), date: rowDate }],
|
||||
INTRO,
|
||||
asOf
|
||||
).total_cost;
|
||||
|
||||
it("uses intro rate before the cutoff (asOf)", () => {
|
||||
assert.equal(cost("2026-07-01", undefined), 10);
|
||||
});
|
||||
|
||||
it("uses intro rate on the cutoff day (inclusive)", () => {
|
||||
assert.equal(cost("2026-08-31", undefined), 10);
|
||||
});
|
||||
|
||||
it("uses standard rate after the cutoff", () => {
|
||||
assert.equal(cost("2026-09-01", undefined), 15);
|
||||
assert.equal(cost("2026-10-15", undefined), 15);
|
||||
});
|
||||
|
||||
it("prefers the row's own date over asOf (per-day pricing)", () => {
|
||||
// asOf is post-cutoff, but the row is dated pre-cutoff → intro applies.
|
||||
assert.equal(cost("2026-12-01", "2026-08-01"), 10);
|
||||
// and vice versa
|
||||
assert.equal(cost("2026-07-01", "2026-09-15"), 15);
|
||||
});
|
||||
|
||||
it("a rule with no intro_until always uses standard rate", () => {
|
||||
const NO_INTRO = [
|
||||
{ model_pattern: "claude-sonnet-5%", input_per_mtok: 3, output_per_mtok: 15 },
|
||||
];
|
||||
assert.equal(
|
||||
calculateCost(
|
||||
[bucket({ model: "claude-sonnet-5", output_tokens: M })],
|
||||
NO_INTRO,
|
||||
"2026-07-01"
|
||||
).total_cost,
|
||||
15
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user