fix(auth): opencode-zen falls back to anonymous no-auth when no key (#2962)

opencode-zen serves the public, signup-free OpenCode Zen endpoint
(https://opencode.ai/zen/v1). With no API-key connection configured,
getProviderCredentials returned null, so the Playground/combos surfaced
"No credentials for provider: opencode-zen" when selecting an OpenCode free
model.

Fall back to synthetic no-auth credentials for opencode-zen when no usable
connection exists (a configured active key is still selected first; a
rate-limited/terminal key returns its own signal before this point). Other
api-key providers are unaffected.

Closes #2962
This commit is contained in:
diegosouzapw
2026-05-31 01:47:39 -03:00
parent 20a91c08ed
commit 380d558586
3 changed files with 79 additions and 0 deletions

View File

@@ -94,6 +94,12 @@
validation falls through to the chat probe for such 403s instead of returning
"Invalid API key", and `checkFallbackError` short-circuits them to no cooldown.
Genuine auth failures (401 / generic 403) still fail fast. (#2929)
- **auth/opencode-zen:** the OpenCode Zen free model now works in the Playground
and combos without an API key. `opencode-zen` serves the public, signup-free
endpoint (`https://opencode.ai/zen/v1`); when no api-key connection is
configured, credential resolution now falls back to anonymous (no-auth) access
instead of failing with "No credentials for provider: opencode-zen". A
configured, active key is still used when present. (#2962)
### ✨ New Features

View File

@@ -943,6 +943,31 @@ export async function getProviderCredentials(
};
}
}
// #2962: opencode-zen exposes the public, signup-free OpenCode Zen endpoint
// (https://opencode.ai/zen/v1). With no usable API-key connection, fall back
// to anonymous (no-auth) access — the free tier — instead of erroring with
// "No credentials". This is what the Playground/combos hit when selecting an
// OpenCode free model. A configured, active key is still selected above; a
// rate-limited/terminal key returns its own signal before reaching here.
if (resolvedId === "opencode-zen") {
return {
apiKey: null,
accessToken: null,
refreshToken: null,
expiresAt: null,
projectId: null,
copilotToken: null,
providerSpecificData: {},
connectionId: "noauth",
testStatus: "active",
lastError: null,
lastErrorType: null,
lastErrorSource: null,
errorCode: null,
rateLimitedUntil: null,
maxConcurrent: null,
};
}
log.warn("AUTH", `No credentials for ${provider}`);
return null;
}

View File

@@ -0,0 +1,48 @@
/**
* Issue #2962 — Playground cannot use the OpenCode free model:
* "No credentials for the provider: opencode-zen".
*
* opencode-zen serves the public, signup-free OpenCode Zen endpoint
* (https://opencode.ai/zen/v1). When no API-key connection is configured,
* getProviderCredentials returned null → the chat handler surfaced
* "No credentials for provider: opencode-zen". It must instead fall back to
* anonymous (no-auth) credentials so the free tier works.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-opencode-zen-noauth-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const { getProviderCredentials } = await import("../../src/sse/services/auth.ts");
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});
test("#2962 opencode-zen with no connection falls back to anonymous no-auth credentials", async () => {
const creds = await getProviderCredentials("opencode-zen");
assert.ok(creds, "opencode-zen must resolve to credentials, not null (no-auth free tier)");
assert.equal(
(creds as { connectionId?: string }).connectionId,
"noauth",
"should be synthetic no-auth credentials"
);
assert.equal(
(creds as { apiKey?: unknown }).apiKey,
null,
"anonymous access carries no api key"
);
});
test("#2962 a normal api-key provider with no connection still returns null (no over-broadening)", async () => {
const creds = await getProviderCredentials("openai");
// Must NOT synthesize no-auth creds for a real api-key provider.
const connectionId = (creds as { connectionId?: string } | null)?.connectionId;
assert.notEqual(connectionId, "noauth", "openai must not get anonymous no-auth credentials");
});