feat(billing): agent-side spend records who was paid
Judge spend gained provider, model and mission on 2026-09-14; agent spend — the larger half — did not. The runtime's `done` frame has always carried `model` and `provider` beside the two token counts, and `topology_exec` read only the counts, summed them, and charged the sum as output with no record of which provider served the turn. `TurnOutcome` and `StepRecord` carry a `Spend` now (input/output split, provider, model), the worker passes it through `cm_billing::charge` along with the mission id, and the chat runtime records the model it requested — that loop drives one provider with no chain, so requested is answered. A bare model name is recorded without a guessed family. `StepRecord.spend` is `serde(default)` so journaled checkpoints from before this field still load, and `tokens` stays as the total every reader keys on. `charge` moved from `query!` to `query`: the macro pins the statement to offline metadata that a schema change then has to regenerate against a live database, for columns that are nullable text and uuid. The done-frame test now asserts the split and the provider survive, not just the sum. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01WZb5A2kfVfjpdwSochkuHz
This commit is contained in:
co-authored by
Claude Opus 5
parent
736b6a9a82
commit
483de9f88a
@@ -875,6 +875,13 @@ impl Runtime {
|
||||
);
|
||||
// Meter the run (§8.4). Billing failures never fail the run — the
|
||||
// usage ledger is the recovery path.
|
||||
// This loop drives ONE provider with no fallback chain, so the model
|
||||
// the request named is the model that answered. The family is taken
|
||||
// only from an explicit `provider:` prefix — a bare model name is
|
||||
// recorded as-is with no family rather than guessed at, and a chat
|
||||
// run is not a mission.
|
||||
let model = state.request.model.as_str();
|
||||
let provider = model.split_once(':').map(|(p, _)| p);
|
||||
if let Err(error) = cm_billing::charge(
|
||||
&self.inner.pool,
|
||||
state.workspace_id,
|
||||
@@ -882,6 +889,9 @@ impl Runtime {
|
||||
Some(run_id),
|
||||
state.input_tokens,
|
||||
state.output_tokens,
|
||||
provider,
|
||||
Some(model),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user