@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 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.176",
6
- "core_contract": "1.19.176",
7
- "source_commit": "9b6c711a358a67610bdb81e8776bc689a0b9cdd8",
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:39:00.850Z",
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": "fe346cc282c27c1ec0f17e6dc13d3a82795f14343fa9e42f120b3df9a3138546",
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": "07c720a5ef0a0e1e93727688e9c6c87e00ab43f289efa7cbb8eebea6f3cdbba9"
4230
+ "sha256": "bd31fbe4175062649acbd10eba6caca643e13941a4d8a366bd5ba2625e7ac92d"
4231
4231
  },
4232
4232
  {
4233
4233
  "path": "package.json",
4234
4234
  "mode": "0000644",
4235
- "sha256": "d014a9180d2a66087029d00f9f10da48fbc8ee3be7a88db11280bc35da7fd611"
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": "c421166d7d5099f96b2b46cf9fb46b2c0b3594fb59d318c1a512751bd00a9969"
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": "9459525829ea38080db6cc5e757ef7be130c3da00c6563324e892bf605313d8d"
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": "a94de0a9ebd15b8aeb80fa2fc976d847334e733bd0c2a65a869bac5c4b6b4dd8"
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": "dec4e9ab447bfac75c6298c6f47d0f0f85e643c7519e192ca704d9a93a834499"
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.176",
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.176",
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.176",
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-opus-4-8', 'claude-sonnet-4-6',
45
- // 'claude-haiku-4-5') to a price family by substring — mirrors grader.js's
46
- // family-token convention (all live Claude ids contain one of these three).
47
- // Unknown → null (caller decides: we conservatively price at the opus rate AND
48
- // flag it, so an unpriced model is loud, never silently $0).
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
- const priceFamily = fam || 'opus'; // conservative default for an unrecognized model
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 conservatively priced as opus AND collected, so an unpriced model is
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']; // conservative default for an unrecognized model
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.176'; // CI auto-patch carrier (ADR 0161); see changelog above
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');
@@ -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
+ });
@@ -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}`);