| 1 | package event |
| 2 | |
| 3 | import "reasonix/internal/nilutil" |
| 4 | |
| 5 | // RunBudgetTotals is one scope's accumulated spend: counts and money, never |
| 6 | // content. Priced is false when no price table covered it, so a reader can |
| 7 | // tell a genuinely free stretch from an unpriced one. |
| 8 | type RunBudgetTotals struct { |
| 9 | Rounds int `json:"rounds"` |
| 10 | Requests int `json:"requests"` |
| 11 | PromptTokens int `json:"promptTokens"` |
| 12 | OutputTokens int `json:"outputTokens"` |
| 13 | Cost float64 `json:"cost"` |
| 14 | Priced bool `json:"priced"` |
| 15 | ElapsedMs int64 `json:"elapsedMs"` |
| 16 | } |
| 17 | |
| 18 | // RunBudgetSample reports both scopes after a round. Turn is the current Run; |
| 19 | // Task spans every Run continuing the same work, because "continue" starts a |
| 20 | // new Run and the spend worth stopping accrues across them. |
| 21 | type RunBudgetSample struct { |
| 22 | Turn RunBudgetTotals `json:"turn"` |
| 23 | Task RunBudgetTotals `json:"task"` |
| 24 | Currency string `json:"currency,omitempty"` |
| 25 | } |
| 26 | |
| 27 | // RunBudgetSink is an optional sink capability for the per-round spend axis. |
| 28 | // Shadow means observed, not enforced: no threshold reads these yet. |
| 29 | type RunBudgetSink interface { |
| 30 | RecordRunBudget(RunBudgetSample) |
| 31 | } |
| 32 | |
| 33 | // RecordRunBudget forwards a turn's spend reading only to sinks that opt in. |
| 34 | // Ordinary UI sinks receive nothing. |
| 35 | func RecordRunBudget(s Sink, sample RunBudgetSample) { |
| 36 | if nilutil.IsNil(s) { |
| 37 | return |
| 38 | } |
| 39 | if rb, ok := s.(RunBudgetSink); ok { |
| 40 | rb.RecordRunBudget(sample) |
| 41 | } |
| 42 | } |
| 43 |