返回 DeepSeek-Reasonix
tabs_billing.go
根目录 / desktop / tabs_billing.go
1 package main
2
3 import (
4 "time"
5
6 "reasonix/internal/billing"
7 "reasonix/internal/control"
8 "reasonix/internal/provider"
9 )
10
11 func (a *App) balanceRequestTarget(tabID string) (*WorkspaceTab, control.SessionAPI, uint64) {
12 a.mu.RLock()
13 tab := a.tabByIDLocked(tabID)
14 var ctrl control.SessionAPI
15 if tab != nil {
16 ctrl = tab.Ctrl
17 }
18 a.mu.RUnlock()
19 if tab == nil || ctrl == nil {
20 return nil, nil, 0
21 }
22 return tab, ctrl, tab.runtimeDisplayGeneration()
23 }
24
25 func (a *App) applyBalanceDisplayHint(tabID string, target *WorkspaceTab, ctrl control.SessionAPI, requested, primary string, generation uint64) {
26 if requested != "" || primary == "" || a.balanceDisplayCurrency() != "" {
27 return
28 }
29 // A response from an old tab, model, session, or display preference may
30 // never rebind the current session.
31 a.mu.RLock()
32 current := a.tabByIDLocked(tabID)
33 currentController := current != nil && current.Ctrl == ctrl
34 a.mu.RUnlock()
35 if current == target && currentController {
36 target.selectRuntimeDisplayCurrencyAtGeneration(primary, generation)
37 }
38 }
39
40 func (t *WorkspaceTab) replaceTelemetry(snapshot tabTelemetrySnapshot, sessionKey string) {
41 if t == nil {
42 return
43 }
44 t.telemMu.Lock()
45 t.readTelemetry = append([]readFileRecord(nil), snapshot.ReadFiles...)
46 t.usageTelemetry = cloneSessionUsageStats(snapshot.Usage)
47 t.runtimeCostDisplayCurrency = ""
48 t.runtimeCostQuote = nil
49 t.runtimeCostGeneration++
50 t.telemetrySessionKey = sessionKey
51 t.telemMu.Unlock()
52 }
53
54 // selectDisplayCurrency rebinds occurrence-time valuations without repricing.
55 func (t *WorkspaceTab) selectDisplayCurrency(display string) bool {
56 if t == nil {
57 return false
58 }
59 t.telemMu.Lock()
60 defer t.telemMu.Unlock()
61 display = billing.NormalizeCurrency(display)
62 t.runtimeCostDisplayCurrency = ""
63 t.runtimeCostQuote = nil
64 t.runtimeCostGeneration++
65 if t.usageTelemetry.CostLedger == nil || len(t.usageTelemetry.CostLedger.Entries) == 0 {
66 if t.usageTelemetry.SessionCost <= 0 {
67 return true
68 }
69 occurred := time.Now().UTC()
70 quote := billing.MigrateLegacyUsage(billing.LegacyUsageRecord{
71 SessionCost: t.usageTelemetry.SessionCost,
72 SessionCurrency: t.usageTelemetry.SessionCurrency,
73 EndedAt: occurred,
74 })
75 t.usageTelemetry.CostLedger = billing.NewLedger()
76 t.usageTelemetry.CostLedger.Add(quote, billing.UsageTokens{
77 PromptTokens: t.usageTelemetry.PromptTokens,
78 CompletionTokens: t.usageTelemetry.CompletionTokens,
79 }, occurred)
80 }
81 total := t.usageTelemetry.CostLedger.SelectDisplay(display)
82 t.usageTelemetry.SessionCostQuote = &total
83 t.usageTelemetry.SessionCostComplete = total.Complete
84 if total.Selected == nil {
85 t.usageTelemetry.SessionCostComplete = false
86 t.usageTelemetry.SessionCost = 0
87 t.usageTelemetry.SessionCurrency = ""
88 t.usageTelemetry.SessionCostUsd = 0
89 return true
90 }
91 t.usageTelemetry.SessionCost = total.Selected.Float64()
92 t.usageTelemetry.SessionCurrency = total.LegacyCurrencyCode()
93 t.usageTelemetry.SessionCostUsd = t.usageTelemetry.SessionCost
94 return true
95 }
96
97 // selectRuntimeDisplayCurrency applies an automatic wallet hint only to the
98 // live tab/session. It never mutates the persisted telemetry or configuration.
99 func (t *WorkspaceTab) selectRuntimeDisplayCurrency(display string) bool {
100 if t == nil {
101 return false
102 }
103 t.telemMu.Lock()
104 defer t.telemMu.Unlock()
105 return t.selectRuntimeDisplayCurrencyLocked(display)
106 }
107
108 // selectRuntimeDisplayCurrencyAtGeneration applies an asynchronous wallet
109 // hint only if the tab/session display binding is still the one that started
110 // the balance request.
111 func (t *WorkspaceTab) selectRuntimeDisplayCurrencyAtGeneration(display string, generation uint64) bool {
112 if t == nil {
113 return false
114 }
115 t.telemMu.Lock()
116 defer t.telemMu.Unlock()
117 if t.runtimeCostGeneration != generation {
118 return false
119 }
120 return t.selectRuntimeDisplayCurrencyLocked(display)
121 }
122
123 func (t *WorkspaceTab) selectRuntimeDisplayCurrencyLocked(display string) bool {
124 display = billing.NormalizeCurrency(display)
125 t.runtimeCostDisplayCurrency = display
126 t.runtimeCostQuote = nil
127 if t.usageTelemetry.CostLedger != nil && len(t.usageTelemetry.CostLedger.Entries) > 0 {
128 total := t.usageTelemetry.CostLedger.SelectDisplay(display)
129 t.runtimeCostQuote = &total
130 return true
131 }
132 if t.usageTelemetry.SessionCost > 0 {
133 quote := billing.MigrateLegacyUsage(billing.LegacyUsageRecord{
134 SessionCost: t.usageTelemetry.SessionCost,
135 SessionCurrency: t.usageTelemetry.SessionCurrency,
136 EndedAt: time.Now().UTC(),
137 })
138 total := billing.AggregateQuotes([]billing.CostQuote{quote}, display)
139 t.runtimeCostQuote = &total
140 }
141 return true
142 }
143
144 func (t *WorkspaceTab) runtimeDisplayGeneration() uint64 {
145 if t == nil {
146 return 0
147 }
148 t.telemMu.Lock()
149 defer t.telemMu.Unlock()
150 return t.runtimeCostGeneration
151 }
152
153 func (t *WorkspaceTab) clearRuntimeDisplayCurrency() {
154 if t == nil {
155 return
156 }
157 t.telemMu.Lock()
158 t.runtimeCostDisplayCurrency = ""
159 t.runtimeCostQuote = nil
160 t.runtimeCostGeneration++
161 t.telemMu.Unlock()
162 }
163
164 // repriceUsage preserves the legacy call shape as a display-only rebind.
165 func (t *WorkspaceTab) repriceUsage(pricingBySource map[string]*provider.Pricing) bool {
166 _ = pricingBySource
167 display := ""
168 if t != nil {
169 display = billing.NormalizeCurrency(t.usageTelemetry.SessionCurrency)
170 }
171 return t.selectDisplayCurrency(display)
172 }
173
173 lines GO