Skip to content

Commit a0db11e

Browse files
committed
fix: range-check tier rates and label keepalive commits honestly
verify.mjs only checked top-level rate fields, so a long-context tier could carry any value and still pass CI. A keepalive-only run also committed under the price-refresh message, which the history is read as a log of.
1 parent 94175ec commit a0db11e

3 files changed

Lines changed: 34 additions & 6 deletions

File tree

.github/workflows/refresh.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,11 @@ jobs:
6969
git config user.name "model-prices[bot]"
7070
git config user.email "noreply@cloudstack.llc"
7171
git add -A
72-
git commit -m "chore: refresh prices from models.dev"
72+
# A keepalive-only run must not claim prices moved; the history is
73+
# read as a price-change log.
74+
if git diff --cached --quiet -- v1; then
75+
git commit -m "chore: keep the refresh schedule alive"
76+
else
77+
git commit -m "chore: refresh prices from models.dev"
78+
fi
7379
git push

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
11
node_modules/
2-
.keepalive.tmp

scripts/verify.mjs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,31 @@ import { COST_UNIT, MAX_RATE, SCHEMA_VERSION, serialize } from "./transform.mjs"
1212
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
1313
const path = resolve(repoRoot, process.argv[2] ?? "v1/prices.json");
1414

15+
16+
// Token limits are counts, not rates, and are excluded from the rate ceiling.
17+
const LIMIT_FIELDS = new Set(["context", "max_output", "above_context"]);
18+
19+
// Tiers hold their own copy of every rate field. Checking only the top level
20+
// would let a long-context rate carry any value at all.
21+
function rateProblems(model, label) {
22+
const problems = [];
23+
for (const [field, value] of Object.entries(model)) {
24+
if (field === "tiers" && Array.isArray(value)) {
25+
value.forEach((tier, index) => {
26+
problems.push(...rateProblems(tier, `${label}.tiers[${index}]`));
27+
});
28+
continue;
29+
}
30+
if (LIMIT_FIELDS.has(field) || typeof value !== "number") {
31+
continue;
32+
}
33+
if (value < 0 || value > MAX_RATE) {
34+
problems.push(`${label}.${field} is out of range: ${value}`);
35+
}
36+
}
37+
return problems;
38+
}
39+
1540
const text = await readFile(path, "utf8");
1641
const artifact = JSON.parse(text);
1742
const problems = [];
@@ -35,10 +60,8 @@ for (const [providerId, entries] of Object.entries(artifact.providers ?? {})) {
3560
problems.push(`${providerId}/${modelId} is missing input or output`);
3661
continue;
3762
}
38-
for (const [field, value] of Object.entries(model)) {
39-
if (typeof value === "number" && (value < 0 || value > MAX_RATE) && field !== "context" && field !== "max_output") {
40-
problems.push(`${providerId}/${modelId}.${field} is out of range: ${value}`);
41-
}
63+
for (const problem of rateProblems(model, `${providerId}/${modelId}`)) {
64+
problems.push(problem);
4265
}
4366
}
4467
}

0 commit comments

Comments
 (0)