From 5ce95dd82925e0222af6dd7debdd5610e7e2d3b3 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Fri, 20 Feb 2026 11:50:50 -0300 Subject: [PATCH] =?UTF-8?q?fix:=20resolve=20issues=20#89=20#90=20#91=20?= =?UTF-8?q?=E2=80=94=20stream=20default,=20custom=20models,=20OAuth=20redi?= =?UTF-8?q?rect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix(chatCore): stream defaults to false per OpenAI spec (body.stream === true) closes #89 - fix(models): custom OpenAI-compatible providers now appear in /v1/models Added raw providerId check against activeAliases closes #90 - fix(OAuthModal): Google OAuth providers force localhost redirect URI Targeted amber warning + redirect_uri_mismatch error guidance for remote deployments closes #91 - docs: Add 'OAuth em Servidor Remoto' tutorial in README with 7-step Google Cloud guide - docs: .env.example prominent warning block for Google OAuth remote setup - chore: bump version 1.0.6 → 1.0.7 - docs: CHANGELOG entry for v1.0.7 --- .env.example | 36 +++++++++-- CHANGELOG.md | 30 +++++++++ README.md | 93 ++++++++++++++++++++++++++++ open-sse/handlers/chatCore.ts | 4 +- package.json | 2 +- src/app/api/v1/models/route.ts | 10 ++- src/shared/components/OAuthModal.tsx | 61 ++++++++++++++++-- 7 files changed, 221 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index ba9de2112d..15fceaea12 100644 --- a/.env.example +++ b/.env.example @@ -82,17 +82,45 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true # Provider OAuth Credentials (optional — override hardcoded defaults) # These can also be set via data/provider-credentials.json # CLAUDE_OAUTH_CLIENT_ID= -# GEMINI_OAUTH_CLIENT_ID= -GEMINI_OAUTH_CLIENT_SECRET=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 + +# ───────────────────────────────────────────────────────────────────────────── +# ⚠️ GOOGLE OAUTH (Antigravity, Gemini CLI) — IMPORTANT FOR REMOTE SERVERS +# ───────────────────────────────────────────────────────────────────────────── +# The built-in Google OAuth credentials ONLY work when OmniRoute runs on +# localhost (127.0.0.1 / local network). They are registered with +# redirect_uri = http://localhost:PORT/callback and Google will reject any +# other redirect URI with: redirect_uri_mismatch. +# +# If you are hosting OmniRoute on a remote server (VPS, Docker, cloud), you +# MUST register your own Google Cloud OAuth 2.0 credentials: +# +# 1. Go to https://console.cloud.google.com/apis/credentials +# 2. Create an OAuth 2.0 Client ID (type: "Web application") +# 3. Add your server URL as Authorized redirect URI: +# https://your-server.com/callback +# 4. Copy the Client ID and Client Secret below. +# +# See the full tutorial in README.md → "OAuth em Servidor Remoto" section. +# +# Antigravity (Google Gemini Code Assist): +# ANTIGRAVITY_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com +# ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-your-secret +ANTIGRAVITY_OAUTH_CLIENT_SECRET=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 + +# Gemini CLI (Google AI): +# GEMINI_OAUTH_CLIENT_ID=your-client-id.apps.googleusercontent.com +# GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-your-secret # GEMINI_CLI_OAUTH_CLIENT_ID= +GEMINI_OAUTH_CLIENT_SECRET=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 GEMINI_CLI_OAUTH_CLIENT_SECRET=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 +# ───────────────────────────────────────────────────────────────────────────── + +# CLAUDE_OAUTH_CLIENT_ID= # CODEX_OAUTH_CLIENT_ID= # CODEX_OAUTH_CLIENT_SECRET= # QWEN_OAUTH_CLIENT_ID= # IFLOW_OAUTH_CLIENT_ID= IFLOW_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW -# ANTIGRAVITY_OAUTH_CLIENT_ID= -ANTIGRAVITY_OAUTH_CLIENT_SECRET=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 # API Key Providers (Phase 1 + Phase 4) # Add via Dashboard → Providers → Add API Key, or set here diff --git a/CHANGELOG.md b/CHANGELOG.md index 4613e26c52..c1f7594904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [1.0.7] — 2026-02-20 + +> ### 🐛 Bugfix Release — OpenAI Compatibility, Custom Models & OAuth UX +> +> Fixes three community-reported issues: stream default now follows OpenAI spec, custom OpenAI-compatible providers appear in `/v1/models`, and Google OAuth shows a clear error + tutorial for remote deployments. + +### 🐛 Bug Fixes + +- **`stream` defaults to `false`** — Aligns with the OpenAI specification which explicitly states `stream` defaults to `false`. Previously OmniRoute defaulted to `true`, causing SSE data to be returned instead of a JSON object, breaking clients like Spacebot, OpenCode, and standard Python/Rust/Go OpenAI SDKs that don't explicitly set `stream: true` ([#89](https://github.com/diegosouzapw/OmniRoute/issues/89)) +- **Custom AI providers now appear in `/v1/models`** — OpenAI-compatible custom providers (e.g. FriendLI) whose provider ID wasn't in the built-in alias map were silently excluded from the models list even when active. Fixed by also checking the raw provider ID from the database against active connections ([#90](https://github.com/diegosouzapw/OmniRoute/issues/90)) +- **OAuth `redirect_uri_mismatch` — improved UX for remote deployments** — Google OAuth providers (Antigravity, Gemini CLI) now always use `localhost` as redirect URI matching the registered credentials. Remote-access users see a targeted amber warning with a link to the new setup guide. The token exchange error message explains the root cause and guides users to configure their own credentials ([#91](https://github.com/diegosouzapw/OmniRoute/issues/91)) + +### 📖 Documentation + +- **OAuth em Servidor Remoto tutorial** — New README section with step-by-step guide to configure custom Google Cloud OAuth 2.0 credentials for remote/VPS/Docker deployments +- **`.env.example` Google OAuth block** — Added prominent warning block explaining remote credential requirements with direct links to Google Cloud Console + +### 📁 Files Modified + +| File | Change | +| -------------------------------------- | ------------------------------------------------------------------------------------------- | +| `open-sse/handlers/chatCore.ts` | `stream` defaults to `false` (was `true`) per OpenAI spec | +| `src/app/api/v1/models/route.ts` | Added raw `providerId` check for custom models active-provider filter | +| `src/shared/components/OAuthModal.tsx` | Force `localhost` redirect for Google OAuth; improved `redirect_uri_mismatch` error message | +| `.env.example` | Added ⚠️ Google OAuth remote credentials block with step-by-step instructions | +| `README.md` | New "🔐 OAuth em Servidor Remoto" tutorial section | + +--- + ## [1.0.6] — 2026-02-20 > ### ✨ Provider & Combo Toggles — Strict Model Filtering @@ -353,6 +382,7 @@ New environment variables: --- +[1.0.7]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.7 [1.0.6]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.6 [1.0.5]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.5 [1.0.4]: https://github.com/diegosouzapw/OmniRoute/releases/tag/v1.0.4 diff --git a/README.md b/README.md index 094595e486..6a0bc0fab5 100644 --- a/README.md +++ b/README.md @@ -858,6 +858,99 @@ The pre-loaded "OmniRoute Golden Set" contains 10 test cases covering: --- +## 🔐 OAuth em Servidor Remoto (Remote OAuth Setup) + + + +> **⚠️ IMPORTANTE para usuários com OmniRoute em VPS/Docker/servidor remoto** + +### Por que o OAuth do Antigravity / Gemini CLI falha em servidores remotos? + +Os provedores **Antigravity** e **Gemini CLI** usam **Google OAuth 2.0** para autenticação. O Google exige que a `redirect_uri` usada no fluxo OAuth seja **exatamente** uma das URIs pré-cadastradas no Google Cloud Console do aplicativo. + +As credenciais OAuth embutidas no OmniRoute estão cadastradas **apenas para `localhost`**. Quando você acessa o OmniRoute em um servidor remoto (ex: `https://omniroute.meuservidor.com`), o Google rejeita a autenticação com: + +``` +Error 400: redirect_uri_mismatch +``` + +### Solução: Configure suas próprias credenciais OAuth + +Você precisa criar um **OAuth 2.0 Client ID** no Google Cloud Console com a URI do seu servidor. + +#### Passo a passo + +**1. Acesse o Google Cloud Console** + +Abra: [https://console.cloud.google.com/apis/credentials](https://console.cloud.google.com/apis/credentials) + +**2. Crie um novo OAuth 2.0 Client ID** + +- Clique em **"+ Create Credentials"** → **"OAuth client ID"** +- Tipo de aplicativo: **"Web application"** +- Nome: escolha qualquer nome (ex: `OmniRoute Remote`) + +**3. Adicione as Authorized Redirect URIs** + +No campo **"Authorized redirect URIs"**, adicione: + +``` +https://seu-servidor.com/callback +``` + +> Substitua `seu-servidor.com` pelo domínio ou IP do seu servidor (inclua a porta se necessário, ex: `http://45.33.32.156:20128/callback`). + +**4. Salve e copie as credenciais** + +Após criar, o Google mostrará o **Client ID** e o **Client Secret**. + +**5. Configure as variáveis de ambiente** + +No seu `.env` (ou nas variáveis de ambiente do Docker): + +```bash +# Para Antigravity: +ANTIGRAVITY_OAUTH_CLIENT_ID=seu-client-id.apps.googleusercontent.com +ANTIGRAVITY_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret + +# Para Gemini CLI: +GEMINI_OAUTH_CLIENT_ID=seu-client-id.apps.googleusercontent.com +GEMINI_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret +GEMINI_CLI_OAUTH_CLIENT_SECRET=GOCSPX-seu-secret +``` + +**6. Reinicie o OmniRoute** + +```bash +# Se usando npm: +npm run dev + +# Se usando Docker: +docker restart omniroute +``` + +**7. Tente conectar novamente** + +Dashboard → Providers → Antigravity (ou Gemini CLI) → OAuth + +Agora o Google redirecionará corretamente para `https://seu-servidor.com/callback` e a autenticação funcionará. + +--- + +### Workaround temporário (sem configurar credenciais próprias) + +Se não quiser criar credenciais próprias agora, ainda é possível usar o fluxo **manual de URL**: + +1. O OmniRoute abrirá a URL de autorização do Google +2. Após você autorizar, o Google tentará redirecionar para `localhost` (que falha no servidor remoto) +3. **Copie a URL completa** da barra de endereço do seu browser (mesmo que a página não carregue) +4. Cole essa URL no campo que aparece no modal de conexão do OmniRoute +5. Clique em **"Connect"** + +> Este workaround funciona porque o código de autorização na URL é válido independente do redirect ter carregado ou não. + +--- + ## 🐛 Troubleshooting
diff --git a/open-sse/handlers/chatCore.ts b/open-sse/handlers/chatCore.ts index 522bac1a65..95d404cb34 100644 --- a/open-sse/handlers/chatCore.ts +++ b/open-sse/handlers/chatCore.ts @@ -110,8 +110,8 @@ export async function handleChatCore({ const modelTargetFormat = getModelTargetFormat(alias, model); const targetFormat = modelTargetFormat || getTargetFormat(provider); - // Default to streaming unless client explicitly sets stream: false - const stream = body.stream !== false; + // Default to false unless client explicitly sets stream: true (OpenAI spec compliant) + const stream = body.stream === true; // ── Phase 9.1: Semantic cache check (non-streaming, temp=0 only) ── if (isCacheable(body, clientRawRequest?.headers)) { diff --git a/package.json b/package.json index bf9e2bd63d..1a00a6dfde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "1.0.6", + "version": "1.0.7", "description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.", "type": "module", "bin": { diff --git a/src/app/api/v1/models/route.ts b/src/app/api/v1/models/route.ts index c6db9a6a28..4fe9bf1fe8 100644 --- a/src/app/api/v1/models/route.ts +++ b/src/app/api/v1/models/route.ts @@ -259,8 +259,14 @@ export async function GET() { for (const [providerId, providerCustomModels] of Object.entries(customModelsMap)) { const alias = providerIdToAlias[providerId] || providerId; const canonicalProviderId = FALLBACK_ALIAS_TO_PROVIDER[alias] || providerId; - // Only include if provider is active - if (!activeAliases.has(alias) && !activeAliases.has(canonicalProviderId)) continue; + // Only include if provider is active — check alias, canonical ID, or raw providerId + // (raw check needed for OpenAI-compatible providers whose ID isn't in the alias map) + if ( + !activeAliases.has(alias) && + !activeAliases.has(canonicalProviderId) && + !activeAliases.has(providerId) + ) + continue; for (const model of providerCustomModels) { // Skip if already added as built-in diff --git a/src/shared/components/OAuthModal.tsx b/src/shared/components/OAuthModal.tsx index 5e06988aea..68db6845b1 100644 --- a/src/shared/components/OAuthModal.tsx +++ b/src/shared/components/OAuthModal.tsx @@ -53,6 +53,9 @@ export default function OAuthModal({ // Define all useCallback hooks BEFORE the useEffects that reference them + // Google OAuth providers that only accept pre-registered localhost redirect URIs + const GOOGLE_OAUTH_PROVIDERS = ["antigravity", "gemini-cli"]; + // Exchange tokens const exchangeTokens = useCallback( async (code, state) => { @@ -75,10 +78,26 @@ export default function OAuthModal({ setStep("success"); onSuccess?.(); } catch (err) { - setError(err.message); + // Provide actionable guidance for redirect_uri_mismatch on Google OAuth providers + if ( + err.message?.toLowerCase().includes("redirect_uri_mismatch") && + GOOGLE_OAUTH_PROVIDERS.includes(provider) + ) { + setError( + "redirect_uri_mismatch: As credenciais padrão do Google OAuth só funcionam em localhost. " + + "Para uso remoto, configure suas próprias credenciais OAuth nas variáveis de ambiente: " + + (provider === "antigravity" + ? "ANTIGRAVITY_OAUTH_CLIENT_ID e ANTIGRAVITY_OAUTH_CLIENT_SECRET" + : "GEMINI_OAUTH_CLIENT_ID e GEMINI_OAUTH_CLIENT_SECRET") + + ". Veja o README, seção 'OAuth em Servidor Remoto'." + ); + } else { + setError(err.message); + } setStep("error"); } }, + // eslint-disable-next-line react-hooks/exhaustive-deps [authData, provider, onSuccess] ); @@ -216,15 +235,23 @@ export default function OAuthModal({ } // Authorization code flow - // On localhost: use localhost callback so popup/BroadcastChannel/localStorage can relay code. - // On remote (behind nginx/reverse proxy): use the actual origin so the callback page loads. - // Codex (OpenAI) requires exactly http://localhost:1455/auth/callback — the registered URI. + // Redirect URI strategy: + // - Codex/OpenAI: always port 1455 (registered in OAuth app) + // - Google OAuth providers (antigravity, gemini-cli): always localhost, regardless of + // where OmniRoute is hosted — Google only accepts pre-registered localhost URIs with + // the built-in credentials. Remote users must configure their own credentials. + // - Other providers on remote: use actual origin (supports PUBLIC_URL env var) + // - Localhost: use localhost:port let redirectUri: string; if (provider === "codex" || provider === "openai") { redirectUri = "http://localhost:1455/auth/callback"; + } else if (GOOGLE_OAUTH_PROVIDERS.includes(provider)) { + // Google OAuth built-in credentials only accept localhost redirect URIs. + // Even in remote deployments we use localhost — user copies the callback URL manually. + const port = window.location.port || "20128"; + redirectUri = `http://localhost:${port}/callback`; } else if (!isLocalhost) { // Behind reverse proxy: use actual origin (e.g., https://omniroute.example.com/callback) - // This ensures the OAuth provider redirects to the proxied URL where the callback page loads. // Supports PUBLIC_URL env var override, or falls back to window.location.origin. const publicUrl = process.env.NEXT_PUBLIC_BASE_URL; const origin = @@ -466,7 +493,29 @@ export default function OAuthModal({ {step === "input" && !isDeviceCode && ( <>
- {!isLocalhost && ( + {/* Remote server info for Google OAuth providers */} + {!isLocalhost && GOOGLE_OAUTH_PROVIDERS.includes(provider) && ( +
+ + warning + + Acesso remoto + Google OAuth: As credenciais padrão só aceitam + redirect para localhost. Após autorizar, o browser tentará abrir + localhost — copie essa URL completa e cole abaixo. Para uso + totalmente remoto sem esse passo manual,{" "} + + configure suas próprias credenciais OAuth + + . +
+ )} + {/* Generic remote info for other providers */} + {!isLocalhost && !GOOGLE_OAUTH_PROVIDERS.includes(provider) && (
info Remote access: Since you're accessing OmniRoute remotely,