Skip to content

Commit e80cb3d

Browse files
committed
fix: add proper rate limiting for GitHub Models API (15 req/min)
1 parent cd85924 commit e80cb3d

File tree

3 files changed

+22
-8
lines changed

3 files changed

+22
-8
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "opencode-codebase-index",
3-
"version": "0.2.3",
3+
"version": "0.2.4",
44
"description": "Semantic codebase indexing and search for OpenCode - find code by meaning, not just keywords",
55
"type": "module",
66
"main": "dist/index.js",

src/indexer/index.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,19 @@ export class Indexer {
498498
}
499499
}
500500

501-
const queue = new PQueue({ concurrency: 3 });
501+
// GitHub Models API rate limit: 15 requests/minute for embeddings
502+
// Use concurrency 1 with 4-second delay to stay under limit (15 req/min = 1 req per 4 sec)
503+
const queue = new PQueue({ concurrency: 1, interval: 4000, intervalCap: 1 });
502504
const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
505+
let rateLimitBackoffMs = 0;
503506

504507
for (const batch of dynamicBatches) {
505508
queue.add(async () => {
509+
// Additional backoff if we've been rate limited
510+
if (rateLimitBackoffMs > 0) {
511+
await new Promise(resolve => setTimeout(resolve, rateLimitBackoffMs));
512+
}
513+
506514
try {
507515
const result = await pRetry(
508516
async () => {
@@ -511,15 +519,16 @@ export class Indexer {
511519
},
512520
{
513521
retries: this.config.indexing.retries,
514-
minTimeout: this.config.indexing.retryDelayMs,
515-
maxTimeout: 30000,
522+
minTimeout: Math.max(this.config.indexing.retryDelayMs, 5000), // Minimum 5s between retries
523+
maxTimeout: 60000, // Max 60s backoff
516524
factor: 2,
517525
onFailedAttempt: (error) => {
518526
const message = getErrorMessage(error);
519527
if (isRateLimitError(error)) {
520-
queue.concurrency = 1;
528+
// Exponential backoff: 10s, 20s, 40s...
529+
rateLimitBackoffMs = Math.min(60000, (rateLimitBackoffMs || 5000) * 2);
521530
console.error(
522-
`Rate limited (attempt ${error.attemptNumber}/${error.retriesLeft + error.attemptNumber}): waiting before retry...`
531+
`Rate limited (attempt ${error.attemptNumber}/${error.retriesLeft + error.attemptNumber}): waiting ${rateLimitBackoffMs / 1000}s before retry...`
523532
);
524533
} else {
525534
console.error(
@@ -529,6 +538,11 @@ export class Indexer {
529538
},
530539
}
531540
);
541+
542+
// Success - gradually reduce backoff
543+
if (rateLimitBackoffMs > 0) {
544+
rateLimitBackoffMs = Math.max(0, rateLimitBackoffMs - 2000);
545+
}
532546

533547
const items = batch.map((chunk, idx) => ({
534548
id: chunk.id,

0 commit comments

Comments
 (0)