diff --git a/src/sse/handlers/chat.ts b/src/sse/handlers/chat.ts index 446dbb61ea..cd110ce263 100644 --- a/src/sse/handlers/chat.ts +++ b/src/sse/handlers/chat.ts @@ -787,7 +787,7 @@ async function handleChatImplementation( ...(bypassProviderQuotaPolicy ? { bypassQuotaPolicy: true } : {}), } ); - if (!creds || creds.allRateLimited) return false; + if (!creds || !("authType" in creds)) return false; // OAuth selection must happen atomically with occupancy reservation in the // actual dispatch. Availability preflight may finish well before a combo diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 0e8e7c868c..1802423c44 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -654,6 +654,7 @@ type AnonymousFallbackProviderDefinition = { noAuth?: boolean; }; function buildSyntheticNoAuthCredentials(providerSpecificData: JsonRecord = {}): { + authType: "none"; apiKey: null; accessToken: null; refreshToken: null; @@ -676,6 +677,7 @@ function buildSyntheticNoAuthCredentials(providerSpecificData: JsonRecord = {}): retryAfterHuman?: never; } { return { + authType: "none", apiKey: null, accessToken: null, refreshToken: null, diff --git a/tests/unit/sse-auth.test.ts b/tests/unit/sse-auth.test.ts index 4924eb741c..8e61ccef52 100644 --- a/tests/unit/sse-auth.test.ts +++ b/tests/unit/sse-auth.test.ts @@ -117,6 +117,13 @@ test("extractApiKey parses bearer headers and isValidApiKey validates persisted assert.equal(await auth.isValidApiKey(""), false); }); +test("getProviderCredentials identifies synthetic no-auth credentials", async () => { + const credentials = await auth.getProviderCredentials("opencode"); + + assert.equal(credentials?.connectionId, "noauth"); + assert.equal(credentials?.authType, "none"); +}); + test("getProviderCredentials reports rate limiting when only inactive suppressed records remain", async () => { const retryAfter = futureIso(); await seedConnection("openai", {