- Add state for custom app name and logo
- Fetch whitelabeling settings from /api/settings
- Listen for whitelabeling changes via settings event
- Display custom app name when set
- Display custom logo (Base64 or URL) when set
- Fall back to default OmniRoute logo and name
* test(settings): add unit tests for debugMode and hiddenSidebarItems
Tests cover:
- PATCH debugMode=true/false
- PATCH hiddenSidebarItems with array values
- Combined updates with both fields
* test(e2e): add Playwright tests for settings toggles
Tests cover:
- Debug mode toggle on/off
- Sidebar visibility toggle
- Settings persistence after page reload
* fix(tests): address code review issues
- Unit tests: fix async/await for getSettings, use direct db functions
- E2E tests: remove conditional logic, use Playwright auto-waiting assertions
* feat(logging): unify request log retention and artifacts
* docs: add dashboard settings toggles to CONTRIBUTING
Add section documenting:
- Debug Mode toggle (Settings → Advanced)
- Sidebar Visibility toggle (Settings → General)
* fix(cache): only inject prompt_cache_key for supported providers
Only inject prompt_cache_key for providers that support prompt caching
(Claude, Anthropic, ZAI, Qwen, DeepSeek). This fixes issue #848 where
NVIDIA API rejected the parameter.
* fix(model-sync): log only channel-level model changes
* feat(providers): add 4 free models to opencode-zen
* feat(providers): add explicit contextLength for opencode-zen free models
* feat(providers): add contextLength for all opencode-zen models
* feat: Improve the Chinese translation
* fix: preserve client cache_control for all Claude-protocol providers
Previously, the cache control preservation logic only recognized a
hardcoded list of providers (claude, anthropic, zai, qwen, deepseek).
This caused OmniRoute to inject its own cache_control markers for
Claude-protocol providers not in that list (bailian-coding-plan, glm,
minimax, minimax-cn, etc.), overwriting the client's cache markers.
The fix checks both:
1. Known caching providers list (existing behavior)
2. Whether targetFormat === 'claude' (all Claude-protocol providers)
This ensures all Claude-compatible providers properly preserve client
cache_control headers when appropriate (Claude Code client, deterministic
routing, etc.).
Also removes unused CacheStatsCard from settings/components (duplicate
of the one in cache/ page).
Fixes cache token calculation for GLM, Minimax, and other Claude-compatible providers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: pure passthrough for Claude→Claude when cache_control preserved
The Claude passthrough path round-trips through OpenAI format
(claude→openai→claude) for structural normalization. This strips
cache_control markers from every content block since OpenAI format
has no equivalent, causing ~42k cache creation tokens per request
with zero cache reads.
When preserveCacheControl is true (Claude Code client, "always"
setting, or deterministic combo), skip the round-trip entirely and
forward the body as-is. Claude Code sends well-formed Messages API
payloads — the normalization was only needed for non-Code clients.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: restore CacheStatsCard — was not a duplicate
The first commit incorrectly deleted CacheStatsCard from
settings/components/ as a "duplicate". It's the only copy — both
settings/page.tsx and cache/page.tsx import from this location.
Restored the i18n-ized version from main.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(429): parse long quota reset times from error body
- Parse XhYmZs format from antigravity error messages (e.g., 27h41m36s)
- Dynamic retry-after threshold (60s default) instead of hardcoded 10s
- Add parseRetryFromErrorText() in accountFallback.ts for body parsing
- Fix 403 'verify your account' to trigger permanent deactivation
- Add keyword matching for 'quota will reset', 'exhausted capacity'
- Add unit tests for retry parsing and keyword matching
Fixes#858 (Antigravity 429 handling)
Fixes#832 (Qwen quota 429 - same underlying bug)
* chore: bump version to v3.4.0-dev
* fix(migrations): rename 013 to 014 to avoid collision with v3.3.11
* chore(docs): update CHANGELOG for v3.4.0 integrations
* fix: Claude token refresh, Antigravity quota, and 429 rate-limit handling
- Fix Claude OAuth token refresh to use form-urlencoded format (standard OAuth2)
- Add anthropic-beta header required by Claude OAuth API
- Switch Antigravity quota to use retrieveUserQuota API (same as Gemini CLI)
- Parse quota reset time for all providers (not just Antigravity)
- Add quota reset keywords to error classifier
- Cap maximum retry time at 24 hours to prevent infinite wait
Closes#836, #857, #858, #832
* fix(dashboard): resolve /dashboard/limits hanging UI with 70+ accounts via chunk parallelization (#784)
---------
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
Co-authored-by: R.D. <rogerproself@gmail.com>
Co-authored-by: kang-heewon <heewon.dev@gmail.com>
Co-authored-by: gmw <rorschach1167@qq.com>
Co-authored-by: tombii <github@tombii.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
- Sync debugMode with showDebug in Sidebar (was using enableRequestLogs env var)
- Only render debug-section sidebar toggles in AppearanceTab when debugMode=true
- Sidebar filters debug-section items based on debugMode (was already correct)
- Debug toggle now triggers omniroute:settings-updated event for instant sidebar update
EOF
- Use ProviderIcon for internal .png paths solving SVG provider 404 images (#745).
- Add id-token: write and packages: write permissions to .github/workflows/electron-release.yml to fix permissions denied failure when calling the reusable workflow npm-publish.yml (#761).
- Fix tests and ESM resolution for autoUpdate.ts override logic.
Adds a new /dashboard/cache page that surfaces the existing but UI-less
semantic cache infrastructure.
Changes:
- New page: src/app/(dashboard)/dashboard/cache/page.tsx
- Live stats: memory entries, DB entries, cache hits, tokens saved
- Hit rate progress bar with color coding (green/yellow/red)
- Hits/Misses/Total breakdown
- Idempotency layer stats (active dedup keys + window)
- Cache behavior info panel
- Clear All button
- Auto-refresh every 10s
- Enhanced API: src/app/api/cache/route.ts
- DELETE ?model=<name> — invalidate by model
- DELETE ?signature=<hex> — invalidate single entry
- DELETE ?staleMs=<ms> — invalidate entries older than N ms
- DELETE (no params) — clear all (existing behavior)
- Sidebar: added Cache nav item (icon: cached)
- i18n: added cache + sidebar.cache keys for all 31 supported locales
No new dependencies. All functionality builds on existing semanticCache.ts,
cacheLayer.ts, and idempotencyLayer.ts modules.
Co-authored-by: oyi77 <oyi77@users.noreply.github.com>
## Proxy UI Bug Fixes
- fix: proxy badge on connection cards now uses resolveProxyForConnection()
per-connection (covers registry + config-file assignments)
- fix: Test Connection button now works in 'saved' proxy mode by resolving
proxy config from savedProxies list
- fix: ProxyConfigModal now calls onClose() after save/clear (fixes UI freeze)
- fix: ProxyRegistryManager loads usage eagerly on mount with deduplication
by scope+scopeId to prevent double-counting; adds per-row Test button
## Connection Tag Grouping (new feature)
- feat: add Tag/Group field to EditConnectionModal (stored in
providerSpecificData.tag, no DB schema change)
- feat: connections list groups by tag with visual dividers when any account
has a tag; untagged accounts appear first without header
## Post-merge fix from PR #607 review
- fix: function_call blocks in translateNonStreamingResponse now also strip
Claude OAuth proxy_ prefix via toolNameMap (kilo-code-bot #607 warning)
Affects OpenAI Responses API format path — tool_use was fixed in PR #607
but function_call was missed
Add model-pattern → combo mapping feature that automatically routes requests
to specific combos based on model name patterns (glob matching).
Implementation:
- New migration 010: model_combo_mappings table with pattern, combo_id, priority
- DB module with CRUD + resolveComboForModel() using glob-to-regex matching
- getComboForModel() in model.ts: augments getCombo() with pattern fallback
- chat.ts: replaced getCombo() → getComboForModel() at routing decision point
- API endpoints: GET/POST /api/model-combo-mappings, GET/PUT/DELETE by [id]
- ModelRoutingSection.tsx: dashboard UI with inline add/edit/toggle/delete
- Integrated into Combos page
- 15 new unit tests (glob matching, priority ordering, disabled filtering)
- Full test suite: 923/923 pass
Examples:
claude-sonnet* → code-combo
claude-*-opus* → frontier-combo
gpt-4o* → openai-combo
gemini-* → google-combo
Resolves: #563
Cherry-pick from codex/omniroute-fixes-20260324:
- Replace MCP singleton transport with per-session architecture for Streamable HTTP
- Fix Claude passthrough via OpenAI round-trip normalization
- Add detectFormatFromEndpoint() for endpoint-aware format detection
- Support raw code#state in OAuth modal for Claude Code remote auth
- Expose cloudConfigured/cloudUrl/machineId in settings API
- Switch docker-compose.prod.yml target to runner-cli
- Add 3 new tests for round-trip and detectFormat
PR: #562
feat(ui): ProviderIcon component with @lobehub/icons + PNG fallback (#529)
- 130+ providers covered by Lobehub SVG components via LobehubErrorBoundary
- Falls back to existing /providers/{id}.png, then generic icon
- Replaces manual img state machine in ProviderCard + ApiKeyProviderCard
feat(scheduler): modelSyncScheduler — 24h model list auto-update (#488)
- Syncs 16 major providers every 24h (MODEL_SYNC_INTERVAL_HOURS configurable)
- Wired into POST /api/sync/initialize startup hook
fix(oauth): Gemini CLI — clear error when client_secret missing in Docker (#537)
Search Playground (Phase 1):
- Web Search as 10th endpoint in Playground with isolated SearchPlayground component
- Endpoint selector moved first; Provider/Model/Send hidden when search selected
- Provider dropdown via GET /api/search/providers, formatted results with cache indicator
Search Tools page (Phase 2) at /dashboard/search-tools:
- Split panel: SearchForm (left) with query, provider, filters + ResultsPanel (right)
- Compare Providers: parallel queries with latency, cost, response size, URL overlap
- Rerank Pipeline: model selector from /v1/models, results with position delta
- Search History: last 10 searches from call_logs with replay
- Sidebar entry under Debug section
Backend:
- GET /api/search/providers — list providers with auth guard + SEARCH_CREDENTIAL_FALLBACKS
- GET /api/search/stats — cache stats, provider aggregates, recent searches (auth guard)
- Add local provider_nodes routing for /v1/rerank (oMLX, vLLM support)
Bug fixes (from F-27 PR #432):
- Fix Brave news normalizer: data.results directly, not data.news.results
- Enforce max_results truncation after normalization for all providers
- Fix EndpointPageClient: use /api/search/providers instead of /api/v1/search
- Add isAuthenticated() guards on /api/search/providers and /api/search/stats
Response size metric in results meta bar and compare table.
i18n: 30+ keys in search namespace (en.json)
* fix: tool description null sanitization, clipboard HTTP fallback fixes
T10 - Sanitize tool.description null in claude-to-openai translator
- claude-to-openai.ts: tool.description defaults to empty string when null/undefined
- claude-to-openai.ts: filter out tools with empty/missing names
- Prevents 400 validation errors on providers like NVIDIA NIM (issue #276)
T11 - Fix copy buttons to work on HTTP/non-HTTPS deployments
- Add src/shared/utils/clipboard.ts with HTTPS+HTTP (execCommand) dual fallback
- Migrate useCopyToClipboard.ts to use shared utility
- Migrate ConsoleLogViewer.tsx, RequestLoggerV2.tsx to shared utility
- Migrate HomePageClient.tsx, endpoint/page.tsx, GetStarted.tsx
- Migrate DefaultToolCard.tsx to shared utility
- Fixes copy buttons when OmniRoute runs behind HTTP proxy (issue #296)
T02 - Verified SSE [DONE] sentinel handling already correct
- sseParser.ts filters [DONE] on line 13 (no change needed)
- stream.ts uses doneSent flag to prevent duplicate sentinel
- bypassHandler.ts correctly separates streaming/non-streaming responses
Issue triage comments posted to #340, #341, #344
* feat: DB read cache + Accept header stream negotiation (T09/T01)
T09 - In-memory TTL cache for hot DB read paths
- Add src/lib/db/readCache.ts with TTL cache (5s settings/connections, 30s pricing)
- Eliminates redundant SQLite reads on concurrent requests
- Integrate invalidation in settings.ts updateSettings() and updatePricing()
- Integrate invalidation in providers.ts create/update/delete operations
- Export getCachedSettings, getCachedPricing, getCachedProviderConnections,
invalidateDbCache via localDb.ts for consumer migration
- Cache auto-busts on any write, preserving data consistency
T01 - Accept header stream negotiation
- src/sse/handlers/chat.ts: detect Accept: text/event-stream header
- Override body.stream=true when Accept header indicates streaming client
- Enables curl, httpx and SDK clients that use HTTP headers instead of JSON
body field to trigger streaming responses
- Logs Accept override at DEBUG level for observability
* fix: auto-advance quota window on expiry to prevent stale blocking (T08)
T08 - Quota Window Rolling Auto-Advance
- quotaCache.ts: add windowDurationMs field to QuotaCacheEntry interface
(optional field that callers can set when they know the window duration)
- Add advancedWindowResetAt() helper: if entry.nextResetAt is in the past,
eagerly returns { exhausted: false } so requests are unblocked immediately
- isAccountQuotaExhausted() now uses advancedWindowResetAt() instead of
the previous inline date check, and optimistically clears entry.exhausted
flag to avoid re-checking the same stale entry on the next request
Before: exhausted accounts with an expired resetAt would wait up to 5
minutes for the background refresh before accepting new requests.
After: the first request after resetAt passes will be immediately accepted
and will trigger a quota refresh on the next background tick.
* feat: manual OAuth token refresh UI (T12)
T12 - Manual Token Refresh UI
- Add POST /api/providers/[id]/refresh endpoint
- Validates connection exists and is OAuth type
- Calls getAccessToken() (same helper used in auto-refresh)
- Persists new credentials via updateProviderCredentials()
- Returns { success, expiresAt, refreshedAt } on success
- Update providers/[id]/page.tsx
- handleRefreshToken() with loading state (refreshingId)
- Pass onRefreshToken + isRefreshing props to ConnectionRow
- ConnectionRow: add optional onRefreshToken/isRefreshing props
- ConnectionRow: tokenMinsLeft state via lazy init (Date.now() in
getter fn, not in render body - satisfies react-hooks/purity)
- Token expiry badge: red 'expired' | amber '~Xm' (<30min) | hidden
- 'Token' button (amber) next to 'Retest' for OAuth connections
- Add en.json i18n: tokenRefreshed, tokenRefreshFailed
* Initial plan
* feat: integrate wildcardRouter into model alias resolution (T13)
T13 - Wildcard Model Routing
- Import resolveWildcardAlias from wildcardRouter.ts into model.ts
- In getModelInfoCore(), after exact alias check fails, try glob wildcard
alias matching (e.g., 'claude-sonnet-*' alias → 'anthropic/claude-sonnet-4')
- Returns { provider, model, extendedContext, wildcardPattern } on match
- Falls back to MODEL_TO_PROVIDERS lookup and openai default as before
* fix: clipboard cleanup and tool validation
* feat: media page UX + T04 playground uploads + T03 HuggingFace/Vertex AI
Media Page (MediaPageClient.tsx):
- Render images inline (img tags from b64_json or url)
- Show transcription as plain readable text (not raw JSON)
- Amber banner for credential errors with link to /dashboard/providers
- Detect empty transcription result and show credentials hint
- Provider credential hint below selector for non-local providers
- Extended provider/model lists: HuggingFace, Qwen TTS, Inworld, Cartesia, PlayHT, AssemblyAI
T04 - Playground File Uploads (playground/page.tsx):
- Audio file upload panel for transcription endpoint (multipart/form-data)
- Image upload panel for vision models (gpt-4o, claude-3, gemini, pixtral, llava...)
- Auto-detect vision models by name heuristic
- Inject uploaded images as base64 image_url in chat messages
- Inline image rendering for image generation results
- Readable text view for transcription results with copy button
- Preview thumbnails for attached images with individual remove
T03 - HuggingFace + Vertex AI Providers:
- HuggingFace: frontend providers.ts + backend providerRegistry.ts
Uses HuggingFace Router OpenAI-compatible endpoint
- Vertex AI: frontend providers.ts + backend providerRegistry.ts
Uses gemini format with generateContent API (urlBuilder fallback)
T07 - API Key Round-Robin: VERIFIED already implemented in auth.ts
fill-first, round-robin, p2c, random, least-used, cost-optimized strategies
* feat: T05 task-aware routing + fix#302 stream override + fix#73 claude provider fallback
T05 - Task-Aware Smart Routing:
- New open-sse/services/taskAwareRouter.ts:
Detects 7 task types: coding, creative, analysis, vision, summarization,
background, chat from system/user message content and images
Configurable taskModelMap per task type, stats tracking
applyTaskAwareRouting() integrates with existing chat pipeline
- New src/app/api/settings/task-routing/route.ts:
GET/PUT/POST API for task routing config + reset-stats + detect action
Persists config via updateSettings('taskRouting')
- Integration in src/sse/handlers/chat.ts:
applyTaskAwareRouting() called after policy enforcement, before combo resolve
Logs task type detection and model overrides
Fix#302 - OpenAI SDK stream=False drops tool_calls:
- src/sse/handlers/chat.ts T01 Accept header negotiation:
Changed condition from 'body.stream !== true' to 'body.stream === undefined'
OpenAI Python SDK sends 'Accept: application/json, text/event-stream' in every
request, even stream=False — the old code was incorrectly forcing stream=true,
causing tool_calls to be dropped from non-streaming responses
Fix#73 - Claude Haiku routed to OpenAI provider instead of Antigravity:
- open-sse/services/model.ts getModelInfoCore():
Added heuristic prefix detection before the blind 'openai' fallback:
claude-* models → antigravity (Anthropic) provider
gemini-*/gemma-* models → gemini provider
Closes: #73, partially addresses #302
* fix: token counts 0 (#74), model import dup (#180), model route fallback (#73)
fix#74 - Token counts always 0 for Antigravity/Claude streaming:
- open-sse/utils/usageTracking.ts extractUsage():
Add handler for 'message_start' SSE event which carries INPUT tokens in
Antigravity/Claude streaming:
{ type: 'message_start', message: { usage: { input_tokens: N } } }
This event was completely unhandled, causing ALL input token counts to be
dropped for every Antigravity/Claude streaming request
fix#180 - Model import shows duplicates with no visual feedback:
- src/shared/components/ModelSelectModal.tsx:
Added addedModelValues prop (string[]) to receive already-added model values
Models already in the combo now shown with ✓ indicator + green highlight
Makes it visually clear which models are already added vs new
- src/app/(dashboard)/dashboard/combos/page.tsx:
Pass addedModelValues={models.map(m => m.model)} to ModelSelectModal
* Harden clipboard UX and Claude tool normalization (#360)
* Initial plan
* chore: plan updates for clipboard and translator fixes
* fix: clipboard cleanup, copy feedback, and claude tool validation
---------
Co-authored-by: openai-code-agent[bot] <242516109+Codex@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: openai-code-agent[bot] <242516109+Codex@users.noreply.github.com>
All 3 throw new Error(data.error) replaced with proper extraction:
typeof error === object ? error.message : error
Fixes Cline and other OAuth providers showing [object Object] on connection failure
Two strings were hardcoded in Portuguese regardless of the user's language setting:
1. The redirect_uri_mismatch error message (line ~101)
2. The remote access info banner for Google OAuth providers (line ~515)
Both are now in English. The anchor href is updated from
'#oauth-em-servidor-remoto' to '#oauth-on-a-remote-server' to match
the EN README anchor.
- New CLI sidebar section: Tools + Agents
- Move Media to Debug section
- Move Health + Logs to System section
- Move CLI Fingerprint card from Settings > Security to Agents page
- Add cliToolsShort i18n key to all 30 languages
- Add Dashboard > Debug > Agents page: grid of 14 built-in CLI agents
with installation status, version detection, protocol badges
- Add custom agent support: users can register any CLI tool via form
- Expand registry from 5 to 14 built-in agents (aider, opencode, cline,
qwen-code, forge, amazon-q, interpreter, cursor-cli, warp)
- Add 60-second detection cache to avoid repeated execSync calls
- API: GET lists all agents, POST adds custom/refreshes, DELETE removes
- Settings schema: add customAgents array field
- Fix missing themeCoral in settings namespace for all 30 languages
- Add agents sidebar key in all 30 languages
- Add agents page i18n namespace (en + pt-BR)
- Add model playground page to dashboard (provider/model/endpoint selectors, Monaco editor, streaming, abort)
- Add CLI fingerprint matching (per-provider header/body ordering to match native CLI binaries)
- Add ACP module (CLI agent discovery, process spawner, session manager, API endpoint)
- Add playground to sidebar debug section with i18n support
- Close#192 and #200 as stale (v1.8.1, no repro info)
- Renamed sidebar 'Endpoint' to 'Endpoints', removed standalone MCP/A2A entries
- Created tabbed layout with SegmentedControl: Endpoint Proxy | MCP | A2A | API Endpoints
- Added inline ServiceToggle (online/offline status) for MCP and A2A tabs
- Created ApiEndpointsTab placeholder with Coming Soon badge
- Updated i18n in en.json and pt.json with new endpoints namespace
Eliminate `any` usage across the codebase by introducing proper generics,
typed interfaces (StatementLike, DbLike, PromptRow, etc.), and helper
conversion functions (toNumber, toString, parseVariables). Add
comprehensive Zod validation schemas for API endpoint inputs to enforce
runtime type safety alongside compile-time checks.
Code Quality Improvements:
- Export COLOR_THEMES from themeStore.ts for reuse (DRY)
- Add coral preset to color list (fixes default inconsistency)
- Sync local customThemeColor state reactively via Zustand subscribe
- Add hex color validation with visual feedback (red border + disabled button)
- Remove dead /themes route from Header.tsx (page doesn't exist)
- Add CSS color-mix() fallback for older browsers
- Add themeCoral i18n key to all 30 locale files