feat: remove native chat cost tracking in favor of AI Gateway cost data (#27330)

## Stack Context

This stack makes AI Gateway data and budgets the source of truth for AI
spend controls.

1. Re-back the per-chat cost endpoint with AI Gateway data (#27328,
merged).
2. Remove native chat usage limits (#27329, merged).
3. **This PR, now based on `main`:** remove native chat cost tracking
and its dedicated admin UI.

## Summary

Removes native per-message price calculation, model pricing fields, cost
persistence, aggregate cost queries, and admin cost API types. It also
deletes the Analytics and Spend pages plus their legacy redirects. The
AI Gateway-backed per-chat cost row and compact budget indicators
remain.

The spend documentation is renamed to `spend-management.md` and updated
for the remaining surfaces, group budget APIs, CSV export, upgrade
handling for native pricing and cost history, and the absence of a
deployment-wide spend dashboard. The per-chat cost API documents that
data follows AI Gateway retention and reports zero after all matching
requests are purged.

No schema is dropped in this release. `chat_messages.total_cost_micros`
remains nullable and unwritten so replicas from the previous release can
continue inserting messages during rolling upgrades. #27600 tracks
removal after the compatibility window.

> Mux prepared this PR on Mike's behalf.
This commit is contained in:
Michael Suchacz
2026-08-04 12:27:38 +02:00
committed by GitHub
parent 0b8b48913f
commit 6b8f820493
72 changed files with 159 additions and 5303 deletions
-13
View File
@@ -12276,7 +12276,6 @@ func TestInsertChatMessages(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -12337,7 +12336,6 @@ func TestInsertChatMessages(t *testing.T) {
CacheReadTokens: []int64{0, 0, 0},
ContextLimit: []int64{0, 0, 0},
Compressed: []bool{false, false, false},
TotalCostMicros: []int64{0, 100, 0},
RuntimeMs: []int64{0, 500, 0},
})
require.NoError(t, err)
@@ -12367,10 +12365,6 @@ func TestInsertChatMessages(t *testing.T) {
require.Equal(t, int64(20), msgs[1].OutputTokens.Int64)
// Verify cost: assistant has cost, others NULL.
require.True(t, msgs[1].TotalCostMicros.Valid)
require.Equal(t, int64(100), msgs[1].TotalCostMicros.Int64)
require.False(t, msgs[0].TotalCostMicros.Valid)
require.False(t, msgs[2].TotalCostMicros.Valid)
// Verify runtime_ms on assistant message.
require.True(t, msgs[1].RuntimeMs.Valid)
@@ -12414,7 +12408,6 @@ func insertChatMessagesInvertedTimestamps(t *testing.T, db database.Store, sqlDB
CacheReadTokens: make([]int64, count),
ContextLimit: make([]int64, count),
Compressed: make([]bool, count),
TotalCostMicros: make([]int64, count),
RuntimeMs: make([]int64, count),
})
require.NoError(t, err)
@@ -12586,7 +12579,6 @@ func TestGetChatMessagesForPromptByChatID(t *testing.T) {
CacheCreationTokens: []int64{0},
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -15610,7 +15602,6 @@ func TestUpdateChatLastTurnSummary(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -15732,7 +15723,6 @@ func TestUpdateChatSummary(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -17511,7 +17501,6 @@ func TestGetChatsFilter(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -17753,7 +17742,6 @@ func TestGetChatsSearch(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)
@@ -17986,7 +17974,6 @@ func TestChatHasUnread(t *testing.T) {
CacheReadTokens: []int64{0},
ContextLimit: []int64{0},
Compressed: []bool{false},
TotalCostMicros: []int64{0},
RuntimeMs: []int64{0},
})
require.NoError(t, err)