mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 08:53:22 +00:00
fix(cycle): BudgetMeter prices from the derived Anthropic view, so the cap is off for canonically-priced models (#3951)
* fix(cycle): price BudgetMeter through the canonical table, not the derived Anthropic view BudgetMeter estimated submit cost with `estimateMaxCostUsd`, which reads ANTHROPIC_PRICING. CLAUDE.md defines that table as a DERIVED view of the one canonical chat-pricing table, so any non-Anthropic model was unpriceable here even when CANONICAL_PRICING carries its rates — and an unpriceable model does not merely lose accuracy, it takes the `cost === null` branch and returns `allowed: true` with cost 0. The cap is off for that model. Measured on130d321d: openai:gpt-5.2 and deepseek:deepseek-chat are both in CANONICAL_PRICING and both absent from the derived view, so the gate was disabled for them. claude-opus-4-7 resolves through either. The estimate now goes through canonicalLookup, falling back to the existing call for anything canonical does not carry. Anthropic numbers are unchanged by construction — the derived view is generated from canonical — and a test pins that parity. Deliberately NOT changed: a model absent from canonical too keeps the documented warn-and-allow bypass, with the same BUDGET_METER_NO_PRICING warning, the same submit_unpriced ledger event, and the same `unpricedSubmits` counter. Whether those should be priced at a conservative fallback rate instead — as synthesize-concepts (#3915) and skillopt/preflight already do — is a policy call, not this fix. The ledger schema is untouched; `test/fixtures/dream-budget-schema-v1.jsonl` still describes it. Related to #2149 (self-closed by its reporter without a fix; the pricing half of what it described is still live). Distinct from #2504, which is the opposite failure mode in BudgetTracker: hard-throw on a missing entry rather than silent bypass. Verification on130d321d(v0.42.76.0): - bun test budget-meter + auto-think-phase + propose-takes + model-pricing -> 93 pass / 0 fail - red check: with upstream/master's budget-meter.ts -> 10 pass / 1 fail on the new gating test - bun run typecheck -> clean - bun run verify -> 34/34 green Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(cycle): guard the non-finite estimate, and correct the docs the change makes stale Codex review, both verified before applying: - Both pricing tables are object literals, so a model id colliding with an inherited key ('constructor', 'toString', '__proto__') resolves to a truthy Object.prototype value whose .input/.output are undefined and the rate arithmetic yields NaN. Measured: canonicalLookup('constructor') returns a function, and estimateMaxCostUsd('constructor', …) already returns NaN on master, so the shape predates this change — but it sits on the path this patch touches. `cumulative + NaN` is NaN and `NaN > budget` is false, so one such submit would disable the gate for the rest of the cycle. The estimate helper now treats a non-finite result as unpriceable, which routes that one submit into the documented warn-and-allow branch and leaves the running total finite. Regression test submits the poison id, then a submit that must be denied. The same shape on the estimateMaxCostUsd path is left alone. - Two docs asserted the old behaviour and are now current-state: the anthropic-pricing.ts header ("non-Anthropic models … bypass … runs unbounded") and the KEY_FILES.md entry for the same file. Both now say the bypass applies only when canonical has no rates either. `bun run build:llms` regenerated; the bundles are unchanged because KEY_FILES.md is linked rather than inlined, and test/build-llms.test.ts passes. Verification on130d321d(v0.42.76.0): - bun test budget-meter + auto-think-phase + propose-takes + model-pricing + build-llms -> 106 pass / 0 fail - bun run typecheck -> clean - bun run verify -> 34/34 green Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -132,7 +132,7 @@ per-release `**vX.Y.Z:**` narration — CI enforces this
|
||||
- `src/core/ai/recipes/voyage.ts` — Voyage AI openai-compatible recipe. Declares `chars_per_token=1` + `safety_factor=0.5` so the gateway pre-splits Voyage batches at a 60K-character budget (50% of 120K-token cap with the dense-tokenizer ratio), avoiding the backfill loop where tiktoken-grounded budgeting undercounted Voyage's actual token usage. Declares `multimodal_models: ['voyage-multimodal-3']` so the gateway rejects text-only Voyage models pointed at the multimodal endpoint with a clear `AIConfigError` instead of waiting for Voyage's HTTP 400. Recipe docstring at `:7-16` names the seven hosted flexible-dim models that accept `output_dimension` (`voyage-4-large`, `voyage-4`, `voyage-4-lite`, `voyage-3-large`, `voyage-3.5`, `voyage-3.5-lite`, `voyage-code-3`) and notes `voyage-4-nano` is the open-weight variant fixed at 1024-dim that does NOT accept the parameter (negative regression assertion in `test/ai/gateway.test.ts`: `dimsProviderOptions` returns `undefined` for `voyage-4-nano`). `voyage-code-3` is the recommended embedding model for gstack per-worktree code brains (Topology 3 in `docs/architecture/topologies.md`); discoverability surfaces: decision-tree branch in `docs/integrations/embedding-providers.md`, Topology 3 "Recommended embedding model" subsection, runtime nudge from `gbrain reindex --code` against non-code-tuned models. Recipe-shape regression pinned by `test/ai/voyage-code-3-recipe.test.ts`.
|
||||
- `src/core/ai/recipes/anthropic.ts` — Anthropic recipe (chat + expansion touchpoints). Canonical id is `claude-sonnet-4-6` (no date suffix); a reverse alias `claude-sonnet-4-6-20250929 → claude-sonnet-4-6` keeps stale user configs working (rescues `facts.extraction_model` and `models.dream.synthesize`). Recipe-shape regression pinned by `test/anthropic-model-ids.test.ts`.
|
||||
- `src/core/model-pricing.ts` — single source of truth for paid-cloud CHAT/completion model pricing (USD per 1M tokens, input | output). `CANONICAL_PRICING` is a `provider:model`-keyed table (Anthropic Opus 5/4.8/4.7/4.6 `$5/$25`, Sonnet 4.6 `$3/$15`, Haiku 4.5 `$1/$5` both dateless + dated, plus OpenAI / Google / Together / DeepSeek panel models). `canonicalLookup(modelId)` resolves bare (`claude-opus-4-8`), colon (`anthropic:claude-opus-4-8`), and slash (`anthropic/...`) forms — bare ids default to the `anthropic:` provider; nested OpenRouter ids (`openrouter:anthropic/...`) intentionally MISS so OpenRouter markup isn't repriced as the inner vendor. Every other chat-pricing table is a DERIVED view of this one (NOT a hand-copied duplicate), so cross-table price drift is structurally impossible. Embeddings live separately in `embedding-pricing.ts` (different unit). Pinned by `test/model-pricing.test.ts` whose drift guard asserts each derived view equals canonical and that the cross-modal panel models are all present.
|
||||
- `src/core/anthropic-pricing.ts` — bare-keyed Anthropic VIEW of `model-pricing.ts` (the `anthropic:` canonical entries with the prefix stripped). Kept distinct because many callers look up by bare Claude id and because `estimateMaxCostUsd(modelId, inTokens, maxOutTokens)` carries the null-on-miss contract the dream-cycle budget gate depends on (non-Anthropic ids return null → caller warns `BUDGET_METER_NO_PRICING` once and runs unbounded). `estimateMaxCostUsd` routes bare/colon/slash ids through `splitProviderModelId`. Do NOT hand-edit prices here — the map is derived from canonical, so it cannot drift. `ANTHROPIC_PRICING` is consumed by `budget/budget-tracker.ts`, `minions/batch-projection.ts`, and `cycle/budget-meter.ts`.
|
||||
- `src/core/anthropic-pricing.ts` — bare-keyed Anthropic VIEW of `model-pricing.ts` (the `anthropic:` canonical entries with the prefix stripped). Kept distinct because many callers look up by bare Claude id and because `estimateMaxCostUsd(modelId, inTokens, maxOutTokens)` carries the null-on-miss contract the dream-cycle budget gate depends on (non-Anthropic ids return null; `BudgetMeter` tries `canonicalLookup` first and only falls back here, so it warns `BUDGET_METER_NO_PRICING` and runs unbounded only when canonical has no rates either). `estimateMaxCostUsd` routes bare/colon/slash ids through `splitProviderModelId`. Do NOT hand-edit prices here — the map is derived from canonical, so it cannot drift. `ANTHROPIC_PRICING` is consumed by `budget/budget-tracker.ts`, `minions/batch-projection.ts`, and `cycle/budget-meter.ts`.
|
||||
- `src/core/takes-quality-eval/pricing.ts` — fail-closed budget pricing for `eval takes-quality run --budget-usd N`. `MODEL_PRICING` is a curated `provider:model` allowlist (default panel + likely overrides) whose VALUES are derived from `model-pricing.ts` via `canonicalLookup`; an allowlisted id missing from canonical throws at module load. Schema is `{input_per_1m, output_per_1m}`. A model NOT on the allowlist aborts the run with an actionable error rather than guessing (distinct from `cross-modal-eval/runner.ts`, which silently estimates zero on unknown models — both now source numbers from canonical).
|
||||
- `src/core/budget/budget-tracker.ts` — keystone primitive for the brainstorm cost-cathedral wave. One typed error (`BudgetExhausted` with `reason: 'cost' | 'runtime' | 'no_pricing'`), one schema-stable audit JSONL at `~/.gbrain/audit/budget-YYYY-Www.jsonl`. Contracts: `record()` throws when cumulative spend exceeds cap (the cap is a real ceiling, not a suggestion); `reserve()` hard-fails with `reason: 'no_pricing'` when `maxCostUsd` is set AND the model is missing from pricing maps (warn-once preserved when cap is unset); `extractUsageFromError(err, fallback)` returns `err.usage` when the SDK provides it, else the pessimistic fallback (caller passes `maxOutputTokens`, not the optimistic pre-call estimate). `onExhausted(cb)` fires once synchronously BEFORE the throw propagates so callers can persist checkpoints. Replaces three parallel copies (inline brainstorm class, cycle/budget-meter, eval-contradictions). Adapts the old `BudgetMeter` (public shape preserved + `schema_version: 1` stamped on every dream-budget audit line). Pinned by 18 unit cases.
|
||||
- `src/core/audit-week-file.ts` — single source of truth for ISO-week audit JSONL filename math. Exports `isoWeek(d)`, `isoWeekFilename(prefix, now?)`, `resolveAuditDir()` (honors `GBRAIN_AUDIT_DIR`). Year-boundary correctness pinned by tests at 2020-W53 (the 53-week year), 2025-W01 rolling in from 2024-12-30 (Monday), 2026-W01. Four call sites migrated: `src/core/minions/handlers/shell-audit.ts`, `src/core/facts/phantom-audit.ts`, `src/core/audit-slug-fallback.ts`, `src/core/cycle/budget-meter.ts`. Each keeps its `compute<X>AuditFilename` thin wrapper for back-compat with existing tests.
|
||||
|
||||
Reference in New Issue
Block a user