Skip to content

Commit 07283bf

Browse files
committed
refactor: unify request and cost tracking approach in AutoApprovalHandler
- Use consistent message-based tracking for both request count and cost limit - Track reset point using lastResetMessageIndex for both limits - Calculate request count from API messages similar to cost calculation - Simplify logic by removing separate tracking mechanisms - Update tests to match new message-based counting approach This addresses review feedback about inconsistent tracking approaches between request counting and cost tracking.
1 parent ed1e6db commit 07283bf

File tree

2 files changed

+88
-45
lines changed

2 files changed

+88
-45
lines changed

src/core/task/AutoApprovalHandler.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ export interface AutoApprovalResult {
1010
}
1111

1212
export class AutoApprovalHandler {
13+
private lastResetMessageIndex: number = 0
1314
private consecutiveAutoApprovedRequestsCount: number = 0
1415
private consecutiveAutoApprovedCost: number = 0
15-
private costResetMessageIndex: number = 0
1616

1717
/**
1818
* Check if auto-approval limits have been reached and handle user approval if needed
@@ -26,7 +26,7 @@ export class AutoApprovalHandler {
2626
) => Promise<{ response: ClineAskResponse; text?: string; images?: string[] }>,
2727
): Promise<AutoApprovalResult> {
2828
// Check request count limit
29-
const requestResult = await this.checkRequestLimit(state, askForApproval)
29+
const requestResult = await this.checkRequestLimit(state, messages, askForApproval)
3030
if (!requestResult.shouldProceed || requestResult.requiresApproval) {
3131
return requestResult
3232
}
@@ -37,19 +37,23 @@ export class AutoApprovalHandler {
3737
}
3838

3939
/**
40-
* Increment the request counter and check if limit is exceeded
40+
* Calculate request count and check if limit is exceeded
4141
*/
4242
private async checkRequestLimit(
4343
state: GlobalState | undefined,
44+
messages: ClineMessage[],
4445
askForApproval: (
4546
type: ClineAsk,
4647
data: string,
4748
) => Promise<{ response: ClineAskResponse; text?: string; images?: string[] }>,
4849
): Promise<AutoApprovalResult> {
4950
const maxRequests = state?.allowedMaxRequests || Infinity
5051

51-
// Increment the counter for each new API request
52-
this.consecutiveAutoApprovedRequestsCount++
52+
// Calculate request count from messages after the last reset point
53+
const messagesAfterReset = messages.slice(this.lastResetMessageIndex)
54+
// Count API request messages (simplified - you may need to adjust based on your message structure)
55+
this.consecutiveAutoApprovedRequestsCount =
56+
messagesAfterReset.filter((msg) => msg.type === "say" && msg.say === "api_req_started").length + 1 // +1 for the current request being checked
5357

5458
if (this.consecutiveAutoApprovedRequestsCount > maxRequests) {
5559
const { response } = await askForApproval(
@@ -59,7 +63,8 @@ export class AutoApprovalHandler {
5963

6064
// If we get past the promise, it means the user approved and did not start a new task
6165
if (response === "yesButtonClicked") {
62-
this.consecutiveAutoApprovedRequestsCount = 0
66+
// Reset tracking by recording the current message count
67+
this.lastResetMessageIndex = messages.length
6368
return {
6469
shouldProceed: true,
6570
requiresApproval: true,
@@ -93,7 +98,7 @@ export class AutoApprovalHandler {
9398
const maxCost = state?.allowedMaxCost || Infinity
9499

95100
// Calculate total cost from messages after the last reset point
96-
const messagesAfterReset = messages.slice(this.costResetMessageIndex)
101+
const messagesAfterReset = messages.slice(this.lastResetMessageIndex)
97102
this.consecutiveAutoApprovedCost = getApiMetrics(messagesAfterReset).totalCost
98103

99104
// Use epsilon for floating-point comparison to avoid precision issues
@@ -106,9 +111,9 @@ export class AutoApprovalHandler {
106111

107112
// If we get past the promise, it means the user approved and did not start a new task
108113
if (response === "yesButtonClicked") {
109-
// Reset the cost tracking by recording the current message count
110-
// Future cost calculations will only include messages after this point
111-
this.costResetMessageIndex = messages.length
114+
// Reset tracking by recording the current message count
115+
// Future calculations will only include messages after this point
116+
this.lastResetMessageIndex = messages.length
112117
return {
113118
shouldProceed: true,
114119
requiresApproval: true,
@@ -129,11 +134,12 @@ export class AutoApprovalHandler {
129134
}
130135

131136
/**
132-
* Reset the request counter and cost tracking (typically called when starting a new task)
137+
* Reset the tracking (typically called when starting a new task)
133138
*/
134139
resetRequestCount(): void {
140+
this.lastResetMessageIndex = 0
135141
this.consecutiveAutoApprovedRequestsCount = 0
136-
this.costResetMessageIndex = 0
142+
this.consecutiveAutoApprovedCost = 0
137143
}
138144

139145
/**

src/core/task/__tests__/AutoApprovalHandler.spec.ts

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,15 @@ describe("AutoApprovalHandler", () => {
4040
mockState.allowedMaxCost = 10
4141
const messages: ClineMessage[] = []
4242

43-
// First call should be under limit
43+
// First call should be under limit (count = 1)
4444
const result1 = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
4545
expect(result1.shouldProceed).toBe(true)
4646
expect(result1.requiresApproval).toBe(false)
4747

48-
// Second call should trigger request limit
48+
// Add a message to simulate first request completed
49+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 })
50+
51+
// Second call should trigger request limit (1 message + current = 2 > 1)
4952
mockAskForApproval.mockResolvedValue({ response: "yesButtonClicked" })
5053
const result2 = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
5154

@@ -64,27 +67,35 @@ describe("AutoApprovalHandler", () => {
6467
mockState.allowedMaxRequests = 3
6568
})
6669

67-
it("should increment request count on each check", async () => {
70+
it("should calculate request count from messages", async () => {
6871
const messages: ClineMessage[] = []
6972

70-
// Check state after each call
71-
for (let i = 1; i <= 3; i++) {
72-
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
73-
const state = handler.getApprovalState()
74-
expect(state.requestCount).toBe(i)
75-
}
73+
// First check - no messages yet, count should be 1 (for current request)
74+
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
75+
let state = handler.getApprovalState()
76+
expect(state.requestCount).toBe(1)
77+
78+
// Add API request messages
79+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 })
80+
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
81+
state = handler.getApprovalState()
82+
expect(state.requestCount).toBe(2) // 1 message + current request
83+
84+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 2000 })
85+
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
86+
state = handler.getApprovalState()
87+
expect(state.requestCount).toBe(3) // 2 messages + current request
7688
})
7789

7890
it("should ask for approval when limit is exceeded", async () => {
7991
const messages: ClineMessage[] = []
8092

81-
// Make 3 requests (within limit)
93+
// Add 3 API request messages (to simulate 3 requests made)
8294
for (let i = 0; i < 3; i++) {
83-
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
95+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 + i })
8496
}
85-
expect(mockAskForApproval).not.toHaveBeenCalled()
8697

87-
// 4th request should trigger approval
98+
// Next check should trigger approval (3 messages + current = 4 > 3)
8899
mockAskForApproval.mockResolvedValue({ response: "yesButtonClicked" })
89100
const result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
90101

@@ -99,29 +110,35 @@ describe("AutoApprovalHandler", () => {
99110
it("should reset count when user approves", async () => {
100111
const messages: ClineMessage[] = []
101112

102-
// Exceed limit
113+
// Add messages to exceed limit
103114
for (let i = 0; i < 3; i++) {
104-
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
115+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 + i })
105116
}
106117

107-
// 4th request should trigger approval and reset
118+
// Next request should trigger approval and reset
108119
mockAskForApproval.mockResolvedValue({ response: "yesButtonClicked" })
109120
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
110121

111-
// Count should be reset
122+
// Add more messages after reset
123+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 4000 })
124+
125+
// Next check should only count messages after reset
126+
const result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
127+
expect(result.requiresApproval).toBe(false) // Should not require approval (1 message + current = 2 <= 3)
128+
112129
const state = handler.getApprovalState()
113-
expect(state.requestCount).toBe(0)
130+
expect(state.requestCount).toBe(2) // 1 message after reset + current request
114131
})
115132

116133
it("should not proceed when user rejects", async () => {
117134
const messages: ClineMessage[] = []
118135

119-
// Exceed limit
136+
// Add messages to exceed limit
120137
for (let i = 0; i < 3; i++) {
121-
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
138+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 + i })
122139
}
123140

124-
// 4th request with rejection
141+
// Next request with rejection
125142
mockAskForApproval.mockResolvedValue({ response: "noButtonClicked" })
126143
const result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
127144

@@ -255,16 +272,21 @@ describe("AutoApprovalHandler", () => {
255272

256273
mockGetApiMetrics.mockReturnValue({ totalCost: 3.0 })
257274

258-
// First two requests should pass
259-
for (let i = 0; i < 2; i++) {
260-
const result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
261-
expect(result.shouldProceed).toBe(true)
262-
expect(result.requiresApproval).toBe(false)
263-
}
275+
// First request should pass (count = 1)
276+
let result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
277+
expect(result.shouldProceed).toBe(true)
278+
expect(result.requiresApproval).toBe(false)
279+
280+
// Add a message and check again (count = 2)
281+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 })
282+
result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
283+
expect(result.shouldProceed).toBe(true)
284+
expect(result.requiresApproval).toBe(false)
264285

265-
// Third request should trigger request limit (not cost limit)
286+
// Add another message - third request should trigger request limit (count = 3 > 2)
287+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 2000 })
266288
mockAskForApproval.mockResolvedValue({ response: "yesButtonClicked" })
267-
const result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
289+
result = await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
268290

269291
expect(mockAskForApproval).toHaveBeenCalledWith(
270292
"auto_approval_max_req_reached",
@@ -277,23 +299,38 @@ describe("AutoApprovalHandler", () => {
277299
})
278300

279301
describe("resetRequestCount", () => {
280-
it("should reset the request counter", async () => {
302+
it("should reset tracking", async () => {
281303
mockState.allowedMaxRequests = 5
304+
mockState.allowedMaxCost = 10.0
282305
const messages: ClineMessage[] = []
283306

284-
// Make some requests
307+
// Add some messages
285308
for (let i = 0; i < 3; i++) {
286-
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
309+
messages.push({ type: "say", say: "api_req_started", text: "{}", ts: 1000 + i })
287310
}
288311

312+
mockGetApiMetrics.mockReturnValue({ totalCost: 5.0 })
313+
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
314+
289315
let state = handler.getApprovalState()
290-
expect(state.requestCount).toBe(3)
316+
expect(state.requestCount).toBe(4) // 3 messages + current
317+
expect(state.currentCost).toBe(5.0)
291318

292319
// Reset
293320
handler.resetRequestCount()
294321

322+
// After reset, counts should be zero
295323
state = handler.getApprovalState()
296324
expect(state.requestCount).toBe(0)
325+
expect(state.currentCost).toBe(0)
326+
327+
// Next check should start fresh
328+
mockGetApiMetrics.mockReturnValue({ totalCost: 8.0 })
329+
await handler.checkAutoApprovalLimits(mockState, messages, mockAskForApproval)
330+
331+
state = handler.getApprovalState()
332+
expect(state.requestCount).toBe(4) // All messages counted again
333+
expect(state.currentCost).toBe(8.0)
297334
})
298335
})
299336
})

0 commit comments

Comments
 (0)