From 7fdcba9e0522b218ada8d4c5f5473ef89e55b183 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 16 May 2026 21:31:03 -0300 Subject: [PATCH] fix(auth): return synthetic credentials for noAuth free providers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Requests to opencode (noAuth: true) were failing with "No credentials for provider: opencode" because getProviderCredentials found no DB connections and returned null — triggering the 400 error path in chatHelpers. Inject a synthetic credential object (connectionId: "noauth", no apiKey/token) before the regular connection lookup so the executor receives valid credentials and skips the Authorization header. Also enable model import for noAuth providers (canImportModels = true when isFreeNoAuth). --- .../dashboard/providers/[id]/page.tsx | 2 +- src/sse/services/auth.ts | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx index cac8ba720c..8298004691 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/page.tsx +++ b/src/app/(dashboard)/dashboard/providers/[id]/page.tsx @@ -2542,7 +2542,7 @@ export default function ProviderDetailPage() { } }; - const canImportModels = connections.some((conn) => conn.isActive !== false); + const canImportModels = isFreeNoAuth || connections.some((conn) => conn.isActive !== false); // Auto-sync toggle state: read from first active connection's providerSpecificData const autoSyncConnection = connections.find((conn: any) => conn.isActive !== false); diff --git a/src/sse/services/auth.ts b/src/sse/services/auth.ts index 210fb66a4e..2410e9edfa 100644 --- a/src/sse/services/auth.ts +++ b/src/sse/services/auth.ts @@ -37,7 +37,7 @@ import { PROVIDER_ERROR_TYPES, } from "@omniroute/open-sse/services/errorClassifier.ts"; import { getCodexModelScope } from "@omniroute/open-sse/executors/codex.ts"; -import { getProviderAlias, resolveProviderId } from "@/shared/constants/providers"; +import { getProviderAlias, resolveProviderId, FREE_PROVIDERS } from "@/shared/constants/providers"; import { isModelExcludedByConnection } from "@/domain/connectionModelRules"; import * as log from "../utils/logger"; import { fisherYatesShuffle, getNextFromDeckSync } from "@/shared/utils/shuffleDeck"; @@ -770,6 +770,29 @@ export async function getProviderCredentials( try { await currentMutex; + // noAuth free providers (e.g. opencode) need no DB connection — return synthetic credentials + // so the executor receives a valid credentials object without auth headers being added. + const resolvedId = resolveProviderId(provider); + if (FREE_PROVIDERS[resolvedId]?.noAuth) { + 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, + }; + } + const allowSuppressedConnections = options.allowSuppressedConnections === true; const bypassQuotaPolicy = options.bypassQuotaPolicy === true; const forcedConnectionId =