feat: add user AI spend endpoint - #26978
Conversation
Docs preview📖 View docs preview for |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
78d6c24 to
9c05813
Compare
|
/coder-agents-review |
|
Chat: Review posted | View chat Review history
deep-review v0.9.0 | Round 1 | Last posted: Round 1, 8 findings (1 P2, 2 P3, 1 P4, 2 Nit, 2 Note), COMMENT. Review Finding inventoryFindings
Round logRound 1Panel. Netero clean. 1 P2, 2 P3, 1 P4, 2 Nit, 2 Note new. 2 dropped. Reviewed against 7c78698..76c0253. About deep-reviewCRF = Coder Review Finding (P0-P4, Nit, Note)
|
9c05813 to
76c0253
Compare
There was a problem hiding this comment.
Clean mechanical floor (Netero found no convention or structural issues). The endpoint is well-structured: correct middleware gating, proper error handling with structured logging, deterministic test suite with a mock clock, and good branch coverage across no-budget, group-budget, and user-override scenarios.
Severity count: 1 P2, 2 P3, 1 P4, 2 Nit, 2 Note.
The P2 is an API contract issue: CurrentSpendMicros uses a bare int64 while the sibling fields use pointers to distinguish "no budget" from zero. Once this ships, changing the field type is a breaking change.
"A caller seeing current_spend_micros: 0 cannot tell whether the user spent nothing or whether spend is not tracked. The nullable fields already established the convention; the spend field breaks it." (Hisoka)
Five reviewers independently flagged the hardcoded AIBudgetPeriodMonth (CRF-3). The config field BudgetPeriod exists at codersdk/deployment.go:4611 and is unused. The sibling caller at coderd/aibridged/http.go:152 has the same issue.
🤖 This review was automatically generated with Coder Agents.
| user := httpmw.UserParam(r) | ||
| logger := api.Logger.With(slog.F("user_id", user.ID)) | ||
|
|
||
| periodWindow, err := budget.CurrentPeriod(api.Clock.Now(), codersdk.AIBudgetPeriodMonth) |
There was a problem hiding this comment.
P3 [CRF-3] codersdk.AIBudgetPeriodMonth is hardcoded instead of reading from api.DeploymentValues.AI.BridgeConfig.BudgetPeriod.
The handler reads BudgetPolicy from config (line 889) but hardcodes the period. The config field BudgetPeriod is declared with serpent.EnumOf at codersdk/deployment.go:4611 and defaults to "month". Today only "month" exists, so behavior is correct. But this is the second caller to hardcode it (coderd/aibridged/http.go:152 does the same). When a second period type is added, both callers will silently use the wrong window.
Fix: codersdk.AIBudgetPeriod(api.DeploymentValues.AI.BridgeConfig.BudgetPeriod). The sibling in aibridged/http.go should get the same treatment. (Hisoka, Mafuuu, Ryosuke P3, Knov, Kite)
🤖
There was a problem hiding this comment.
Deferring to a follow-up PR because the fix should land in the endpoint, the daemon enforcement path, and the RPC test simultaneously.
| const ( | ||
| // AIBudgetLimitSourceUserOverride indicates the limit came from a | ||
| // per-user override. | ||
| AIBudgetLimitSourceUserOverride AIBudgetLimitSource = "user_override" |
There was a problem hiding this comment.
Note [CRF-8] The pre-existing manual frontend type UserAISpend in site/src/api/api.ts:231 uses "override" for the user-override source, while this constant is "user_override". Currently harmless (the frontend never inspects limit_source), but the migration from the manual type to the generated UserAISpendStatus will change the wire value. The AIGOV-473 ticket should capture this discrepancy. (Pariston)
🤖
76c0253 to
b91c682
Compare
| user := httpmw.UserParam(r) | ||
| logger := api.Logger.With(slog.F("user_id", user.ID)) | ||
|
|
||
| periodWindow, err := budget.CurrentPeriod(api.Clock.Now(), codersdk.AIBudgetPeriodMonth) |
There was a problem hiding this comment.
Since we'll support additional periods later, I wonder if we should move this behind a func (which always returns a static value for now).
Once the period is configurable, we'll just have one place to update it and everywhere will get the new value.
There was a problem hiding this comment.
This is related to #26978 (comment) and will be addressed in a follow-up PR, since the period source should be consistent across the endpoint and the aibridge daemon.
2323d91 to
9f18e80
Compare

Description
Adds the
GET /api/v2/users/{user}/ai/spendendpoint returning the user's current AI spend, effective budget, and period bounds.Changes
userAISpendStatushandler under the same feature/experiment gate as/api/v2/users/{user}/ai/budget.codersdk.UserAIBudgetSummary(embedded intoUserAISpendStatus) and aUserAISpendStatusclient method.LimitSourcefromcoderd/aibridge/budgettocodersdkso the type is shared across endpoints.Closes https://linear.app/codercom/issue/AIGOV-472
Note
Initially generated by Claude Opus 4.7, modified and reviewed by @ssncferreira