diff --git a/open-sse/config/providerRegistry.ts b/open-sse/config/providerRegistry.ts index 7dc2152795..1df5ac72c0 100644 --- a/open-sse/config/providerRegistry.ts +++ b/open-sse/config/providerRegistry.ts @@ -47,6 +47,8 @@ export interface RegistryEntry { executor: string; baseUrl?: string; baseUrls?: string[]; + /** Override base URL used only for API key validation (e.g., opencode-go validates on zen/v1) */ + testKeyBaseUrl?: string; responsesBaseUrl?: string; urlSuffix?: string; urlBuilder?: (base: string, model: string, stream: boolean) => string; @@ -501,6 +503,8 @@ export const REGISTRY: Record = { format: "openai", executor: "opencode", baseUrl: "https://opencode.ai/zen/go/v1", + // (#532) Key validation must hit the main zen endpoint (same key works for both tiers) + testKeyBaseUrl: "https://opencode.ai/zen/v1", authType: "apikey", authHeader: "Authorization", authPrefix: "Bearer", diff --git a/open-sse/executors/antigravity.ts b/open-sse/executors/antigravity.ts index 2e18d1aa48..fd09aaad3f 100644 --- a/open-sse/executors/antigravity.ts +++ b/open-sse/executors/antigravity.ts @@ -44,12 +44,28 @@ export class AntigravityExecutor extends BaseExecutor { // stale/wrong client-side values causing 404/403 from Cloud Code endpoints. // Opt-in escape hatch: set OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE=1. const projectId = - allowBodyProjectOverride && bodyProjectId ? bodyProjectId : credentialsProjectId || bodyProjectId; + allowBodyProjectOverride && bodyProjectId + ? bodyProjectId + : credentialsProjectId || bodyProjectId; if (!projectId) { - throw new Error( - "Missing Google projectId for Antigravity account. Please reconnect OAuth so OmniRoute can fetch your real Cloud Code project (loadCodeAssist)." - ); + // (#489) Return a structured error instead of throwing — gives the client a clear signal + // to show a "Reconnect OAuth" prompt rather than an opaque "Internal Server Error". + const errorMsg = + "Missing Google projectId for Antigravity account. Please reconnect OAuth in Providers → Antigravity so OmniRoute can fetch your Cloud Code project."; + const errorBody = { + error: { + message: errorMsg, + type: "oauth_missing_project_id", + code: "missing_project_id", + }, + }; + const resp = new Response(JSON.stringify(errorBody), { + status: 422, + headers: { "Content-Type": "application/json" }, + }); + // Returning a Response object signals the executor to stop and forward it + return resp as unknown as never; } // Fix contents for Claude models via Antigravity diff --git a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx index f7c88cc631..75e59468bd 100644 --- a/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx +++ b/src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx @@ -523,15 +523,12 @@ export default function ApiManagerPageClient() {
{key.key} - + lock +
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 3b51b6f3b7..e17e0c53b3 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -69,6 +69,11 @@ export default function LoginPage() { router.refresh(); } else { const data = await res.json(); + // (#521) If no password is set, redirect to onboarding instead of showing an error + if (data.needsSetup) { + router.push("/dashboard/onboarding"); + return; + } setError(data.error || t("invalidPassword")); } } catch (err) { diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index a7e5f0cf57..295e794f90 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -281,6 +281,7 @@ "failedUpdatePermissionsRetry": "Failed to update permissions. Please try again.", "unknownProvider": "unknown", "copyMaskedKey": "Copy masked key", + "keyOnlyAvailableAtCreation": "Full key available only at creation time — copy it when you first create the key", "modelsCount": "{count, plural, one {# model} other {# models}}", "lastUsedOn": "Last: {date}", "editPermissions": "Edit permissions", diff --git a/src/lib/providers/validation.ts b/src/lib/providers/validation.ts index dd71ef91f8..ae71e4e857 100644 --- a/src/lib/providers/validation.ts +++ b/src/lib/providers/validation.ts @@ -610,7 +610,12 @@ export async function validateProviderApiKey({ provider, apiKey, providerSpecifi } const modelId = entry.models?.[0]?.id || null; - const baseUrl = resolveBaseUrl(entry, providerSpecificData); + // (#532) Use testKeyBaseUrl if defined — some providers validate keys on a different endpoint + // than where requests are sent (e.g. opencode-go validates on zen/v1, not zen/go/v1) + const validationEntry = entry.testKeyBaseUrl + ? { ...entry, baseUrl: entry.testKeyBaseUrl } + : entry; + const baseUrl = resolveBaseUrl(validationEntry, providerSpecificData); try { if (OPENAI_LIKE_FORMATS.has(entry.format)) {