Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1610627
refactor(openai-shim): extract request execution
jatmn Jul 18, 2026
b0d8ba1
fix(openai-shim): rebase executor extraction
jatmn Jul 18, 2026
7ef850b
test(openai-shim): preserve local stream options coverage
jatmn Jul 18, 2026
e508f0b
test(openai-shim): isolate Azure compatibility state
jatmn Jul 18, 2026
dcc2da9
fix(openai-shim): preserve executor retry contracts
jatmn Jul 30, 2026
3259f25
fix(openai-shim): retain route credential isolation
jatmn Jul 30, 2026
2642ebe
fix(openai-shim): preserve executor transport behavior
jatmn Jul 30, 2026
1dc1cbb
fix(openai-shim): avoid duplicate local retries
jatmn Jul 30, 2026
33b65f0
fix(openai-shim): preserve LongCat credential routing
jatmn Jul 30, 2026
05ebfa6
fix(openai-shim): retain executor abort contracts
jatmn Jul 30, 2026
99d6966
fix(openai-shim): preserve fallback cancellation
jatmn Jul 30, 2026
805b8b6
fix(openai-shim): preserve executor retry and transport contracts
jatmn Jul 30, 2026
8726767
fix(openai-shim): stabilize extracted executor smoke coverage
jatmn Jul 30, 2026
0fa19e7
test(openai-shim): cover extracted executor retry contracts
jatmn Jul 30, 2026
7638a36
test(openai-shim): assert redacted HTTP errors
jatmn Jul 30, 2026
9bdb776
test(openai-shim): isolate Azure executor configuration
jatmn Jul 30, 2026
c0d8eb5
fix(openai-shim): preserve executor recovery retries
jatmn Jul 30, 2026
368b5f3
test(openai-shim): remove migrated executor duplicates
jatmn Jul 30, 2026
7b494b2
docs(openai-shim): clarify extracted façade budget
jatmn Jul 30, 2026
8c3cb39
test(openai-shim): enforce extraction modules
jatmn Jul 30, 2026
fbebf7a
fix(openai-shim): stop retrying cooled GitHub keys
jatmn Jul 30, 2026
7e30882
fix(openai-shim): avoid concurrent cooled-key retries
jatmn Jul 30, 2026
b0d3846
test(openai-shim): stabilize pooled-key retry coverage
jatmn Jul 30, 2026
cf50221
fix(openai-shim): preserve newer credential cooldowns
jatmn Jul 30, 2026
b548b49
docs(openai-shim): explain stale auth eviction
jatmn Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion src/services/api/credentialPool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,34 @@ test('CredentialPool cools down recoverable failures', () => {
expect(pool.next()?.value).toBe('key-a')
})

test('CredentialPool does not let an older success clear a newer cooldown', () => {
const pool = new CredentialPool(['key-a', 'key-b'])
const firstA = pool.next()
const keyB = pool.next()

pool.reportFailure(keyB, 'auth', 0)
const concurrentA = pool.next()
pool.reportFailure(concurrentA, 'cooldown', 30_000)
pool.reportSuccess(firstA)

expect(pool.hasAvailableCredential()).toBe(false)
})

test('CredentialPool lets a stale auth failure permanently disable a cooled key', () => {
let now = 1_000
const pool = new CredentialPool(['key-a', 'key-b'], () => now)
const firstA = pool.next()
const keyB = pool.next()

pool.reportFailure(keyB, 'auth', 0)
const concurrentA = pool.next()
pool.reportFailure(concurrentA, 'cooldown', 30_000)
pool.reportFailure(firstA, 'auth', 0)

now += 30_001
expect(pool.hasAvailableCredential()).toBe(false)
})

test('CredentialPool falls back to least-recently failed credential when all are cooling down', () => {
let now = 1_000
const pool = new CredentialPool(['key-a', 'key-b'], () => now)
Expand All @@ -56,5 +84,5 @@ test('CredentialPool falls back to least-recently failed credential when all are
const second = pool.next()
pool.reportFailure(second, 'cooldown', 30_000)

expect(pool.next()).toEqual({ value: 'key-a', index: 0 })
expect(pool.next()).toEqual({ value: 'key-a', index: 0, generation: 1 })
})
35 changes: 31 additions & 4 deletions src/services/api/credentialPool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ export type CredentialPoolFailureKind = 'auth' | 'cooldown'
export type CredentialLease = {
value: string
index: number
generation: number
}

type CredentialState = {
value: string
disabled: boolean
cooldownUntil: number
lastFailureAt: number
generation: number
}

export class CredentialPool {
Expand All @@ -23,6 +25,7 @@ export class CredentialPool {
disabled: false,
cooldownUntil: 0,
lastFailureAt: 0,
generation: 0,
}))
this.now = now
}
Expand All @@ -44,7 +47,7 @@ export class CredentialPool {
continue
}
this.cursor = (index + 1) % this.credentials.length
return { value: candidate.value, index }
return { value: candidate.value, index, generation: candidate.generation }
}

let leastRecentlyFailedIndex = -1
Expand All @@ -66,13 +69,30 @@ export class CredentialPool {

const fallback = this.credentials[leastRecentlyFailedIndex]
this.cursor = (leastRecentlyFailedIndex + 1) % this.credentials.length
return { value: fallback.value, index: leastRecentlyFailedIndex }
return {
value: fallback.value,
index: leastRecentlyFailedIndex,
generation: fallback.generation,
}
}

hasAvailableCredential(): boolean {
const now = this.now()
return this.credentials.some(
credential => !credential.disabled && credential.cooldownUntil <= now,
)
}

reportSuccess(lease: CredentialLease | null): void {
if (!lease) return
const credential = this.credentials[lease.index]
if (!credential || credential.value !== lease.value) return
if (
!credential ||
credential.value !== lease.value ||
credential.generation !== lease.generation
) {
return
}
credential.cooldownUntil = 0
}

Expand All @@ -83,9 +103,16 @@ export class CredentialPool {
): void {
if (!lease) return
const credential = this.credentials[lease.index]
if (!credential || credential.value !== lease.value) return
if (!credential || credential.value !== lease.value) {
return
}
// Auth failures must evict a key even when a newer cooldown advanced its lease.
if (kind !== 'auth' && credential.generation !== lease.generation) {
return
}

const now = this.now()
credential.generation += 1
Comment thread
coderabbitai[bot] marked this conversation as resolved.
credential.lastFailureAt = now
if (kind === 'auth') {
credential.disabled = true
Expand Down
Loading