feat(ms-ai-architect): add lib/cost-estimat heuristic for API-key budget [skip-docs]

Pure auth-mode-aware cost estimator for v1.12.0 cron pre-flight.

Heuristic: critical+high files only (medium/low excluded per brief);
3000 input + 1500 output tokens per file at Sonnet pricing
($3/M in, $15/M out).

Auth-mode behavior:
- api-key:   numeric usd, kvote_warn off  (subject to dollar-cap)
- long-oauth, subscription-browser-only:
             usd null, kvote_warn on      (quota, no dollar billing)
- unauthenticated/missing: best-effort api-key estimate

11/11 tests pass; covers both billing modes plus token-math
invariance across auth-mode (auth only affects dollar-field, not tokens).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-05-05 10:34:37 +02:00
commit f2b76b6d8e
2 changed files with 118 additions and 0 deletions

View file

@ -0,0 +1,36 @@
// cost-estimat.mjs — Heuristic cost-estimate for KB-update runs.
// Pure function. Auth-mode-aware: api-key returns numeric USD,
// subscription modes return null USD + kvote_warn flag.
// Zero dependencies.
const AVG_INPUT_TOKENS_PER_FILE = 3000;
const AVG_OUTPUT_TOKENS_PER_FILE = 1500;
const SONNET_INPUT_USD_PER_M = 3.0;
const SONNET_OUTPUT_USD_PER_M = 15.0;
const SUBSCRIPTION_MODES = new Set(['long-oauth', 'subscription-browser-only']);
/**
* Estimate cost (and quota-warn flag) for a run of N files at given priorities.
* Filters to critical + high only (medium/low excluded per brief).
*
* @param {object} priorities { critical, high, medium, low } file counts
* @param {object} [opts]
* @param {string} [opts.authMode] 'api-key' | 'long-oauth' | 'subscription-browser-only' | 'unauthenticated'
* @returns {{tokens_input: number, tokens_output: number, usd: number|null, kvote_warn: boolean}}
*/
export function estimateCost(priorities = {}, opts = {}) {
const authMode = opts.authMode ?? 'api-key';
const fileCount = (priorities.critical ?? 0) + (priorities.high ?? 0);
const tokens_input = fileCount * AVG_INPUT_TOKENS_PER_FILE;
const tokens_output = fileCount * AVG_OUTPUT_TOKENS_PER_FILE;
if (SUBSCRIPTION_MODES.has(authMode)) {
return { tokens_input, tokens_output, usd: null, kvote_warn: true };
}
const usd =
(tokens_input / 1_000_000) * SONNET_INPUT_USD_PER_M +
(tokens_output / 1_000_000) * SONNET_OUTPUT_USD_PER_M;
return { tokens_input, tokens_output, usd, kvote_warn: false };
}

View file

@ -0,0 +1,82 @@
// tests/kb-update/test-cost-estimat.test.mjs
// Unit tests for scripts/kb-update/lib/cost-estimat.mjs
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { estimateCost } from '../../scripts/kb-update/lib/cost-estimat.mjs';
test('estimateCost — api-key returns numeric usd, kvote_warn unset', () => {
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'api-key' });
assert.equal(typeof result.usd, 'number');
assert.equal(result.kvote_warn, false);
assert.ok(result.usd > 0);
});
test('estimateCost — api-key empty input returns 0 USD', () => {
const result = estimateCost({}, { authMode: 'api-key' });
assert.equal(result.usd, 0);
assert.equal(result.kvote_warn, false);
assert.equal(result.tokens_input, 0);
assert.equal(result.tokens_output, 0);
});
test('estimateCost — api-key tokens are integers', () => {
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'api-key' });
assert.equal(Number.isInteger(result.tokens_input), true);
assert.equal(Number.isInteger(result.tokens_output), true);
});
test('estimateCost — ignores medium and low (only critical+high counted)', () => {
const a = estimateCost({ critical: 1, high: 1 }, { authMode: 'api-key' });
const b = estimateCost({ critical: 1, high: 1, medium: 100, low: 100 }, { authMode: 'api-key' });
assert.equal(a.usd, b.usd);
assert.equal(a.tokens_input, b.tokens_input);
});
test('estimateCost — long-oauth returns null usd, kvote_warn flag set', () => {
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'long-oauth' });
assert.strictEqual(result.usd, null);
assert.strictEqual(result.kvote_warn, true);
});
test('estimateCost — subscription-browser-only returns null usd, kvote_warn flag set', () => {
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'subscription-browser-only' });
assert.strictEqual(result.usd, null);
assert.strictEqual(result.kvote_warn, true);
});
test('estimateCost — auth-mode does not affect token math', () => {
const apikey = estimateCost({ critical: 5, high: 10 }, { authMode: 'api-key' });
const oauth = estimateCost({ critical: 5, high: 10 }, { authMode: 'long-oauth' });
const sub = estimateCost({ critical: 5, high: 10 }, { authMode: 'subscription-browser-only' });
assert.equal(apikey.tokens_input, oauth.tokens_input);
assert.equal(apikey.tokens_input, sub.tokens_input);
assert.equal(apikey.tokens_output, oauth.tokens_output);
assert.equal(apikey.tokens_output, sub.tokens_output);
});
test('estimateCost — unauthenticated treated as best-effort api-key', () => {
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'unauthenticated' });
assert.equal(typeof result.usd, 'number');
assert.equal(result.kvote_warn, false);
});
test('estimateCost — missing authMode opt treated as best-effort api-key', () => {
const result = estimateCost({ critical: 3, high: 15 });
assert.equal(typeof result.usd, 'number');
assert.equal(result.kvote_warn, false);
});
test('estimateCost — unknown priority keys are ignored', () => {
const result = estimateCost({ critical: 1, high: 1, weird: 999 }, { authMode: 'api-key' });
// Should equal {critical:1, high:1} alone
const baseline = estimateCost({ critical: 1, high: 1 }, { authMode: 'api-key' });
assert.equal(result.usd, baseline.usd);
});
test('estimateCost — fixture {critical: 3, high: 15} produces expected order of magnitude', () => {
// 18 files * (3000 in + 1500 out) tokens = 54k in, 27k out
// api-key cost: 54k * $3/M + 27k * $15/M = $0.162 + $0.405 = $0.567
const result = estimateCost({ critical: 3, high: 15 }, { authMode: 'api-key' });
assert.ok(result.usd > 0.4 && result.usd < 0.8, `expected ~$0.567, got $${result.usd}`);
});