Merge fix/issues-89-90-91-v1.0.7: resolve #89 #90 #91, bump v1.0.7

This commit is contained in:
diegosouzapw
2026-02-20 11:51:19 -03:00
7 changed files with 221 additions and 15 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -858,6 +858,99 @@ The pre-loaded "OmniRoute Golden Set" contains 10 test cases covering:
---
## 🔐 OAuth em Servidor Remoto (Remote OAuth Setup)
<a name="oauth-em-servidor-remoto"></a>
> **⚠️ 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
<details>

View File

@@ -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)) {

View File

@@ -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": {

View File

@@ -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

View File

@@ -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 && (
<>
<div className="space-y-4">
{!isLocalhost && (
{/* Remote server info for Google OAuth providers */}
{!isLocalhost && GOOGLE_OAUTH_PROVIDERS.includes(provider) && (
<div className="rounded-lg border border-amber-500/30 bg-amber-500/10 p-3 text-xs text-amber-200">
<span className="material-symbols-outlined text-sm align-middle mr-1">
warning
</span>
<strong>Acesso remoto + Google OAuth:</strong> As credenciais padrão aceitam
redirect para <code>localhost</code>. Após autorizar, o browser tentará abrir
<code>localhost</code> copie essa URL completa e cole abaixo. Para uso
totalmente remoto sem esse passo manual,{" "}
<a
href="https://github.com/diegosouzapw/OmniRoute#oauth-em-servidor-remoto"
target="_blank"
rel="noreferrer"
className="underline"
>
configure suas próprias credenciais OAuth
</a>
.
</div>
)}
{/* Generic remote info for other providers */}
{!isLocalhost && !GOOGLE_OAUTH_PROVIDERS.includes(provider) && (
<div className="rounded-lg border border-blue-500/30 bg-blue-500/10 p-3 text-xs text-blue-200">
<span className="material-symbols-outlined text-sm align-middle mr-1">info</span>
<strong>Remote access:</strong> Since you&apos;re accessing OmniRoute remotely,