github-issue-tower-defence-management 1.67.3 → 1.67.5
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 +17 -0
- package/README.md +1 -1
- package/bin/adapter/entry-points/cli/index.js +2 -2
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/cli/projectConfig.js +2 -24
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -16
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/proxy/RateLimitCache.js +9 -6
- package/bin/adapter/proxy/RateLimitCache.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +33 -10
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +0 -117
- package/src/adapter/entry-points/cli/index.ts +2 -2
- package/src/adapter/entry-points/cli/projectConfig.ts +1 -31
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -94
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -47
- package/src/adapter/proxy/RateLimitCache.test.ts +123 -0
- package/src/adapter/proxy/RateLimitCache.ts +9 -6
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +77 -195
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +65 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +120 -24
- package/types/adapter/entry-points/cli/projectConfig.d.ts +1 -2
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/proxy/RateLimitCache.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +3 -2
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
|
@@ -109,27 +109,7 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
|
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
-
export const
|
|
113
|
-
'defaultAgentName',
|
|
114
|
-
'defaultLlmModelName',
|
|
115
|
-
'defaultLlmAgentName',
|
|
116
|
-
'maximumPreparingIssuesCount',
|
|
117
|
-
'allowIssueCacheMinutes',
|
|
118
|
-
'utilizationPercentageThreshold',
|
|
119
|
-
'allowedIssueAuthors',
|
|
120
|
-
'thresholdForAutoReject',
|
|
121
|
-
'workflowBlockerResolvedWebhookUrl',
|
|
122
|
-
'preparationProcessCheckCommand',
|
|
123
|
-
'codexHomeCandidates',
|
|
124
|
-
'claudeCodeOauthTokenListJsonPath',
|
|
125
|
-
'awLogDirectoryPath',
|
|
126
|
-
'awLogStaleThresholdMinutes',
|
|
127
|
-
];
|
|
128
|
-
|
|
129
|
-
export const parseProjectReadmeConfig = (
|
|
130
|
-
readme: string,
|
|
131
|
-
projectUrl?: string,
|
|
132
|
-
): ConfigFile => {
|
|
112
|
+
export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
|
|
133
113
|
const detailsRegex =
|
|
134
114
|
/<details>\s*<summary>config<\/summary>([\s\S]*?)<\/details>/i;
|
|
135
115
|
const match = detailsRegex.exec(readme);
|
|
@@ -145,16 +125,6 @@ export const parseProjectReadmeConfig = (
|
|
|
145
125
|
if (!isRecord(parsed)) {
|
|
146
126
|
return {};
|
|
147
127
|
}
|
|
148
|
-
const knownKeySet = new Set<string>(knownProjectReadmeConfigKeys);
|
|
149
|
-
const unknownKeys = Object.keys(parsed).filter(
|
|
150
|
-
(key) => !knownKeySet.has(key),
|
|
151
|
-
);
|
|
152
|
-
const projectUrlSuffix = projectUrl ? ` (project: ${projectUrl})` : '';
|
|
153
|
-
for (const unknownKey of unknownKeys) {
|
|
154
|
-
console.warn(
|
|
155
|
-
`Unknown key "${unknownKey}" in project README config section${projectUrlSuffix}`,
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
128
|
return {
|
|
159
129
|
defaultAgentName: getStringValue(parsed, 'defaultAgentName'),
|
|
160
130
|
defaultLlmModelName: getStringValue(parsed, 'defaultLlmModelName'),
|
|
@@ -457,98 +457,4 @@ claudeCodeOauthTokenListJsonPath: /readme/tokens.json
|
|
|
457
457
|
);
|
|
458
458
|
});
|
|
459
459
|
});
|
|
460
|
-
|
|
461
|
-
describe('effective config logging', () => {
|
|
462
|
-
let consoleLogSpy: jest.SpyInstance;
|
|
463
|
-
|
|
464
|
-
beforeEach(() => {
|
|
465
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
afterEach(() => {
|
|
469
|
-
consoleLogSpy.mockRestore();
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it('should log the effective values with configFile source when only the YAML config sets them', async () => {
|
|
473
|
-
const configWithPreparation = {
|
|
474
|
-
...validConfig,
|
|
475
|
-
startPreparation: {
|
|
476
|
-
defaultAgentName: 'yaml-agent',
|
|
477
|
-
defaultLlmModelName: 'yaml-model',
|
|
478
|
-
configFilePath: './config.yml',
|
|
479
|
-
maximumPreparingIssuesCount: 10,
|
|
480
|
-
},
|
|
481
|
-
};
|
|
482
|
-
mockFetchReturningReadme(null);
|
|
483
|
-
jest
|
|
484
|
-
.mocked(fs.readFileSync)
|
|
485
|
-
.mockReturnValue(YAML.stringify(configWithPreparation));
|
|
486
|
-
|
|
487
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
488
|
-
await handler.handle('config.yml', false);
|
|
489
|
-
|
|
490
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
491
|
-
'Effective maximumPreparingIssuesCount: 10 (source: configFile)',
|
|
492
|
-
);
|
|
493
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
494
|
-
'Effective defaultLlmModelName: yaml-model (source: configFile)',
|
|
495
|
-
);
|
|
496
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
497
|
-
'Effective defaultAgentName: yaml-agent (source: configFile)',
|
|
498
|
-
);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it('should log the effective values with readmeOverride source when the README config overrides them', async () => {
|
|
502
|
-
const readmeContent = `<details>
|
|
503
|
-
<summary>config</summary>
|
|
504
|
-
maximumPreparingIssuesCount: 3
|
|
505
|
-
defaultLlmModelName: readme-model
|
|
506
|
-
defaultAgentName: readme-agent
|
|
507
|
-
</details>`;
|
|
508
|
-
mockFetchReturningReadme(readmeContent);
|
|
509
|
-
const configWithPreparation = {
|
|
510
|
-
...validConfig,
|
|
511
|
-
startPreparation: {
|
|
512
|
-
defaultAgentName: 'yaml-agent',
|
|
513
|
-
defaultLlmModelName: 'yaml-model',
|
|
514
|
-
configFilePath: './config.yml',
|
|
515
|
-
maximumPreparingIssuesCount: 10,
|
|
516
|
-
},
|
|
517
|
-
};
|
|
518
|
-
jest
|
|
519
|
-
.mocked(fs.readFileSync)
|
|
520
|
-
.mockReturnValue(YAML.stringify(configWithPreparation));
|
|
521
|
-
|
|
522
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
523
|
-
await handler.handle('config.yml', false);
|
|
524
|
-
|
|
525
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
526
|
-
'Effective maximumPreparingIssuesCount: 3 (source: readmeOverride)',
|
|
527
|
-
);
|
|
528
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
529
|
-
'Effective defaultLlmModelName: readme-model (source: readmeOverride)',
|
|
530
|
-
);
|
|
531
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
532
|
-
'Effective defaultAgentName: readme-agent (source: readmeOverride)',
|
|
533
|
-
);
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
it('should log null with unset (default) source when neither README nor config provides the value', async () => {
|
|
537
|
-
mockFetchReturningReadme(null);
|
|
538
|
-
jest.mocked(fs.readFileSync).mockReturnValue(YAML.stringify(validConfig));
|
|
539
|
-
|
|
540
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
541
|
-
await handler.handle('config.yml', false);
|
|
542
|
-
|
|
543
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
544
|
-
'Effective maximumPreparingIssuesCount: null (source: unset (default))',
|
|
545
|
-
);
|
|
546
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
547
|
-
'Effective defaultLlmModelName: null (source: unset (default))',
|
|
548
|
-
);
|
|
549
|
-
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
550
|
-
'Effective defaultAgentName: null (source: unset (default))',
|
|
551
|
-
);
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
460
|
});
|
|
@@ -94,9 +94,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
94
94
|
|
|
95
95
|
const managerToken = input.credentials.manager.github.token;
|
|
96
96
|
const readme = await fetchProjectReadme(input.projectUrl, managerToken);
|
|
97
|
-
const readmeConfig = readme
|
|
98
|
-
? parseProjectReadmeConfig(readme, input.projectUrl)
|
|
99
|
-
: {};
|
|
97
|
+
const readmeConfig = readme ? parseProjectReadmeConfig(readme) : {};
|
|
100
98
|
|
|
101
99
|
const mergedInput = {
|
|
102
100
|
...input,
|
|
@@ -139,50 +137,6 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
139
137
|
: input.startPreparation,
|
|
140
138
|
};
|
|
141
139
|
|
|
142
|
-
type EffectiveConfigValue = string | number | null | undefined;
|
|
143
|
-
|
|
144
|
-
const resolveConfigSource = (
|
|
145
|
-
readmeValue: EffectiveConfigValue,
|
|
146
|
-
configFileValue: EffectiveConfigValue,
|
|
147
|
-
): 'readmeOverride' | 'configFile' | 'unset (default)' => {
|
|
148
|
-
if (readmeValue !== undefined && readmeValue !== null) {
|
|
149
|
-
return 'readmeOverride';
|
|
150
|
-
}
|
|
151
|
-
if (configFileValue !== undefined && configFileValue !== null) {
|
|
152
|
-
return 'configFile';
|
|
153
|
-
}
|
|
154
|
-
return 'unset (default)';
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
const formatEffectiveConfig = (
|
|
158
|
-
value: EffectiveConfigValue,
|
|
159
|
-
readmeValue: EffectiveConfigValue,
|
|
160
|
-
configFileValue: EffectiveConfigValue,
|
|
161
|
-
): string =>
|
|
162
|
-
`${value ?? 'null'} (source: ${resolveConfigSource(readmeValue, configFileValue)})`;
|
|
163
|
-
|
|
164
|
-
console.log(
|
|
165
|
-
`Effective maximumPreparingIssuesCount: ${formatEffectiveConfig(
|
|
166
|
-
mergedInput.startPreparation?.maximumPreparingIssuesCount,
|
|
167
|
-
readmeConfig.maximumPreparingIssuesCount,
|
|
168
|
-
input.startPreparation?.maximumPreparingIssuesCount,
|
|
169
|
-
)}`,
|
|
170
|
-
);
|
|
171
|
-
console.log(
|
|
172
|
-
`Effective defaultLlmModelName: ${formatEffectiveConfig(
|
|
173
|
-
mergedInput.startPreparation?.defaultLlmModelName,
|
|
174
|
-
readmeConfig.defaultLlmModelName,
|
|
175
|
-
input.startPreparation?.defaultLlmModelName,
|
|
176
|
-
)}`,
|
|
177
|
-
);
|
|
178
|
-
console.log(
|
|
179
|
-
`Effective defaultAgentName: ${formatEffectiveConfig(
|
|
180
|
-
mergedInput.startPreparation?.defaultAgentName,
|
|
181
|
-
readmeConfig.defaultAgentName,
|
|
182
|
-
input.startPreparation?.defaultAgentName,
|
|
183
|
-
)}`,
|
|
184
|
-
);
|
|
185
|
-
|
|
186
140
|
const systemDateRepository = new SystemDateRepository();
|
|
187
141
|
const localStorageRepository = new LocalStorageRepository();
|
|
188
142
|
const googleSpreadsheetRepository = new GoogleSpreadsheetRepository(
|
|
@@ -280,6 +280,129 @@ describe('RateLimitCache', () => {
|
|
|
280
280
|
});
|
|
281
281
|
});
|
|
282
282
|
|
|
283
|
+
describe('writeRateLimit preserves previous values when response has no anthropic-ratelimit-* headers', () => {
|
|
284
|
+
it('should not write any file when no previous cache exists and headers contain no anthropic-ratelimit-*', () => {
|
|
285
|
+
const token = '429-no-headers-no-prior-token';
|
|
286
|
+
writeRateLimit(token, {
|
|
287
|
+
'content-type': 'application/json',
|
|
288
|
+
});
|
|
289
|
+
expect(fs.existsSync(cachePathForToken(token))).toBe(false);
|
|
290
|
+
expect(readRateLimit(token)).toBeNull();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should preserve the previous cache when a later response carries no anthropic-ratelimit-* headers (429 without headers)', () => {
|
|
294
|
+
const token = '429-no-headers-with-prior-token';
|
|
295
|
+
writeRateLimit(token, {
|
|
296
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
297
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
298
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
299
|
+
'anthropic-ratelimit-unified-5h-utilization': '42',
|
|
300
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
301
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
302
|
+
'anthropic-ratelimit-unified-7d-utilization': '17',
|
|
303
|
+
});
|
|
304
|
+
const beforeContent = fs.readFileSync(cachePathForToken(token), 'utf8');
|
|
305
|
+
writeRateLimit(token, {
|
|
306
|
+
'content-type': 'application/json',
|
|
307
|
+
'anthropic-organization-id': 'org-1',
|
|
308
|
+
});
|
|
309
|
+
const afterContent = fs.readFileSync(cachePathForToken(token), 'utf8');
|
|
310
|
+
expect(afterContent).toBe(beforeContent);
|
|
311
|
+
const snapshot = readRateLimit(token);
|
|
312
|
+
expect(snapshot?.fiveHourUtilization).toBe(42);
|
|
313
|
+
expect(snapshot?.fiveHourReset).toBe(1700000000);
|
|
314
|
+
expect(snapshot?.sevenDayUtilization).toBe(17);
|
|
315
|
+
expect(snapshot?.sevenDayReset).toBe(1700100000);
|
|
316
|
+
expect(snapshot?.unifiedRejected).toBe(false);
|
|
317
|
+
expect(snapshot?.fiveHourRejected).toBe(false);
|
|
318
|
+
expect(snapshot?.sevenDayRejected).toBe(false);
|
|
319
|
+
expect(snapshot?.blocked).toBe(false);
|
|
320
|
+
expect(snapshot?.rejected).toBe(false);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should overwrite the previous cache when the response carries anthropic-ratelimit-* headers regardless of status code (429 with headers)', () => {
|
|
324
|
+
const token = '429-with-headers-token';
|
|
325
|
+
writeRateLimit(token, {
|
|
326
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
327
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
328
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
329
|
+
'anthropic-ratelimit-unified-5h-utilization': '42',
|
|
330
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
331
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
332
|
+
'anthropic-ratelimit-unified-7d-utilization': '17',
|
|
333
|
+
});
|
|
334
|
+
writeRateLimit(token, {
|
|
335
|
+
'anthropic-ratelimit-unified-status': 'rejected',
|
|
336
|
+
'anthropic-ratelimit-unified-5h-status': 'rejected',
|
|
337
|
+
'anthropic-ratelimit-unified-5h-reset': '1700050000',
|
|
338
|
+
'anthropic-ratelimit-unified-5h-utilization': '100',
|
|
339
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
340
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
341
|
+
'anthropic-ratelimit-unified-7d-utilization': '60',
|
|
342
|
+
});
|
|
343
|
+
const snapshot = readRateLimit(token);
|
|
344
|
+
expect(snapshot?.unifiedRejected).toBe(true);
|
|
345
|
+
expect(snapshot?.fiveHourRejected).toBe(true);
|
|
346
|
+
expect(snapshot?.fiveHourUtilization).toBe(100);
|
|
347
|
+
expect(snapshot?.fiveHourReset).toBe(1700050000);
|
|
348
|
+
expect(snapshot?.sevenDayUtilization).toBe(60);
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should overwrite the previous cache when a 200 response carries anthropic-ratelimit-* headers (200 with headers)', () => {
|
|
352
|
+
const token = '200-with-headers-token';
|
|
353
|
+
writeRateLimit(token, {
|
|
354
|
+
'anthropic-ratelimit-unified-status': 'rejected',
|
|
355
|
+
'anthropic-ratelimit-unified-5h-status': 'rejected',
|
|
356
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
357
|
+
'anthropic-ratelimit-unified-5h-utilization': '100',
|
|
358
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
359
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
360
|
+
'anthropic-ratelimit-unified-7d-utilization': '60',
|
|
361
|
+
});
|
|
362
|
+
writeRateLimit(token, {
|
|
363
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
364
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
365
|
+
'anthropic-ratelimit-unified-5h-reset': '1700050000',
|
|
366
|
+
'anthropic-ratelimit-unified-5h-utilization': '30',
|
|
367
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
368
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
369
|
+
'anthropic-ratelimit-unified-7d-utilization': '20',
|
|
370
|
+
});
|
|
371
|
+
const snapshot = readRateLimit(token);
|
|
372
|
+
expect(snapshot?.unifiedRejected).toBe(false);
|
|
373
|
+
expect(snapshot?.fiveHourRejected).toBe(false);
|
|
374
|
+
expect(snapshot?.fiveHourUtilization).toBe(30);
|
|
375
|
+
expect(snapshot?.fiveHourReset).toBe(1700050000);
|
|
376
|
+
expect(snapshot?.sevenDayUtilization).toBe(20);
|
|
377
|
+
expect(snapshot?.rejected).toBe(false);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('should not modify any header value based on response status code', () => {
|
|
381
|
+
const token = 'no-status-based-mutation-token';
|
|
382
|
+
const inputHeaders: Record<string, string> = {
|
|
383
|
+
'anthropic-ratelimit-unified-status': 'allowed',
|
|
384
|
+
'anthropic-ratelimit-unified-5h-status': 'allowed',
|
|
385
|
+
'anthropic-ratelimit-unified-5h-reset': '1700000000',
|
|
386
|
+
'anthropic-ratelimit-unified-5h-utilization': '42',
|
|
387
|
+
'anthropic-ratelimit-unified-7d-status': 'allowed',
|
|
388
|
+
'anthropic-ratelimit-unified-7d-reset': '1700100000',
|
|
389
|
+
'anthropic-ratelimit-unified-7d-utilization': '17',
|
|
390
|
+
};
|
|
391
|
+
writeRateLimit(token, inputHeaders);
|
|
392
|
+
const raw: unknown = JSON.parse(
|
|
393
|
+
fs.readFileSync(cachePathForToken(token), 'utf8'),
|
|
394
|
+
);
|
|
395
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
396
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
397
|
+
if (!isRecord(raw) || !isRecord(raw.headers)) {
|
|
398
|
+
throw new Error('expected stored cache to contain headers object');
|
|
399
|
+
}
|
|
400
|
+
for (const [key, expectedValue] of Object.entries(inputHeaders)) {
|
|
401
|
+
expect(raw.headers[key]).toBe(expectedValue);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
|
|
283
406
|
describe('parseModelRateLimitsFromBody', () => {
|
|
284
407
|
it('should extract a rejected seven_day_sonnet limit from a rate_limit event body', () => {
|
|
285
408
|
const body =
|
|
@@ -71,17 +71,11 @@ export const writeRateLimit = (
|
|
|
71
71
|
token: string,
|
|
72
72
|
headers: Record<string, string | string[] | undefined>,
|
|
73
73
|
): void => {
|
|
74
|
-
const dir = cacheDir();
|
|
75
|
-
if (!fs.existsSync(dir)) {
|
|
76
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
77
|
-
}
|
|
78
74
|
const pick = (key: string): string | undefined => {
|
|
79
75
|
const value = headers[key];
|
|
80
76
|
if (Array.isArray(value)) return value[0];
|
|
81
77
|
return value;
|
|
82
78
|
};
|
|
83
|
-
const filePath = path.join(dir, `${hashToken(token)}.json`);
|
|
84
|
-
const existing = readPayload(filePath);
|
|
85
79
|
const rateLimitHeaders: Record<string, string> = {};
|
|
86
80
|
for (const key of Object.keys(headers)) {
|
|
87
81
|
if (key.startsWith('anthropic-ratelimit-')) {
|
|
@@ -91,6 +85,15 @@ export const writeRateLimit = (
|
|
|
91
85
|
}
|
|
92
86
|
}
|
|
93
87
|
}
|
|
88
|
+
if (Object.keys(rateLimitHeaders).length === 0) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const dir = cacheDir();
|
|
92
|
+
if (!fs.existsSync(dir)) {
|
|
93
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
const filePath = path.join(dir, `${hashToken(token)}.json`);
|
|
96
|
+
const existing = readPayload(filePath);
|
|
94
97
|
const payload = {
|
|
95
98
|
ts: Date.now() / 1000,
|
|
96
99
|
headers: rateLimitHeaders,
|