-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapproval.go
More file actions
160 lines (138 loc) · 3.58 KB
/
approval.go
File metadata and controls
160 lines (138 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package main
import (
"context"
"sync"
"time"
)
// CommandResult stores the outcome of one approved/denied command.
type CommandResult struct {
Command string
Approved bool
Output string
}
// PendingTurn holds all pending commands for a single AI response.
type PendingTurn struct {
Commands []string
CurrentIdx int
Results []CommandResult
SessionID string
Provider string // "claude" or "gemini"
}
// ApprovalStore is a thread-safe map of chatID → pending turn.
type ApprovalStore struct {
mu sync.RWMutex
pending map[int64]*PendingTurn
}
func NewApprovalStore() *ApprovalStore {
return &ApprovalStore{pending: make(map[int64]*PendingTurn)}
}
func (s *ApprovalStore) Get(chatID int64) *PendingTurn {
s.mu.RLock()
defer s.mu.RUnlock()
return s.pending[chatID]
}
func (s *ApprovalStore) Set(chatID int64, turn *PendingTurn) {
s.mu.Lock()
defer s.mu.Unlock()
s.pending[chatID] = turn
}
func (s *ApprovalStore) Delete(chatID int64) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.pending, chatID)
}
func (s *ApprovalStore) Has(chatID int64) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, ok := s.pending[chatID]
return ok
}
// PendingLogin holds state for an in-progress login.
// For Claude this is an OAuth PTY flow; for Gemini it's an API key prompt.
type PendingLogin struct {
FeedCode func(code string) error
Cancel context.CancelFunc
OriginalMessage string
Provider string // "claude" or "gemini"
}
// LoginStore is a thread-safe map of chatID → pending login.
type LoginStore struct {
mu sync.RWMutex
pending map[int64]*PendingLogin
}
func NewLoginStore() *LoginStore {
return &LoginStore{pending: make(map[int64]*PendingLogin)}
}
func (s *LoginStore) Get(chatID int64) *PendingLogin {
s.mu.RLock()
defer s.mu.RUnlock()
return s.pending[chatID]
}
func (s *LoginStore) Set(chatID int64, login *PendingLogin) {
s.mu.Lock()
defer s.mu.Unlock()
s.pending[chatID] = login
}
func (s *LoginStore) Delete(chatID int64) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.pending, chatID)
}
func (s *LoginStore) Has(chatID int64) bool {
s.mu.RLock()
defer s.mu.RUnlock()
_, ok := s.pending[chatID]
return ok
}
// ChatUsage accumulates usage stats for a single chat session.
type ChatUsage struct {
TotalCostUSD float64
InputTokens int64
OutputTokens int64
CacheRead int64
CacheCreate int64
NumCalls int
TotalDuration time.Duration
LastCallTime time.Time
}
// UsageTracker is a thread-safe map of chatID → accumulated usage.
type UsageTracker struct {
mu sync.RWMutex
stats map[int64]*ChatUsage
}
func NewUsageTracker() *UsageTracker {
return &UsageTracker{stats: make(map[int64]*ChatUsage)}
}
// Record adds a Claude response's usage data to the running totals.
func (t *UsageTracker) Record(chatID int64, resp *ClaudeResponse) {
if resp == nil {
return
}
t.mu.Lock()
defer t.mu.Unlock()
s := t.stats[chatID]
if s == nil {
s = &ChatUsage{}
t.stats[chatID] = s
}
s.TotalCostUSD += resp.CostUSD
s.InputTokens += resp.Usage.InputTokens
s.OutputTokens += resp.Usage.OutputTokens
s.CacheRead += resp.Usage.CacheReadInputTokens
s.CacheCreate += resp.Usage.CacheCreationInputTokens
s.NumCalls++
s.TotalDuration += time.Duration(resp.DurationMs) * time.Millisecond
s.LastCallTime = time.Now()
}
// Get returns the accumulated usage for a chat, or nil if none.
func (t *UsageTracker) Get(chatID int64) *ChatUsage {
t.mu.RLock()
defer t.mu.RUnlock()
return t.stats[chatID]
}
// Reset clears usage stats for a chat.
func (t *UsageTracker) Reset(chatID int64) {
t.mu.Lock()
defer t.mu.Unlock()
delete(t.stats, chatID)
}