mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-29 19:29:39 +02:00
fix(lib): reject invalid token counts and document the Fable/Mythos tier
`estimateCost` used `inputTokens` and `outputTokens` without validation. A negative count yielded a negative cost, and a non-finite one yielded NaN. That matters here specifically because this module backs the cost-aware-llm-pipeline budget skill: `NaN > budget` is false, so a corrupt token count silently passes the budget check it exists to enforce. It now throws a RangeError naming the offending field. The unknown-model fallback to sonnet rates deliberately stays fail-open: that degrades an estimate, whereas these inputs corrupt one. Separately, the estimator prices Fable and Mythos at $10/$50 per million tokens, but all three pricing tables (the skill and its ja-JP and zh-CN translations) listed only Haiku, Sonnet, and Opus, so a reader could not budget for two supported model families. Added the missing row to each. 16 of the new assertions fail against the previous behaviour (23 passed, 16 failed) and all pass with the guard (39 passed). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KV43bDJpgyMPovT2CHAoba
This commit is contained in:
co-authored by
Claude Opus 5
parent
39416e3cdd
commit
5e6c8f9e1f
@@ -158,6 +158,7 @@ def process(text: str, config: Config, tracker: CostTracker) -> tuple[Result, Co
|
||||
| Haiku 4.5 | $1.00 | $5.00 | 1x |
|
||||
| Sonnet 4.6 | $3.00 | $15.00 | 3x |
|
||||
| Opus 4.5 | $5.00 | $25.00 | 5x |
|
||||
| Fable 5 / Mythos 5 | $10.00 | $50.00 | 10x |
|
||||
|
||||
## ベストプラクティス
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ def process(text: str, config: Config, tracker: CostTracker) -> tuple[Result, Co
|
||||
| Haiku 4.5 | $1.00 | $5.00 | 1x |
|
||||
| Sonnet 4.6 | $3.00 | $15.00 | 3x |
|
||||
| Opus 4.5 | $5.00 | $25.00 | 5x |
|
||||
| Fable 5 / Mythos 5 | $10.00 | $50.00 | 10x |
|
||||
|
||||
## 最佳实践
|
||||
|
||||
|
||||
@@ -48,11 +48,24 @@ const LEGACY_HAIKU_RE = /3-5-haiku|haiku-3-5/;
|
||||
* Estimate USD cost from token counts.
|
||||
* @param {string} model - Model name (may contain "haiku", "sonnet", "opus",
|
||||
* "fable" or "mythos"); anything else is priced at sonnet rates.
|
||||
* @param {number} inputTokens
|
||||
* @param {number} outputTokens
|
||||
* @param {number} inputTokens - Finite, non-negative.
|
||||
* @param {number} outputTokens - Finite, non-negative.
|
||||
* @returns {number} Estimated cost in USD (rounded to 6 decimal places)
|
||||
* @throws {RangeError} If either token count is negative or non-finite.
|
||||
*/
|
||||
function estimateCost(model, inputTokens, outputTokens) {
|
||||
// Callers use this to decide whether a call fits a budget, and both bad
|
||||
// inputs defeat that check silently rather than loudly: a negative count
|
||||
// yields a negative cost, and a non-finite one yields NaN, for which every
|
||||
// `cost > budget` comparison is false. An unpriceable model still falls
|
||||
// back to sonnet rates on purpose — that degrades an estimate; this
|
||||
// corrupts one.
|
||||
for (const [name, value] of [['inputTokens', inputTokens], ['outputTokens', outputTokens]]) {
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
throw new RangeError(`${name} must be a finite non-negative number, got ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
const normalized = String(model || '').toLowerCase();
|
||||
let rates = RATE_TABLE.sonnet;
|
||||
if (normalized.includes('haiku')) {
|
||||
|
||||
@@ -159,6 +159,7 @@ def process(text: str, config: Config, tracker: CostTracker) -> tuple[Result, Co
|
||||
| Haiku 4.5 | $1.00 | $5.00 | 1x |
|
||||
| Sonnet 4.6 | $3.00 | $15.00 | 3x |
|
||||
| Opus 4.5 | $5.00 | $25.00 | 5x |
|
||||
| Fable 5 / Mythos 5 | $10.00 | $50.00 | 10x |
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
||||
@@ -172,6 +172,36 @@ function runTests() {
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
// A negative count yields a negative cost and a non-finite one yields NaN,
|
||||
// and `NaN > budget` is false — so bad input defeats a budget check silently
|
||||
// rather than loudly. Pin the throw so a regression fails here.
|
||||
for (const bad of [-1, -0.5, NaN, Infinity, -Infinity, undefined, null, '100']) {
|
||||
if (
|
||||
test(`rejects ${String(bad)} as an input token count`, () => {
|
||||
assert.throws(() => estimateCost('sonnet', bad, 100), RangeError);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
test(`rejects ${String(bad)} as an output token count`, () => {
|
||||
assert.throws(() => estimateCost('sonnet', 100, bad), RangeError);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
}
|
||||
|
||||
if (
|
||||
test('accepts zero and fractional token counts', () => {
|
||||
assert.strictEqual(estimateCost('sonnet', 0, 0), 0);
|
||||
assert.strictEqual(estimateCost('sonnet', 500_000, 0), 1.5);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
// Summary
|
||||
console.log(`\nResults: ${passed} passed, ${failed} failed\n`);
|
||||
return { passed, failed };
|
||||
|
||||
Reference in New Issue
Block a user