github-issue-tower-defence-management 1.80.0 → 1.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/adapter/entry-points/cli/index.js +35 -2
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/proxy/RateLimitCache.js +32 -4
- package/bin/adapter/proxy/RateLimitCache.js.map +1 -1
- package/bin/adapter/proxy/proxyEntry.js +1 -1
- package/bin/adapter/proxy/proxyEntry.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +3 -0
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +8 -0
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +2 -1
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +4 -0
- package/src/adapter/proxy/RateLimitCache.test.ts +103 -0
- package/src/adapter/proxy/RateLimitCache.ts +44 -2
- package/src/adapter/proxy/proxyEntry.test.ts +17 -0
- package/src/adapter/proxy/proxyEntry.ts +5 -1
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +14 -0
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +3 -0
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +190 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +14 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/proxy/RateLimitCache.d.ts +4 -1
- package/types/adapter/proxy/RateLimitCache.d.ts.map +1 -1
- package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
- package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +2 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
cacheDir,
|
|
6
6
|
cachePathForToken,
|
|
7
7
|
hashToken,
|
|
8
|
+
HEADERLESS_429_DEFAULT_COOLDOWN_SECONDS,
|
|
9
|
+
HEADERLESS_429_MAX_COOLDOWN_SECONDS,
|
|
8
10
|
parseModelRateLimitsFromBody,
|
|
9
11
|
readRateLimit,
|
|
10
12
|
writeModelRateLimit,
|
|
@@ -446,6 +448,107 @@ describe('RateLimitCache', () => {
|
|
|
446
448
|
});
|
|
447
449
|
});
|
|
448
450
|
|
|
451
|
+
describe('writeRateLimit records a cooldown on a 429 with no anthropic-ratelimit-* headers', () => {
|
|
452
|
+
it('should set blockedUntilEpoch using the default cooldown when no Retry-After header is present', () => {
|
|
453
|
+
const token = '429-no-headers-default-cooldown-token';
|
|
454
|
+
const before = Date.now() / 1000;
|
|
455
|
+
writeRateLimit(token, { 'content-type': 'application/json' }, 429);
|
|
456
|
+
const after = Date.now() / 1000;
|
|
457
|
+
const snapshot = readRateLimit(token);
|
|
458
|
+
expect(snapshot).not.toBeNull();
|
|
459
|
+
expect(snapshot?.blockedUntilEpoch).toBeGreaterThanOrEqual(
|
|
460
|
+
before + HEADERLESS_429_DEFAULT_COOLDOWN_SECONDS,
|
|
461
|
+
);
|
|
462
|
+
expect(snapshot?.blockedUntilEpoch).toBeLessThanOrEqual(
|
|
463
|
+
after + HEADERLESS_429_DEFAULT_COOLDOWN_SECONDS,
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it('should honor the Retry-After header when present', () => {
|
|
468
|
+
const token = '429-no-headers-retry-after-token';
|
|
469
|
+
const retryAfterSeconds = 120;
|
|
470
|
+
const before = Date.now() / 1000;
|
|
471
|
+
writeRateLimit(
|
|
472
|
+
token,
|
|
473
|
+
{ 'content-type': 'application/json', 'retry-after': '120' },
|
|
474
|
+
429,
|
|
475
|
+
);
|
|
476
|
+
const after = Date.now() / 1000;
|
|
477
|
+
const snapshot = readRateLimit(token);
|
|
478
|
+
expect(snapshot?.blockedUntilEpoch).toBeGreaterThanOrEqual(
|
|
479
|
+
before + retryAfterSeconds,
|
|
480
|
+
);
|
|
481
|
+
expect(snapshot?.blockedUntilEpoch).toBeLessThanOrEqual(
|
|
482
|
+
after + retryAfterSeconds,
|
|
483
|
+
);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should clamp the cooldown to the maximum when Retry-After is very large', () => {
|
|
487
|
+
const token = '429-no-headers-clamp-token';
|
|
488
|
+
const before = Date.now() / 1000;
|
|
489
|
+
writeRateLimit(
|
|
490
|
+
token,
|
|
491
|
+
{ 'content-type': 'application/json', 'retry-after': '99999' },
|
|
492
|
+
429,
|
|
493
|
+
);
|
|
494
|
+
const after = Date.now() / 1000;
|
|
495
|
+
const snapshot = readRateLimit(token);
|
|
496
|
+
expect(snapshot?.blockedUntilEpoch).toBeGreaterThanOrEqual(
|
|
497
|
+
before + HEADERLESS_429_MAX_COOLDOWN_SECONDS,
|
|
498
|
+
);
|
|
499
|
+
expect(snapshot?.blockedUntilEpoch).toBeLessThanOrEqual(
|
|
500
|
+
after + HEADERLESS_429_MAX_COOLDOWN_SECONDS,
|
|
501
|
+
);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should preserve the previous last-good snapshot while adding the cooldown', () => {
|
|
505
|
+
const token = '429-no-headers-preserve-snapshot-token';
|
|
506
|
+
writeRateLimit(token, {
|
|
507
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
508
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
509
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
510
|
+
'anthropic-ratelimit-unified-5h-utilization': '42',
|
|
511
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
512
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
513
|
+
'anthropic-ratelimit-unified-7d-utilization': '17',
|
|
514
|
+
});
|
|
515
|
+
writeRateLimit(token, { 'content-type': 'application/json' }, 429);
|
|
516
|
+
const snapshot = readRateLimit(token);
|
|
517
|
+
expect(snapshot?.fiveHourUtilization).toBe(42);
|
|
518
|
+
expect(snapshot?.fiveHourReset).toBe(1700000000);
|
|
519
|
+
expect(snapshot?.sevenDayUtilization).toBe(17);
|
|
520
|
+
expect(snapshot?.sevenDayReset).toBe(1700100000);
|
|
521
|
+
expect(snapshot?.rejected).toBe(false);
|
|
522
|
+
expect(snapshot?.blocked).toBe(false);
|
|
523
|
+
expect(snapshot?.blockedUntilEpoch).toBeGreaterThan(Date.now() / 1000);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
it('should clear the cooldown when a later header-bearing response arrives', () => {
|
|
527
|
+
const token = '429-then-normal-clears-cooldown-token';
|
|
528
|
+
writeRateLimit(token, { 'content-type': 'application/json' }, 429);
|
|
529
|
+
expect(readRateLimit(token)?.blockedUntilEpoch).toBeGreaterThan(
|
|
530
|
+
Date.now() / 1000,
|
|
531
|
+
);
|
|
532
|
+
writeRateLimit(token, {
|
|
533
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
534
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
535
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
536
|
+
'anthropic-ratelimit-unified-5h-utilization': '30',
|
|
537
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
538
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
539
|
+
'anthropic-ratelimit-unified-7d-utilization': '20',
|
|
540
|
+
});
|
|
541
|
+
expect(readRateLimit(token)?.blockedUntilEpoch).toBe(0);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should not write any cooldown for a headerless response that is not a 429', () => {
|
|
545
|
+
const token = '500-no-headers-no-cooldown-token';
|
|
546
|
+
writeRateLimit(token, { 'content-type': 'application/json' }, 500);
|
|
547
|
+
expect(fs.existsSync(cachePathForToken(token))).toBe(false);
|
|
548
|
+
expect(readRateLimit(token)).toBeNull();
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
449
552
|
describe('parseModelRateLimitsFromBody', () => {
|
|
450
553
|
it('should extract a rejected seven_day_sonnet limit from a rate_limit event body', () => {
|
|
451
554
|
const body =
|
|
@@ -20,12 +20,17 @@ export interface RateLimitSnapshot {
|
|
|
20
20
|
sevenDayRejected: boolean;
|
|
21
21
|
modelWeeklyLimits: Record<string, ModelWeeklyLimit>;
|
|
22
22
|
lastUpdatedEpoch: number;
|
|
23
|
+
blockedUntilEpoch: number;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export const PROXY_PORT = 8787;
|
|
26
27
|
|
|
27
28
|
const HASH_ALGORITHM = 'sha256';
|
|
28
29
|
|
|
30
|
+
export const HEADERLESS_429_DEFAULT_COOLDOWN_SECONDS = 90;
|
|
31
|
+
|
|
32
|
+
export const HEADERLESS_429_MAX_COOLDOWN_SECONDS = 600;
|
|
33
|
+
|
|
29
34
|
export const cacheDir = (): string => {
|
|
30
35
|
const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
|
|
31
36
|
return path.join(base, 'tdpm', 'ratelimit');
|
|
@@ -68,9 +73,21 @@ const readModelWeeklyLimits = (
|
|
|
68
73
|
return result;
|
|
69
74
|
};
|
|
70
75
|
|
|
76
|
+
const cooldownEndFromRetryAfter = (
|
|
77
|
+
retryAfterSeconds: number | null,
|
|
78
|
+
nowEpochSeconds: number,
|
|
79
|
+
): number => {
|
|
80
|
+
const cooldownSeconds =
|
|
81
|
+
retryAfterSeconds !== null && retryAfterSeconds > 0
|
|
82
|
+
? Math.min(retryAfterSeconds, HEADERLESS_429_MAX_COOLDOWN_SECONDS)
|
|
83
|
+
: HEADERLESS_429_DEFAULT_COOLDOWN_SECONDS;
|
|
84
|
+
return nowEpochSeconds + cooldownSeconds;
|
|
85
|
+
};
|
|
86
|
+
|
|
71
87
|
export const writeRateLimit = (
|
|
72
88
|
token: string,
|
|
73
89
|
headers: Record<string, string | string[] | undefined>,
|
|
90
|
+
statusCode: number | null = null,
|
|
74
91
|
): void => {
|
|
75
92
|
const pick = (key: string): string | undefined => {
|
|
76
93
|
const value = headers[key];
|
|
@@ -86,14 +103,35 @@ export const writeRateLimit = (
|
|
|
86
103
|
}
|
|
87
104
|
}
|
|
88
105
|
}
|
|
106
|
+
const dir = cacheDir();
|
|
107
|
+
const filePath = path.join(dir, `${hashToken(token)}.json`);
|
|
89
108
|
if (Object.keys(rateLimitHeaders).length === 0) {
|
|
109
|
+
if (statusCode !== 429) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const existing = readPayload(filePath);
|
|
113
|
+
const retryAfterRaw = pick('retry-after');
|
|
114
|
+
const retryAfterSeconds =
|
|
115
|
+
retryAfterRaw !== undefined && Number.isFinite(Number(retryAfterRaw))
|
|
116
|
+
? Number(retryAfterRaw)
|
|
117
|
+
: null;
|
|
118
|
+
const blockedUntilEpoch = cooldownEndFromRetryAfter(
|
|
119
|
+
retryAfterSeconds,
|
|
120
|
+
Date.now() / 1000,
|
|
121
|
+
);
|
|
122
|
+
if (!fs.existsSync(dir)) {
|
|
123
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
124
|
+
}
|
|
125
|
+
const payload = {
|
|
126
|
+
...existing,
|
|
127
|
+
blockedUntilEpoch,
|
|
128
|
+
};
|
|
129
|
+
fs.writeFileSync(filePath, JSON.stringify(payload));
|
|
90
130
|
return;
|
|
91
131
|
}
|
|
92
|
-
const dir = cacheDir();
|
|
93
132
|
if (!fs.existsSync(dir)) {
|
|
94
133
|
fs.mkdirSync(dir, { recursive: true });
|
|
95
134
|
}
|
|
96
|
-
const filePath = path.join(dir, `${hashToken(token)}.json`);
|
|
97
135
|
const existing = readPayload(filePath);
|
|
98
136
|
const payload = {
|
|
99
137
|
ts: Date.now() / 1000,
|
|
@@ -186,6 +224,9 @@ export const readRateLimit = (token: string): RateLimitSnapshot | null => {
|
|
|
186
224
|
const sevenDayRejected = sevenDayStatus === 'rejected';
|
|
187
225
|
const storedTs = parsed.ts;
|
|
188
226
|
const lastUpdatedEpoch = typeof storedTs === 'number' ? storedTs : 0;
|
|
227
|
+
const storedBlockedUntil = parsed.blockedUntilEpoch;
|
|
228
|
+
const blockedUntilEpoch =
|
|
229
|
+
typeof storedBlockedUntil === 'number' ? storedBlockedUntil : 0;
|
|
189
230
|
return {
|
|
190
231
|
fiveHourUtilization: num('anthropic-ratelimit-unified-5h-utilization'),
|
|
191
232
|
fiveHourReset: num('anthropic-ratelimit-unified-5h-reset'),
|
|
@@ -201,6 +242,7 @@ export const readRateLimit = (token: string): RateLimitSnapshot | null => {
|
|
|
201
242
|
sevenDayRejected,
|
|
202
243
|
modelWeeklyLimits: readModelWeeklyLimits(parsed),
|
|
203
244
|
lastUpdatedEpoch,
|
|
245
|
+
blockedUntilEpoch,
|
|
204
246
|
};
|
|
205
247
|
} catch {
|
|
206
248
|
return null;
|
|
@@ -291,6 +291,23 @@ describe('startProxy', () => {
|
|
|
291
291
|
expect(writeModelRateLimitSpy).toHaveBeenCalledTimes(1);
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
+
it('should forward the upstream status code to writeRateLimit on a 429 response', async () => {
|
|
295
|
+
upstreamHandler = (_request, response) => {
|
|
296
|
+
response.writeHead(429, { 'content-type': 'application/json' });
|
|
297
|
+
response.end('{"type":"error","error":{"type":"rate_limit_error"}}');
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const response = await requestThroughProxy('POST', '/v1/messages', '{}');
|
|
301
|
+
|
|
302
|
+
expect(response.statusCode).toBe(429);
|
|
303
|
+
expect(writeRateLimitSpy).toHaveBeenCalledTimes(1);
|
|
304
|
+
expect(writeRateLimitSpy).toHaveBeenCalledWith(
|
|
305
|
+
TOKEN,
|
|
306
|
+
expect.objectContaining({ 'content-type': 'application/json' }),
|
|
307
|
+
429,
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
|
|
294
311
|
it('should forward non-SSE responses without crashing', async () => {
|
|
295
312
|
upstreamHandler = (_request, response) => {
|
|
296
313
|
response.writeHead(200, { 'content-type': 'application/json' });
|
|
@@ -50,7 +50,11 @@ const startProxy = (
|
|
|
50
50
|
(upstreamResponse) => {
|
|
51
51
|
if (token !== null) {
|
|
52
52
|
try {
|
|
53
|
-
writeRateLimit(
|
|
53
|
+
writeRateLimit(
|
|
54
|
+
token,
|
|
55
|
+
upstreamResponse.headers,
|
|
56
|
+
upstreamResponse.statusCode ?? null,
|
|
57
|
+
);
|
|
54
58
|
} catch (error) {
|
|
55
59
|
console.error('Failed to write rate limit cache:', error);
|
|
56
60
|
}
|
|
@@ -109,6 +109,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
109
109
|
sevenDayUtilization: 0,
|
|
110
110
|
blocked: false,
|
|
111
111
|
rejected: false,
|
|
112
|
+
blockedUntilEpoch: 0,
|
|
112
113
|
modelWeeklyLimits: {
|
|
113
114
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
114
115
|
},
|
|
@@ -120,6 +121,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
120
121
|
sevenDayUtilization: 0,
|
|
121
122
|
blocked: false,
|
|
122
123
|
rejected: false,
|
|
124
|
+
blockedUntilEpoch: 0,
|
|
123
125
|
modelWeeklyLimits: {},
|
|
124
126
|
},
|
|
125
127
|
]);
|
|
@@ -153,6 +155,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
153
155
|
sevenDayUtilization: 0,
|
|
154
156
|
blocked: true,
|
|
155
157
|
rejected: false,
|
|
158
|
+
blockedUntilEpoch: 0,
|
|
156
159
|
modelWeeklyLimits: {
|
|
157
160
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
158
161
|
},
|
|
@@ -188,6 +191,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
188
191
|
sevenDayUtilization: 0,
|
|
189
192
|
blocked: false,
|
|
190
193
|
rejected: true,
|
|
194
|
+
blockedUntilEpoch: 0,
|
|
191
195
|
modelWeeklyLimits: {
|
|
192
196
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
193
197
|
},
|
|
@@ -223,6 +227,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
223
227
|
sevenDayUtilization: 30,
|
|
224
228
|
blocked: false,
|
|
225
229
|
rejected: false,
|
|
230
|
+
blockedUntilEpoch: 0,
|
|
226
231
|
modelWeeklyLimits: {
|
|
227
232
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
228
233
|
},
|
|
@@ -258,6 +263,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
258
263
|
sevenDayUtilization: 0,
|
|
259
264
|
blocked: false,
|
|
260
265
|
rejected: false,
|
|
266
|
+
blockedUntilEpoch: 0,
|
|
261
267
|
modelWeeklyLimits: {
|
|
262
268
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
263
269
|
},
|
|
@@ -293,6 +299,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
293
299
|
sevenDayUtilization: 0,
|
|
294
300
|
blocked: false,
|
|
295
301
|
rejected: false,
|
|
302
|
+
blockedUntilEpoch: 0,
|
|
296
303
|
modelWeeklyLimits: {
|
|
297
304
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
298
305
|
},
|
|
@@ -328,6 +335,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
328
335
|
sevenDayUtilization: 0,
|
|
329
336
|
blocked: false,
|
|
330
337
|
rejected: false,
|
|
338
|
+
blockedUntilEpoch: 0,
|
|
331
339
|
modelWeeklyLimits: {},
|
|
332
340
|
},
|
|
333
341
|
]);
|
|
@@ -361,6 +369,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
361
369
|
sevenDayUtilization: 0,
|
|
362
370
|
blocked: false,
|
|
363
371
|
rejected: true,
|
|
372
|
+
blockedUntilEpoch: 0,
|
|
364
373
|
modelWeeklyLimits: {
|
|
365
374
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
366
375
|
},
|
|
@@ -396,6 +405,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
396
405
|
sevenDayUtilization: 100,
|
|
397
406
|
blocked: false,
|
|
398
407
|
rejected: true,
|
|
408
|
+
blockedUntilEpoch: 0,
|
|
399
409
|
modelWeeklyLimits: {
|
|
400
410
|
seven_day: { rejected: true, resetsAt: futureReset },
|
|
401
411
|
},
|
|
@@ -431,6 +441,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
431
441
|
sevenDayUtilization: 0,
|
|
432
442
|
blocked: false,
|
|
433
443
|
rejected: false,
|
|
444
|
+
blockedUntilEpoch: 0,
|
|
434
445
|
modelWeeklyLimits: {
|
|
435
446
|
seven_day: { rejected: false, resetsAt: futureReset },
|
|
436
447
|
},
|
|
@@ -455,6 +466,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
455
466
|
sevenDayUtilization: 0,
|
|
456
467
|
blocked: false,
|
|
457
468
|
rejected: false,
|
|
469
|
+
blockedUntilEpoch: 0,
|
|
458
470
|
modelWeeklyLimits: {},
|
|
459
471
|
},
|
|
460
472
|
]);
|
|
@@ -490,6 +502,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
490
502
|
sevenDayUtilization: 10,
|
|
491
503
|
blocked: false,
|
|
492
504
|
rejected: false,
|
|
505
|
+
blockedUntilEpoch: 0,
|
|
493
506
|
modelWeeklyLimits: {
|
|
494
507
|
seven_day_sonnet: { rejected: true, resetsAt: futureReset },
|
|
495
508
|
},
|
|
@@ -527,6 +540,7 @@ describe('ProxyClaudeTokenUsageRepository', () => {
|
|
|
527
540
|
sevenDayUtilization: 10,
|
|
528
541
|
blocked: false,
|
|
529
542
|
rejected: false,
|
|
543
|
+
blockedUntilEpoch: 0,
|
|
530
544
|
modelWeeklyLimits: {
|
|
531
545
|
seven_day_sonnet: { rejected: false, resetsAt: pastReset },
|
|
532
546
|
},
|
|
@@ -36,6 +36,7 @@ export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageReposito
|
|
|
36
36
|
blocked: false,
|
|
37
37
|
rejected: false,
|
|
38
38
|
modelWeeklyLimits: {},
|
|
39
|
+
blockedUntilEpoch: 0,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
const fiveHourExpired = nowEpochSeconds > snapshot.fiveHourReset;
|
|
@@ -83,6 +84,7 @@ export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageReposito
|
|
|
83
84
|
resetsAt: snapshot.sevenDayReset,
|
|
84
85
|
};
|
|
85
86
|
}
|
|
87
|
+
const cooldownActive = snapshot.blockedUntilEpoch > nowEpochSeconds;
|
|
86
88
|
return {
|
|
87
89
|
name,
|
|
88
90
|
token,
|
|
@@ -91,6 +93,7 @@ export class ProxyClaudeTokenUsageRepository implements ClaudeTokenUsageReposito
|
|
|
91
93
|
blocked: snapshot.blocked,
|
|
92
94
|
rejected,
|
|
93
95
|
modelWeeklyLimits,
|
|
96
|
+
blockedUntilEpoch: cooldownActive ? snapshot.blockedUntilEpoch : 0,
|
|
94
97
|
};
|
|
95
98
|
});
|
|
96
99
|
};
|