@bongos/core 1.19.176 → 1.19.177
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +11 -11
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/llm-pricing.js +37 -8
- package/src/module-api.js +3 -1
- package/tests/cost_reconcile.mjs +30 -0
- package/tests/llm-pricing.mjs +68 -0
package/.bongos-core.json
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.177",
|
|
6
|
+
"core_contract": "1.19.177",
|
|
7
|
+
"source_commit": "ec902233a41d6187d97c1b0761ca59b57ae3b180",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-08-16T20:
|
|
9
|
+
"built_at": "2026-08-16T20:47:16.225Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 306,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
19
|
"file_count": 1600,
|
|
20
|
-
"tree_sha256": "
|
|
20
|
+
"tree_sha256": "07b51c6ba546cfe6dc3f252283ffdbf5fa06230b8a86baeb82b82311d5e11dee",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/blocker-review/SKILL.md",
|
|
@@ -4227,12 +4227,12 @@
|
|
|
4227
4227
|
{
|
|
4228
4228
|
"path": "package-lock.json",
|
|
4229
4229
|
"mode": "0000644",
|
|
4230
|
-
"sha256": "
|
|
4230
|
+
"sha256": "bd31fbe4175062649acbd10eba6caca643e13941a4d8a366bd5ba2625e7ac92d"
|
|
4231
4231
|
},
|
|
4232
4232
|
{
|
|
4233
4233
|
"path": "package.json",
|
|
4234
4234
|
"mode": "0000644",
|
|
4235
|
-
"sha256": "
|
|
4235
|
+
"sha256": "f5fe7936f9d6739c7268697daab41d5520a0da60bc2b63099d8bd7145e977091"
|
|
4236
4236
|
},
|
|
4237
4237
|
{
|
|
4238
4238
|
"path": "public-docs/index.html",
|
|
@@ -5642,7 +5642,7 @@
|
|
|
5642
5642
|
{
|
|
5643
5643
|
"path": "src/bongos/llm-pricing.js",
|
|
5644
5644
|
"mode": "0000644",
|
|
5645
|
-
"sha256": "
|
|
5645
|
+
"sha256": "f7688e75a873f9c287f6559311877081830798cb1b5f1e548ceff3fcbed9e31d"
|
|
5646
5646
|
},
|
|
5647
5647
|
{
|
|
5648
5648
|
"path": "src/bongos/metrics.js",
|
|
@@ -5837,7 +5837,7 @@
|
|
|
5837
5837
|
{
|
|
5838
5838
|
"path": "src/module-api.js",
|
|
5839
5839
|
"mode": "0000644",
|
|
5840
|
-
"sha256": "
|
|
5840
|
+
"sha256": "5904881edeeaeedb873a2968c36a01b3c2faf6e79112bd56653f28692aa5267b"
|
|
5841
5841
|
},
|
|
5842
5842
|
{
|
|
5843
5843
|
"path": "src/module-loader/catalog.js",
|
|
@@ -6367,7 +6367,7 @@
|
|
|
6367
6367
|
{
|
|
6368
6368
|
"path": "tests/cost_reconcile.mjs",
|
|
6369
6369
|
"mode": "0000644",
|
|
6370
|
-
"sha256": "
|
|
6370
|
+
"sha256": "8356250d8fc753afb2960d867f81586531053afd15f4e66da1a1b7b62b6b5db7"
|
|
6371
6371
|
},
|
|
6372
6372
|
{
|
|
6373
6373
|
"path": "tests/cost_summary_shape.mjs",
|
|
@@ -7102,7 +7102,7 @@
|
|
|
7102
7102
|
{
|
|
7103
7103
|
"path": "tests/llm-pricing.mjs",
|
|
7104
7104
|
"mode": "0000644",
|
|
7105
|
-
"sha256": "
|
|
7105
|
+
"sha256": "de3fabaeafb3eb3ad500becf565f3b6d7118a763f1e80d085f94b6ba990d5c58"
|
|
7106
7106
|
},
|
|
7107
7107
|
{
|
|
7108
7108
|
"path": "tests/local_preview.mjs",
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.177",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.177",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.177",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -34,20 +34,42 @@
|
|
|
34
34
|
// Family-based pricing assumes the CURRENT model in each family (what this
|
|
35
35
|
// instance runs). A historical id like claude-opus-4-1 ($15/$75) would be
|
|
36
36
|
// mis-priced low here — acceptable: the platform only runs 4.x-current models.
|
|
37
|
+
//
|
|
38
|
+
// VERIFIED 2026-08-16 against the live Anthropic pricing page (task 1002883):
|
|
39
|
+
// added the `fable` family. familyFor() previously matched only opus/sonnet/
|
|
40
|
+
// haiku, so claude-fable-5 — the owner's top-model default and this instance's
|
|
41
|
+
// `assumed_builder_model` (modules/grading/grader-rubric.json) — fell through to
|
|
42
|
+
// null and was priced at the opus fallback with unknown_model=true. Fable is
|
|
43
|
+
// 2× opus, so every cost-plus reward computed from a Fable session was priced at
|
|
44
|
+
// HALF its true cost (not conservative: cost-plus UNDER-pays the builder). The
|
|
45
|
+
// grading side already carried the family token (grader.js MODEL_FAMILY_TOKENS);
|
|
46
|
+
// this table is the surface that never caught up.
|
|
47
|
+
// - fable = Claude Fable 5 / Mythos 5 ($10 in / $50 out) — identical rates,
|
|
48
|
+
// which is why one family token covers both.
|
|
49
|
+
// - opus = Claude Opus 5 / 4.8 / 4.7 ($5 in / $25 out) [re-verified]
|
|
50
|
+
// - sonnet = Claude Sonnet 4.6 ($3 in / $15 out) [re-verified]
|
|
51
|
+
// - haiku = Claude Haiku 4.5 ($1 in / $5 out) [re-verified]
|
|
52
|
+
// NOTE (deliberately NOT changed here — its own task, per the rule above):
|
|
53
|
+
// Claude Sonnet 5 is now $2/$10, not the $3/$15 this `sonnet` row carries. The
|
|
54
|
+
// grader's default worker model is claude-sonnet-5, so grade costs are currently
|
|
55
|
+
// OVER-priced ~1.5×. That is a separate price-table decision; see task 1003001.
|
|
37
56
|
const PRICES = {
|
|
38
57
|
// family : { input, output, cache_write_5m, cache_write_1h, cache_read } (USD / 1e6 tokens)
|
|
58
|
+
fable: { input: 10, output: 50, cache_write_5m: 12.5, cache_write_1h: 20, cache_read: 1 },
|
|
39
59
|
opus: { input: 5, output: 25, cache_write_5m: 6.25, cache_write_1h: 10, cache_read: 0.5 },
|
|
40
60
|
sonnet: { input: 3, output: 15, cache_write_5m: 3.75, cache_write_1h: 6, cache_read: 0.3 },
|
|
41
61
|
haiku: { input: 1, output: 5, cache_write_5m: 1.25, cache_write_1h: 2, cache_read: 0.1 },
|
|
42
62
|
};
|
|
43
63
|
|
|
44
|
-
// Resolve a model id (e.g. 'claude-
|
|
45
|
-
// 'claude-haiku-4-5') to a price family by substring —
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
64
|
+
// Resolve a model id (e.g. 'claude-fable-5', 'claude-opus-4-8',
|
|
65
|
+
// 'claude-sonnet-4-6', 'claude-haiku-4-5') to a price family by substring —
|
|
66
|
+
// mirrors grader.js's MODEL_FAMILY_TOKENS convention, including the fable/mythos
|
|
67
|
+
// pair it checks first (all live Claude ids contain one of these tokens).
|
|
68
|
+
// Unknown → null (caller decides: we price at the opus rate AND flag it, so an
|
|
69
|
+
// unpriced model is loud, never silently $0 — see priceUsage on why opus).
|
|
49
70
|
function familyFor(model) {
|
|
50
71
|
const m = String(model || '').toLowerCase();
|
|
72
|
+
if (m.includes('fable') || m.includes('mythos')) return 'fable';
|
|
51
73
|
if (m.includes('opus')) return 'opus';
|
|
52
74
|
if (m.includes('sonnet')) return 'sonnet';
|
|
53
75
|
if (m.includes('haiku')) return 'haiku';
|
|
@@ -70,7 +92,14 @@ function priceUsage(model, usage) {
|
|
|
70
92
|
const u = usage || {};
|
|
71
93
|
const fam = familyFor(model);
|
|
72
94
|
const unknownModel = fam === null;
|
|
73
|
-
|
|
95
|
+
// Unrecognized → price at the opus rate. Since `fable` landed (task 1002883)
|
|
96
|
+
// opus is no longer the ceiling, so this is a MID-range fallback, not a
|
|
97
|
+
// conservative one: an unknown Fable-class model prices at half its true rate.
|
|
98
|
+
// That is why unknown_model=true rides along — the loudness is the safeguard,
|
|
99
|
+
// not the rate. (Deliberately left at opus: every model this platform actually
|
|
100
|
+
// runs is now matched, so raising the fallback would only over-price genuinely
|
|
101
|
+
// foreign ids like gpt-4o.)
|
|
102
|
+
const priceFamily = fam || 'opus';
|
|
74
103
|
const p = PRICES[priceFamily];
|
|
75
104
|
|
|
76
105
|
const input = Number(u.input_tokens) || 0; // uncached input
|
|
@@ -179,7 +208,7 @@ function priceModelUsage(modelUsage) {
|
|
|
179
208
|
* Like priceModelUsage, model_usage has NO 5m/1h cache-creation breakdown — the
|
|
180
209
|
* `cache_creation` lump is priced at the 5m rate, exactly as session-cost.js
|
|
181
210
|
* priceTranscript does in its `else` branch (no breakdown → 5m). Unknown model
|
|
182
|
-
* ids are
|
|
211
|
+
* ids are priced as opus AND collected, so an unpriced model is
|
|
183
212
|
* loud, never silently $0. Pure: no I/O, no Date.now().
|
|
184
213
|
*
|
|
185
214
|
* @param {Object<string, {input?:number,output?:number,cache_read?:number,cache_creation?:number}>} modelUsage
|
|
@@ -199,7 +228,7 @@ function componentsFromModelUsage(modelUsage) {
|
|
|
199
228
|
const usage = u || {};
|
|
200
229
|
const fam = familyFor(rawId);
|
|
201
230
|
if (fam === null) unknown.push(canonicalizeModelId(rawId));
|
|
202
|
-
const p = PRICES[fam || 'opus']; //
|
|
231
|
+
const p = PRICES[fam || 'opus']; // mid-range fallback; unknown ids ride the `unknown` list
|
|
203
232
|
fresh_input_usd += ((Number(usage.input) || 0) * p.input) / 1e6;
|
|
204
233
|
output_usd += ((Number(usage.output) || 0) * p.output) / 1e6;
|
|
205
234
|
cache_read_usd += ((Number(usage.cache_read) || 0) * p.cache_read) / 1e6;
|
package/src/module-api.js
CHANGED
|
@@ -779,8 +779,10 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
779
779
|
// landed since 1.19.174 with no explicit bump. run 31970199477. (task 1002620)
|
|
780
780
|
// 1.19.176 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
781
781
|
// landed since 1.19.175 with no explicit bump. run 31971125373. (task 1002620)
|
|
782
|
+
// 1.19.177 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
783
|
+
// landed since 1.19.176 with no explicit bump. run 31971532260. (task 1002620)
|
|
782
784
|
// ---------------------------------------------------------------------------
|
|
783
|
-
const CORE_VERSION = '1.19.
|
|
785
|
+
const CORE_VERSION = '1.19.177'; // CI auto-patch carrier (ADR 0161); see changelog above
|
|
784
786
|
|
|
785
787
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
786
788
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/tests/cost_reconcile.mjs
CHANGED
|
@@ -100,3 +100,33 @@ test('1366 missing per-tier fields default to 0 (partial model_usage)', () => {
|
|
|
100
100
|
assert.equal(out.cache_write_usd, 0);
|
|
101
101
|
assert.ok(near(out.total_usd, 5));
|
|
102
102
|
});
|
|
103
|
+
|
|
104
|
+
// ---------- fable / mythos family (task 1002883) ----------
|
|
105
|
+
// The reconciler is the SECOND pricing path the missing fable family broke: a
|
|
106
|
+
// cloud-box / overnight Fable session whose client cost hook never posted lands
|
|
107
|
+
// here, and before the fix it re-priced at the opus fallback — half the true
|
|
108
|
+
// cost, and flagged unknown on the platform's own default model.
|
|
109
|
+
|
|
110
|
+
test('1002883 fable 1M/1M/10M/1M → $10 / $50 / $10 / $12.50, total $82.50, not flagged', () => {
|
|
111
|
+
const out = componentsFromModelUsage({
|
|
112
|
+
'claude-fable-5': { input: M, output: M, cache_read: 10 * M, cache_creation: M },
|
|
113
|
+
});
|
|
114
|
+
assert.ok(near(out.fresh_input_usd, 10), `fresh ${out.fresh_input_usd}`);
|
|
115
|
+
assert.ok(near(out.output_usd, 50), `output ${out.output_usd}`);
|
|
116
|
+
assert.ok(near(out.cache_read_usd, 10), `cache_read ${out.cache_read_usd}`); // 10M * 1/M
|
|
117
|
+
assert.ok(near(out.cache_write_usd, 12.5), `cache_write ${out.cache_write_usd}`);
|
|
118
|
+
assert.ok(near(out.total_usd, 82.5), `total ${out.total_usd}`);
|
|
119
|
+
const sum = out.fresh_input_usd + out.cache_read_usd + out.cache_write_usd + out.output_usd;
|
|
120
|
+
assert.ok(near(sum, out.total_usd), `tiers sum ${sum} must equal total ${out.total_usd}`);
|
|
121
|
+
assert.deepEqual(out.unknown_models, [], 'fable is the platform default — never "unknown"');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('1002883 mythos prices as fable, and a fable session is 2× the same opus session', () => {
|
|
125
|
+
const usage = { input: M, output: M };
|
|
126
|
+
const fable = componentsFromModelUsage({ 'claude-fable-5': usage });
|
|
127
|
+
const mythos = componentsFromModelUsage({ 'claude-mythos-5': usage });
|
|
128
|
+
const opus = componentsFromModelUsage({ 'claude-opus-5': usage });
|
|
129
|
+
assert.ok(near(fable.total_usd, mythos.total_usd), 'mythos shares fable rates');
|
|
130
|
+
assert.deepEqual(mythos.unknown_models, []);
|
|
131
|
+
assert.ok(near(fable.total_usd, opus.total_usd * 2), `fable ${fable.total_usd} != 2× opus ${opus.total_usd}`);
|
|
132
|
+
});
|
package/tests/llm-pricing.mjs
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// Prices VERIFIED 2026-06-20 against the live Anthropic models/pricing page
|
|
3
3
|
// (GDS-V4 task 1343): opus (4.8/4.7) $5/$25, sonnet (4.6) $3/$15, haiku (4.5)
|
|
4
4
|
// $1/$5; flat across the 1M window (no long-context premium).
|
|
5
|
+
// Re-VERIFIED + extended 2026-08-16 (task 1002883): fable (Fable 5 / Mythos 5)
|
|
6
|
+
// $10/$50 — the family familyFor() used to miss, so Fable sessions priced at the
|
|
7
|
+
// opus fallback and cost-plus under-paid by half.
|
|
5
8
|
// Run: node tests/llm-pricing.mjs
|
|
6
9
|
|
|
7
10
|
import { strict as assert } from 'node:assert';
|
|
@@ -18,6 +21,9 @@ const M = 1_000_000;
|
|
|
18
21
|
const near = (a, b) => Math.abs(a - b) < 1e-6;
|
|
19
22
|
|
|
20
23
|
test('familyFor matches by substring; unknown → null', () => {
|
|
24
|
+
assert.equal(familyFor('claude-fable-5'), 'fable');
|
|
25
|
+
assert.equal(familyFor('claude-mythos-5'), 'fable'); // same rates → same family
|
|
26
|
+
assert.equal(familyFor('claude-mythos-preview'), 'fable');
|
|
21
27
|
assert.equal(familyFor('claude-opus-4-8'), 'opus');
|
|
22
28
|
assert.equal(familyFor('claude-sonnet-4-6'), 'sonnet');
|
|
23
29
|
assert.equal(familyFor('claude-haiku-4-5'), 'haiku');
|
|
@@ -26,6 +32,68 @@ test('familyFor matches by substring; unknown → null', () => {
|
|
|
26
32
|
assert.equal(familyFor(undefined), null);
|
|
27
33
|
});
|
|
28
34
|
|
|
35
|
+
// ---------- fable / mythos family (task 1002883) ----------
|
|
36
|
+
// The regression this locks: claude-fable-5 is the owner's top-model default and
|
|
37
|
+
// the grader's assumed_builder_model, but familyFor() matched only opus/sonnet/
|
|
38
|
+
// haiku — so it fell to the opus fallback (unknown_model=true) at HALF its rate,
|
|
39
|
+
// and every cost-plus reward from a Fable session under-paid the builder.
|
|
40
|
+
|
|
41
|
+
test('fable 5: 1M input + 1M output = $10 + $50 = $60, and is NOT flagged unknown', () => {
|
|
42
|
+
const r = priceUsage('claude-fable-5', { input_tokens: M, output_tokens: M });
|
|
43
|
+
assert.ok(near(r.usd, 60), `got ${r.usd}`);
|
|
44
|
+
assert.equal(r.unknown_model, false, 'fable must resolve, not ride the opus fallback');
|
|
45
|
+
assert.equal(r.family, 'fable');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('mythos 5 prices identically to fable 5 (one family, shared rates)', () => {
|
|
49
|
+
const f = priceUsage('claude-fable-5', { input_tokens: M, output_tokens: M });
|
|
50
|
+
const m = priceUsage('claude-mythos-5', { input_tokens: M, output_tokens: M });
|
|
51
|
+
assert.ok(near(f.usd, m.usd), `fable ${f.usd} != mythos ${m.usd}`);
|
|
52
|
+
assert.equal(m.family, 'fable');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('fable cache tiers: read $1, 5m write $12.50, 1h write $20 per 1M', () => {
|
|
56
|
+
assert.ok(near(priceUsage('claude-fable-5', { cache_read_input_tokens: M }).usd, 1));
|
|
57
|
+
assert.ok(near(priceUsage('claude-fable-5', {
|
|
58
|
+
cache_creation_input_tokens: M,
|
|
59
|
+
cache_creation: { ephemeral_5m_input_tokens: M, ephemeral_1h_input_tokens: 0 },
|
|
60
|
+
}).usd, 12.5));
|
|
61
|
+
assert.ok(near(priceUsage('claude-fable-5', {
|
|
62
|
+
cache_creation_input_tokens: M,
|
|
63
|
+
cache_creation: { ephemeral_5m_input_tokens: 0, ephemeral_1h_input_tokens: M },
|
|
64
|
+
}).usd, 20));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('fable rides the published cache multipliers off its own base input (1.25× / 2× / 0.1×)', () => {
|
|
68
|
+
const { fable } = PRICES;
|
|
69
|
+
assert.ok(near(fable.cache_write_5m, fable.input * 1.25));
|
|
70
|
+
assert.ok(near(fable.cache_write_1h, fable.input * 2));
|
|
71
|
+
assert.ok(near(fable.cache_read, fable.input * 0.1));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('fable is exactly 2× opus — the size of the pre-fix cost-plus shortfall', () => {
|
|
75
|
+
const usage = { input_tokens: M, output_tokens: M };
|
|
76
|
+
const fable = priceUsage('claude-fable-5', usage).usd;
|
|
77
|
+
const opus = priceUsage('claude-opus-5', usage).usd;
|
|
78
|
+
assert.ok(near(fable, opus * 2), `fable ${fable} != 2× opus ${opus}`);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('priceModelUsage: a fable session costs $60 / 1M+1M and reports no unknown models', () => {
|
|
82
|
+
const r = priceModelUsage({ 'claude-fable-5': { input: M, output: M } });
|
|
83
|
+
assert.ok(near(r.true_cost_usd, 60), `got ${r.true_cost_usd}`);
|
|
84
|
+
assert.deepEqual(r.unknown_models, [], 'fable must not be reported as unpriced');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('canonicalizeModelId collapses fable variants without changing the price', () => {
|
|
88
|
+
assert.equal(canonicalizeModelId('claude-fable-5[1m]'), 'claude-fable-5');
|
|
89
|
+
const r = priceModelUsage({
|
|
90
|
+
'claude-fable-5': { input: M },
|
|
91
|
+
'claude-fable-5[1m]': { input: M },
|
|
92
|
+
});
|
|
93
|
+
assert.equal(r.per_model.length, 2, 'raw keys kept as separate breakdown rows');
|
|
94
|
+
assert.ok(near(r.true_cost_usd, 20), `got ${r.true_cost_usd}`); // 10 + 10
|
|
95
|
+
});
|
|
96
|
+
|
|
29
97
|
test('opus 4.8: 1M input + 1M output = $5 + $25 = $30', () => {
|
|
30
98
|
const r = priceUsage('claude-opus-4-8', { input_tokens: M, output_tokens: M });
|
|
31
99
|
assert.ok(near(r.usd, 30), `got ${r.usd}`);
|