mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
36
.env.example
36
.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
|
||||
|
||||
30
CHANGELOG.md
30
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
|
||||
|
||||
93
README.md
93
README.md
@@ -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>
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 só 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're accessing OmniRoute remotely,
|
||||
|
||||
Reference in New Issue
Block a user