Release v3.7.0 - #1439
Release v3.7.0#1439
Conversation
…rializer (#1438) Integrated into release/v3.7.0. Thanks @benzntech for this great contribution! 🎉 We've removed the unrelated sync-fork.yml file and it's now merged into the release branch.
…ck (#1430) Integrated into release/v3.7.0. Thanks @clousky2020 for this massive and important contribution! 🎉 We've translated the deprecation comments to English for consistency, and it is now officially merged into the release branch. Great work on the ModelScope integration and Circuit Breaker!
There was a problem hiding this comment.
Code Review
This pull request upgrades OmniRoute to version 3.7.0, introducing provider-level circuit breakers, daily quota exhaustion locks, and ModelScope integration. It also adds real-time model status badges to the dashboard and hardens the SSE streaming decoder against memory exhaustion. Feedback focuses on improving the TOML configuration parser to correctly handle boolean and numeric types during round-trips and removing redundant re-entrancy guards from the synchronous failure tracking logic.
| let key = kvMatch[1].trim(); | ||
| let value = kvMatch[2].trim(); | ||
| // Remove quotes | ||
| // Strip quotes from key (TOML quoted keys like "gpt-5.3-codex") | ||
| if ( | ||
| (key.startsWith('"') && key.endsWith('"')) || | ||
| (key.startsWith("'") && key.endsWith("'")) | ||
| ) { | ||
| key = key.slice(1, -1); | ||
| } | ||
| // Remove quotes from string values only (not arrays, booleans, numbers) | ||
| if ( | ||
| (value.startsWith('"') && value.endsWith('"')) || | ||
| (value.startsWith("'") && value.endsWith("'")) |
There was a problem hiding this comment.
The current TOML parser treats all values as strings. This causes a type mismatch during round-trips: boolean and numeric values in the original TOML (e.g., enabled = true) are parsed as strings and then re-serialized with quotes (e.g., enabled = "true"). This can break compatibility with tools expecting specific TOML types. The parser should detect and convert unquoted boolean and numeric strings into their respective types.
let key = kvMatch[1].trim();
let value = kvMatch[2].trim();
let parsedValue: any = value;
// Strip quotes from key (TOML quoted keys like "gpt-5.3-codex")
if (
(key.startsWith('"') && key.endsWith('"')) ||
(key.startsWith("'") && key.endsWith("'"))
) {
key = key.slice(1, -1);
}
// Handle value types: check if quoted first
const isQuoted = (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"));
if (isQuoted) {
parsedValue = value.slice(1, -1);
} else {
// Convert unquoted values to appropriate types
if (value === "true") parsedValue = true;
else if (value === "false") parsedValue = false;
else if (!isNaN(Number(value)) && value !== "") parsedValue = Number(value);
}
if (currentSection) {
result[currentSection][key] = parsedValue;
} else {
result._root[key] = parsedValue;
}| // NOT a true mutex — Node.js is single-threaded, so different SSE streams | ||
| // can interleave across ticks. This Set prevents a single call from recursively | ||
| // re-entering recordProviderFailure within the same synchronous call stack. | ||
| const providerFailureLocks = new Set<string>(); |
There was a problem hiding this comment.
The providerFailureLocks set and the associated logic in recordProviderFailure appear to be redundant. Since Node.js is single-threaded and recordProviderFailure is a synchronous function that does not call itself or any other function that might recursively call it, there is no risk of synchronous re-entrancy within the same call stack. This mechanism adds unnecessary complexity without providing actual protection.
| if (providerFailureLocks.has(provider)) return; | ||
| providerFailureLocks.add(provider); |
| if (typeof value === "string") return `"${value}"`; | ||
| return `"${value}"`; | ||
| }; |
There was a problem hiding this comment.
This logic is redundant as both lines return the same formatted string. Additionally, the fallback return at line 76 will stringify and quote non-string types like null or objects in a way that is likely invalid for TOML.
| if (typeof value === "string") return `"${value}"`; | |
| return `"${value}"`; | |
| }; | |
| if (typeof value === "string") return value.startsWith("[") && value.endsWith("]") ? value : `"${value}"`; | |
| return String(value); |
CI Coverage Report
Coverage artifact was not available for this run. PR Test PolicyThis PR changes production code in |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 333f0b9f2d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| modelscope: { | ||
| id: "modelscope", | ||
| alias: "ms", | ||
| format: "openai", | ||
| executor: "default", | ||
| baseUrl: "https://api-inference.modelscope.cn/v1/chat/completions", |
There was a problem hiding this comment.
Register ModelScope in managed provider catalogs
This commit adds modelscope only to the runtime registry, but provider creation still validates IDs against the managed provider catalog (/api/providers uses isManagedProviderConnectionId). Since modelscope is not present in src/shared/constants/providers.ts/catalog, normal create flows reject it as an invalid provider, so the new integration cannot actually be configured by users.
Useful? React with 👍 / 👎.
| // Update all registered models with fresh data | ||
| const now = Date.now(); | ||
| registeredModels.forEach((key) => { | ||
| const [provider, model] = key.split("/"); |
There was a problem hiding this comment.
Preserve full model IDs when decoding status keys
Model status keys are encoded as ${provider}/${model}, but this code decodes with key.split("/") into only two parts. Models containing / in their ID (including new ModelScope IDs like moonshotai/Kimi-K2.5) get truncated, so matching against /api/models/availability fails and badges incorrectly show as available/unknown instead of cooldown/error.
Useful? React with 👍 / 👎.
| // Only write when explicitly enabled; omit to let registry default take effect | ||
| ...(formData.passthroughModels ? { passthroughModels: true } : {}), |
There was a problem hiding this comment.
Persist explicit false for passthroughModels updates
When editing a connection, passthroughModels is only sent when true; disabling the toggle omits the field. The update endpoint merges providerSpecificData with existing values, so an existing passthroughModels: true remains set and cannot be turned off from the UI. This makes the new toggle one-way for edited connections.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
OmniRoute/open-sse/handlers/chatCore.ts
Lines 2089 to 2093 in 9311f2a
In handleChatCore, the new connection-level override (providerSpecificData.passthroughModels) is not forwarded when calling lockModelIfPerModelQuota for rate-limit/quota paths, so providers that are manually configured for per-model quota can still be marked connection-wide unavailable. In practice, a 429 on one model can still set rateLimitedUntil for the whole connection here, which defeats the new toggle and causes unnecessary full-account lockouts.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| globalAttempts++; | ||
| if (globalAttempts > MAX_GLOBAL_ATTEMPTS) { | ||
| log.warn( | ||
| "COMBO", | ||
| `Maximum combo attempts (${MAX_GLOBAL_ATTEMPTS}) exceeded across all targets and fallbacks. Terminating loop to prevent runaway background requests.` |
There was a problem hiding this comment.
Derive combo attempt ceiling from targets and retries
This fixed global cap can terminate valid combo runs before all configured fallbacks are attempted. For example, with maxRetries = 1, a 20-target combo under transient failures needs up to 40 attempts, but this guard exits at 30 and returns 503 early, skipping later targets despite user-configured retry behavior.
Useful? React with 👍 / 👎.
| // Immediately fetch if no data yet | ||
| if (!modelStatusMap.has(key)) { | ||
| fetchModelStatus(); |
There was a problem hiding this comment.
Gate initial status fetch to a single in-flight request
registerModel calls fetchModelStatus() whenever that model key has no cache entry, so when many badges mount together on first render, each one triggers its own /api/models/availability request before the first response populates the map. This reintroduces the redundant polling burst that the shared context is meant to avoid and can significantly increase dashboard load.
Useful? React with 👍 / 👎.
Complete translation and terminology improvements for Ukrainian documentation: - docs/i18n/uk-UA/README.md: full Ukrainian translation - docs/i18n/uk-UA/SECURITY.md: full Ukrainian translation - docs/i18n/uk-UA/docs/A2A-SERVER.md: full Ukrainian translation - docs/i18n/uk-UA/docs/API_REFERENCE.md: full Ukrainian translation - docs/i18n/uk-UA/docs/AUTO-COMBO.md: full Ukrainian translation - docs/i18n/uk-UA/docs/USER_GUIDE.md: complete translation (966 lines) Changes: - Translated all English content to Ukrainian - Preserved all code examples, commands, and technical terms - Maintained proper Ukrainian orthography with diacritical marks
Address all 6 review comments from PR #1457: - Fix typo: "drastично" → "драстично" (mixed Latin/Cyrillic) - Translate model table entries: "Unlimited" → "Необмежено" - Translate model table entries: "No reported cap" → "Немає повідомлень про ліміт" - Translate OpenRouter description to Ukrainian - Fix section header: "Documentation" → "Документація" - Fix section header: "License" → "Ліцензія" All terminology now uses proper Ukrainian orthography.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
…nts' into docs/uk-ua-translation-improvements
Integrated into release/v3.7.0
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b38e57d452
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : (fallbackResult.baseCooldownMs ?? effectiveProviderProfile?.baseCooldownMs ?? 0), | ||
| effectiveProviderProfile, | ||
| { | ||
| exactCooldownMs: | ||
| fallbackResult.usedUpstreamRetryHint === true ? fallbackResult.cooldownMs : null, |
There was a problem hiding this comment.
Preserve daily-quota cooldown for per-model lockouts
When a per-model provider (e.g., passthrough/Gemini/ModelScope) returns a 429 daily-quota error, checkFallbackError provides a long cooldownMs (until tomorrow), but this branch only forwards exactCooldownMs when usedUpstreamRetryHint === true. Daily-quota results do not set that flag, so recordModelLockoutFailure falls back to short base cooldowns instead of the intended overnight lock, causing repeated retries on exhausted models rather than respecting daily reset windows.
Useful? React with 👍 / 👎.
| return NextResponse.json({ | ||
| profiles: settings.providerProfiles || PROVIDER_PROFILES, | ||
| defaults: { | ||
| ...DEFAULT_API_LIMITS, | ||
| ...asRecord(settings.rateLimitDefaults), | ||
| requestQueue: resilience.requestQueue, | ||
| connectionCooldown: resilience.connectionCooldown, | ||
| providerBreaker: resilience.providerBreaker, | ||
| waitForCooldown: { |
There was a problem hiding this comment.
Keep circuit-breaker data in resilience GET response
GET /api/resilience now returns only configuration fields, but MCP health aggregation still reads resilience.circuitBreakers (open-sse/mcp-server/server.ts in handleGetHealth). With this response shape, MCP omniroute_get_health reports an empty circuit-breaker list even when providers are OPEN/HALF_OPEN, reducing operational visibility for users relying on MCP health output.
Useful? React with 👍 / 👎.
…1444) Integrated into release/v3.7.0
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1d3d99bb9e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const circuitBreakers = getAllCircuitBreakerStatuses(); | ||
| const lockedIdentifiers = getLockedIdentifiers(); | ||
| return NextResponse.json({ circuitBreakers, lockedIdentifiers }); | ||
| return NextResponse.json({ lockedIdentifiers }); |
There was a problem hiding this comment.
Keep circuit-breaker data in policies GET payload
GET /api/policies now returns only lockedIdentifiers, but the usage dashboard still reads policies.circuitBreakers in BudgetTelemetryCards to show active/open breaker counts. With this response shape, that card always reports zero and never shows OPEN-breaker warnings, which hides live resilience incidents from operators.
Useful? React with 👍 / 👎.
| if (formData.passthroughModels) { | ||
| providerSpecificData.passthroughModels = true; |
There was a problem hiding this comment.
Persist explicit false for passthroughModels on create
The create flow only includes providerSpecificData.passthroughModels when the toggle is true. For providers whose registry default is passthroughModels: true (for example ModelScope/passthrough providers), leaving the toggle off sends undefined, so hasPerModelQuota falls back to the provider default and still treats the connection as per-model quota. This makes the new toggle unable to disable per-model quota at creation time.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9754b04bd6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| resolveDataDir = require("@/lib/dataPaths").resolveDataDir; | ||
| } catch (err) { | ||
| const fallbackDataDir = process.env.DATA_DIR || join(process.cwd(), "data"); |
There was a problem hiding this comment.
Resolve credentials path with an ESM-safe loader
This module is ESM ("type": "module"), so calling require(...) here throws at runtime and always takes the fallback path. That silently switches credential lookup to process.cwd()/data (unless DATA_DIR is set), so installs that rely on the default OmniRoute data dir will stop loading provider-credentials.json and OAuth/client secrets appear missing after upgrade.
Useful? React with 👍 / 👎.
| } catch (finalErr: unknown) { | ||
| const finalMessage = finalErr instanceof Error ? finalErr.message : String(finalErr); | ||
| console.error( | ||
| `[Encryption] Decryption final() failed: ${finalMessage}. ` + | ||
| `Ciphertext prefix: ${ciphertext.slice(0, 30)}... ` + | ||
| `Auth tag validation likely failed.` | ||
| ); | ||
| return ciphertext; |
There was a problem hiding this comment.
Return null when GCM auth-tag verification fails
When decryption reaches decipher.final() and auth-tag validation fails (the common wrong-key/corrupted-ciphertext path), this branch returns the original ciphertext string instead of null. That reintroduces the failure mode this patch is trying to avoid: encrypted token blobs can flow downstream as credentials, causing repeated auth failures and leaking encrypted payloads into request paths.
Useful? React with 👍 / 👎.
| if (body.comboDefaults) { | ||
| updates.comboDefaults = body.comboDefaults; | ||
| updates.comboDefaults = sanitizeComboRuntimeConfig(body.comboDefaults); | ||
| } |
There was a problem hiding this comment.
Avoid overwriting combo defaults with empty sanitized payloads
This writes comboDefaults whenever the field is present, even if sanitization removed all keys (e.g., legacy-only fields like timeoutMs/healthCheck*, or an empty object). Because updateSettings replaces the whole key, that turns existing saved defaults into {} and drops user configuration unexpectedly on otherwise valid PATCH requests.
Useful? React with 👍 / 👎.
- create createSettingsApiHarness function with temp directory setup - add beforeEach/afterEach hooks for storage reset between tests - add after hook for cleanup - use dynamic imports after env setup to ensure proper initialization
- add provider-level circuit breaker config to PROVIDER_PROFILES - remove hardcoded threshold constants in favor of profile-based config - use getProviderProfile() to read thresholds with fallback defaults - support different failure tolerance per provider type
The mergeProviderProfile function was missing the three new fields added to PROVIDER_PROFILES (providerFailureThreshold, providerFailureWindowMs, providerCooldownMs). This caused tests to fail because the profile returned by getRuntimeProviderProfile did not include these fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. Remove 429 from PROVIDER_FAILURE_ERROR_CODES - 429 (rate limit) is already handled by model-level and account-level locks - Including it in provider-wide circuit breaker causes premature cooldown 2. Fix reference counting in ModelStatusContext - Changed registeredModels from Set to Map<string, number> - Prevents polling stop when one component unmounts while others still track the model 3. Fix model ID parsing for providers with slashes in model names - Use indexOf/substring instead of split to handle models like "modelscope/moonshotai/Kimi-K2.5" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Register LM Studio as an OpenAI-compatible local provider and map the new grok-4.3 thinking model for web executor requests. This update also hardens related platform behavior by switching backup archive creation to execFileSync, validating ACP agent ids, expanding shared CORS handling, and making prompt injection guard failures return an explicit 500 response. To preserve existing stored credentials, encryption now derives new keys from a secret-based salt while still falling back to the legacy static-salt key during decryption.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Populate newly introduced dashboard and provider UI message keys in all locale bundles to prevent missing translation lookups after the v3.7.0 changes. Also fix quota reset handling so expired limits are only marked stale when usage is still pending, adjust combo form dark-mode backgrounds, and expand the prepublish hash rewrite to handle nested package paths.
Pass the entered sudo password through endpoint enable and disable requests so macOS and Linux installs can start or stop Tailscale without retrying unauthenticated commands. Also detect and cache the active tailscaled socket before issuing CLI calls, preferring the system daemon socket when available so status and funnel operations target the running service correctly.
Prevent repeated provider-limit refresh requests by guarding the bulk refresh flow with a ref-backed lock instead of a stale callback dependency. Also avoid tying eval data loading to translation updates and replace the fetch failure path with a static error so the effect runs predictably. Refresh English cost dashboard copy to provide clearer labels and empty state messaging.
…ound-trip The parseToml function was stripping all value quotes uniformly, turning every value into a JS string. When toToml re-serialized, unquoted integers like 2 were wrapped in quotes becoming "2" — a TOML string. This broke Codex CLI which expects u32 for tui.model_availability_nux: Error loading config.toml: invalid type: string "2", expected u32 Now parseToml detects booleans (true/false), integers, and floats, preserving their native JS types. formatTomlValue already handles number/boolean types correctly, so round-tripping no longer corrupts third-party config sections.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
1 similar comment
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…og to 39 languages
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Replace placeholder builtin skill responses with real file, HTTP, and code-execution flows constrained to per-key workspaces, size limits, and sanitized request headers. Harden the Docker sandbox with dropped capabilities, tmpfs-backed workdirs, configurable runtime limits, and clearer failure behavior for disabled browser automation. Also consolidate legacy dashboard usage navigation into logs, remove stale sidebar and SSE backup artifacts, and expand tests to lock in the new runtime and routing contracts.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
- guide-settings-route.test.ts: control XDG_CONFIG_HOME so OpenCode config path resolves to the test dummy dir (CI runners have XDG set) - proxy-registry-flow.test.ts: disable DASHBOARD_PASSWORD to prevent 401 on direct route handler calls (CI postinstall auto-generates it) - _chatPipelineHarness.ts: clear DASHBOARD_PASSWORD for all integration tests using the shared chat pipeline harness
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
CI sets INITIAL_PASSWORD and JWT_SECRET env vars, which makes isAuthRequired() return true even with a fresh temp DB. The tests call route handlers directly without session cookies, so auth must be fully disabled by clearing all auth-related env vars.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Update release notes and project documentation to reflect the current 160+ provider catalog, 29-tool MCP server footprint, and newly shipped v3.7.0 features and fixes. This keeps public-facing docs, architecture references, and agent guidance aligned with the actual release contents and supported capabilities.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
# Conflicts: # src/i18n/messages/pt.json
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|



[3.7.0] — 2026-04-26
✨ New Features
feat(providers): Implement Image Generation and Editing capabilities for ChatGPT Web, including in-band chat image generation and caching (feat(providers): wire CrofAI /usage_api/ into quota preflight, monitor & Limits page #1606).
feat(ui): Integrate OpenCode Zen/Go API tool logo SVG and polish API key copy-to-clipboard interactions (feat(chatgpt-web): image generation + edit (Open WebUI compatible) #1607).
feat(providers): Add CrofAI as a built-in API-key provider with quota/usage monitoring wired into the dashboard Limits page (feat(providers): add CrofAI as a built-in API-key provider #1604, feat(providers): wire CrofAI /usage_api/ into quota preflight, monitor & Limits page #1606).
feat(skills): Add workspace-scoped built-in skills (
file_read,file_write,http_request,eval_code,execute_command) with real sandbox execution via Docker, replacing stub responses. Browser skills now fail explicitly when runtime is not configured.feat(providers): Integrate AgentRouter as a new OpenAI-compatible passthrough provider with $200 free credits via sign-up (Issue Provider Request: Add AgentRouter (Non-profit AI Gateway — Free Credits) #1572).
feat(ui): Implement on-demand per-model testing in the provider dashboard, allowing single-token diagnostic checks without triggering rate-limits (Issue [Feature] In the provider, when a valid key is assign.. Show which model is valid/working #1532).
feat(provider): add ChatGPT Web (Plus/Pro) session provider (feat: add ChatGPT Web (Plus/Pro) session provider #1593)
feat(provider): add Baidu Qianfan chat provider (feat(provider): add Baidu Qianfan chat provider #1582)
feat(codex): support GPT-5.5 responses websocket (feat(codex): support GPT-5.5 responses websocket #1573)
feat(sse): Codex CLI image_generation + DALL-E-style image route (feat(sse): Codex CLI image_generation + DALL-E-style image route #1544)
feat(dashboard): Complete the reconciled v3.7.0 dashboard task set: MCP cache tools and count, video endpoint visibility, provider taxonomy, upstream proxy visibility, provider count badges, costs overview, eval suite management, Custom CLI builder, ACP-focused Agents copy, Translator stream transformer, logs convergence, learned rate-limit health cards, docs expansion, and active request payload inspection.
feat(mcp): Register
omniroute_cache_statsandomniroute_cache_flushacross MCP schemas, server registration, handlers, docs, and tests.feat(providers): Complete the v3.7.0 provider onboarding wave with self-hosted/local providers (
lm-studio,vllm,lemonade,llamafile,triton,docker-model-runner,xinference,oobabooga), OpenAI-compatible gateways (glhf,cablyai,thebai,fenayai,empower,poe), enterprise providers (datarobot,azure-openai,azure-ai,bedrock,watsonx,oci,sap), specialty providers (clarifai,modal,reka,nous-research,nlpcloud,petals,vertex-partner),amazon-q, GitLab/GitLab Duo, and Chutes.ai.feat(providers): Add Cloudflare Workers AI integration and UI support for robust backend execution.
feat(telemetry): Implement proactive public IP capture from client headers (
x-forwarded-for,x-real-ip, etc.) withinsafeLogEventsfor accurate database observability.feat(audio): Add AWS Polly as an audio speech provider with SigV4 request signing, static engine catalog, provider validation, managed-provider UI coverage, and sanitization for AWS secret/session fields.
feat(search): Add You.com search provider support with dashboard discovery, validation, livecrawl option handling, and search handler normalization.
feat(video): Add RunwayML task-based video generation support, task polling, provider catalog metadata, validation, and dashboard/model-list coverage.
feat(providers): Add search functionality to the providers dashboard with i18n support. (Add search in the providers page #1511 — thanks @th-ch)
feat(providers): Register 6 new models in the opencode-go provider catalog. (feat(opencode-go): register 6 missing models from upstream catalog #1510 — thanks @kang-heewon)
feat(providers): Add ModelScope provider (Chinese AI marketplace) with Kimi K2.5, GLM-5, and Step-3.5-Flash integration. (feat: add ModelScope provider with circuit breaker and daily quota lock #1430 — thanks @clousky2020)
feat(providers): Add LM Studio as an OpenAI-compatible local provider for self-hosted model inference.
feat(providers): Add Grok 4.3 thinking model support for xAI web executor requests.
feat(core): Implement provider-level Circuit Breaker to prevent cascading failures across connections, enforcing a 10-minute cooldown after 5 consecutive transient failures. (feat: add ModelScope provider with circuit breaker and daily quota lock #1430)
feat(core): Add daily quota exhaustion lock to detect "quota exceeded" signals and lock the specific model until midnight. (feat: add ModelScope provider with circuit breaker and daily quota lock #1430)
feat(core): Auto-inject
stream_options.include_usage = truefor OpenAI format streams to guarantee token usage is reported correctly during streaming. ([Feature] Auto-injectstream_options.include_usagefor llama.cpp backend #1423)feat(core): Add OpenAI Batch Processing API support — submit, monitor, and manage batch jobs through the proxy with full lifecycle tracking.
feat(vision-bridge): Add automatic image description fallback for non-vision models via
VisionBridgeGuardrail(priority 5). Intercepts image-bearing requests to non-vision models, extracts descriptions via a configurable vision model (default: gpt-4o-mini), and replaces images with text before forwarding. Fails open on any error. (feat(vision-bridge): add automatic image description fallback for non-vision models #1476)feat(dashboard): Introduce real-time model status badges with countdown timers in the provider detail and combo panel interfaces. (feat: add ModelScope provider with circuit breaker and daily quota lock #1430)
feat(dashboard): Add Batch/File management data grid with full i18n translations for batch processing workflows. (feat: add support for OpenAI batch processing #1479)
feat(usage): MiniMax + MiniMax-CN quota tracking in provider limits dashboard. (feat(usage): MiniMax + MiniMax-CN quota tracking in provider limits dashboard #1516)
feat(providers): Fix OpenRouter remote discovery and unify managed model sync. (Fix OpenRouter remote discovery and unify managed model sync #1521)
feat(providers): Implement provider and account-level concurrency cap enforcement (
maxConcurrent) using robust semaphore mechanisms. (feat: provider/account-level concurrency cap enforcement #1524)feat(core): Implement Hermes CLI config generation and message content stripping. (Feature request: add Hermes quick-configuration support to tools #1475)
feat(combos): Add expert combo configuration mode for advanced routing controls. (Add expert combo configuration mode #1547)
feat(providers): Register Codex auto review and expand icon coverage.
feat(tunnels): Add Tailscale tunnel management routes and runtime helpers for install, login, daemon start, enable/disable, and health checks.
🐛 Bug Fixes
fix(mitm): Compile MITM utilities as NodeNext ESM during prepublish, copy the CommonJS MITM server into the standalone artifact, and resolve MITM data paths without relying on Next.js aliases in packaged runtime.
fix(build): Move the local
.tmp/wine32Wine prefix out of the isolated Next.js build path so Windows Electron packaging artifacts cannot triggerEACCESscans during Node 24 builds.fix(build): Copy the
wreq-jsnative runtime directory into the isolated Next.js standalone output so packaged Playwright/E2E starts can load the instrumentation hook on Linux.fix(api): Validate the Codex Responses websocket bridge and
/v1/batchesJSON payloads with Zod before use, keepingrequest.json()route validation green and returning explicit 400 responses for invalid bodies.fix(providers): Add explicit typing to provider alias and category helpers so the strict
typecheck:noimplicit:coreCI gate passes.fix(ui): Keep the upstream proxy provider detail page labeled with a fallback "Managed via Upstream Proxy Settings" management surface when translations are unavailable.
fix(electron): Harden the production desktop CSP by removing
unsafe-evaloutside development and adding object, base URI, form action, frame ancestor, and worker restrictions.fix(cli): Replace shell-interpolated setup and privileged command execution paths with argument-based
spawn/execFilehelpers for database setup, Tailscale sudo commands, MITM DNS edits, and certificate install/uninstall flows.fix(ui): Keep provider icons resilient by using direct
@lobehub/iconscomponents first, then local PNG/SVG fallbacks, avoiding the@lobehub/uipeer runtime in the dashboard.fix(chatgpt-web): Fix empty-file race in
tlsFetchStreamingwherewaitForFileaccepted zero-byte files, silently degrading streaming requests to buffered mode. Replaced withwaitForContentrequiringfile.size > 0with early exit on request settlement. (chatgpt-web: fix two real bugs flagged on PR #1596 (empty-file race + stale session-token) #1597 — thanks @trader-payne)fix(chatgpt-web): Fix stale NextAuth session-token cookies surviving rotation shape changes (unchunked↔chunked).
mergeRefreshedCookienow drops all session-token family members viaSESSION_TOKEN_FAMILY_REbefore appending the refreshed set, preventing auth failures from dual cookie submission. (chatgpt-web: fix two real bugs flagged on PR #1596 (empty-file race + stale session-token) #1597 — thanks @trader-payne)fix(codex): WebSocket memory retention and weekly limit handling (Fix Codex Responses WebSocket memory retention and weekly limit handling #1581)
fix(providers): Default models list logic ([Improvement] Fix Providers Default models list (Done) #1577)
fix(ui): Dashboard endpoint URL hydration respects
NEXT_PUBLIC_BASE_URLwhen behind a reverse proxy ([BUG] Endpoints page ignores NEXT_PUBLIC_BASE_URL, shows wrong API URL behind reverse proxy #1579)fix(providers): Restore strict PascalCase header masquerading for Claude Code to resolve HTTP 429 upstream errors ([BUG] Opus 4.6 returns 429 on all messages #1556)
fix(sse): make Responses passthrough robust for size-sensitive clients (fix(sse): make Responses passthrough robust for size-sensitive clients #1580)
fix(codex): update client version for gpt-5.5 (fix(codex): update client version for gpt-5.5 #1578)
fix(vision-bridge): force GPT-family image fallback (fix(vision-bridge): force GPT-family image fallback #1571)
fix(claude): skip adaptive thinking defaults for unsupported models (fix(claude): skip adaptive thinking defaults for unsupported models #1563)
fix(claude): preserve tool_result adjacency in native and CC-compatible paths (fix(claude): preserve tool_result adjacency in native and CC-compatible paths #1555)
fix(reasoning): Preserve OpenAI Chat Completions
reasoning_effortthrough assistant-prefill requests and label OpenAI request protocols explicitly asOpenAI-ChatorOpenAI-Responses. (fix(reasoning): preserve chat effort and protocol labels #1550)fix(codex): Fix Codex auto-review model routing so review traffic resolves to the intended configured model. (Fix Codex auto-review model routing #1551)
fix(resilience): Route HTTP 429 cooldowns through runtime settings so cooldown behavior follows the configured resilience profile. (fix(resilience): route 429 cooldowns through settings #1548)
fix(providers): Normalize Anthropic header keys to lowercase in the provider registry to avoid duplicate or case-variant upstream headers. (fix: normalize Anthropic header keys to lowercase in provider registry #1527)
fix(providers): Preserve audio, embedding, rerank, image, video, and OpenAI-compatible alias metadata when
/v1/modelsmerges static and discovered catalogs.fix(providers): Discover Azure OpenAI deployments from resource endpoints using
api-keyauth and configurable API versions.fix(providers): Keep local OpenAI-style providers authless when no API key is configured, including the Lemonade Server default endpoint.
fix(translator): Preserve Antigravity default system instructions and caller-provided system prompts as separate Gemini
systemInstructionparts instead of concatenating them.fix(security): Sanitize provider-specific AWS secrets and session tokens from provider management API responses.
fix(release): Resolve combo prefixing, Electron packaging, CLI auth, and release-branch integration regressions. ([BUG] combos with prefix broken #1471, Black screen on first launch — node_modules missing in resources/app, server crashes with "Cannot find module 'next'" #1492, Missing
pino-abstract-transportat runtime — uncaughtException from pino worker thread in installed app #1496,better_sqlite3.nodeABI mismatch — Webpack's hashed copy in.next/node_modules/has wrong ABI, dashboard fails with HTTP 500 #1497, Claude Code CLI auth is not detected on Linux when claude is already logged in #1486)fix(providers): Resolve 400 errors for GLM and Antigravity Claude adapter during request translation by scoping prompt caching to compatible Anthropic endpoints and flattening system instructions. ([BUG] [400]: Request contains an invalid argument. #1514, [BUG] GLM Request error, Direct connection is normal, but there is an error when reporting achievements through a relay #1520, [BUG] [400]: Request contains an invalid argument when any memory is available #1522)
fix(core): Strip
reasoning_contentfrom OpenAI format messages for non-reasoning models to prevent upstream HTTP 400 validation errors. ([BUG] 400 errors on Kiro's sonnet 4.5 model if "reasoning_content" present #1505)fix(sse): Map Claude
output_config/thinkingto OpenAIreasoning_effortfor proper Antigravity tool translation. (fix(sse): map Claude output_config/thinking to OpenAI reasoning_effort #1528)fix(combo): Fallback to next model on all-accounts-rate-limited (HTTP 503/429) to maintain high availability. (fix(combo): fallback to next model on all-accounts-rate-limited 503 (… #1523)
fix(api): Harden batch and file endpoints for auth and recovery to prevent schema state collisions.
fix(ui): Add missing UI wiring for "Add Memory" and "Import" buttons on the
/dashboard/memorypage. ([BUG] "Add Memory" and "Import" buttons on /dashboard/memory have no onClick handler (no-op) #1506)fix(ui): Prevent Dark Mode FOUC (Flash of Unstyled Content) by injecting a synchronous theme initialization script into the root
layout.tsx.fix(ui): Fix mobile layout text overflow in provider and combo cards, and enable touch-friendly reordering arrows across all combo strategies.
fix(core): Add periodic runtime log rotation checks to prevent disk exhaustion in long-running instances. (fix(logs): add periodic runtime log rotation check #1504 — thanks @ether-btc)
fix(build): Resolve missing
processmodule in webpack client build for pino-abstract-transport. (bug: fixes Error: Cannot find module 'process/' #1507 #1509 — thanks @hartmark)fix(ui): Add dark mode support for native dropdown
<option>elements on Linux/Windows, resolving invisible text in settings and combo builders ([BUG] Dropdowns not changing to dark mode #1488)fix(batch): Add batch item dispatching to specific handlers based on URL to support embeddings and other modalities (fix: add batch item dispatching to specific handlers based on URL #1495 — thanks @hartmark)
fix(dashboard): Correct TOML round-trip corruption in Codex config serializer by dequoting keys and preserving array/boolean structures properly. (fix(dashboard): correct TOML round-trip corruption in codex config serializer #1438 — thanks @benzntech)
fix(security): Resolve CodeQL alert 164 (ReDoS in extraction) and 163 (incomplete URL sanitization). (Gemini (non-CLI) wrong models import #163, docs: reference WFGY 16-problem RAG failure map in TROUBLESHOOTING #164)
fix(providers): Add optional chaining to connection object before accessing
providerSpecificData, preventing runtime errors when the connection is null/undefined.fix(codex): Preserve namespace MCP tools forwarded to Codex Responses API, preventing tool name stripping during translation. (fix(codex): preserve namespace MCP tools forwarded to Codex Responses… #1483)
fix(codex): Deduplicate case-variant
anthropic-versionheader in Claude Code patch to prevent duplicate header injection. (fix: deduplicate case-variant anthropic-version header in Claude Code patch #1481)fix(fallback): Use shared
CircuitBreakerinstead of undefined constants, fixing runtime errors in provider failure handling. (fix(fallback): use shared CircuitBreaker instead of undefined constants #1485)fix(fallback): Merge new provider failure threshold fields (
providerFailureThreshold,providerFailureWindowMs,providerCooldownMs) into resilience profiles.fix(fallback): Remove 429 from
PROVIDER_FAILURE_ERROR_CODES— rate limits are already handled by model-level and account-level locks; including them in the provider-wide circuit breaker caused premature cooldown.fix(sse): Enable tool calling for GPT OSS and DeepSeek Reasoner models. (fix(sse): enable tool calling for GPT OSS and DeepSeek Reasoner models #1455)
fix(encryption): Return null on decryption failure to prevent sending encrypted tokens to providers. (fix(encryption): return null on decryption failure to prevent sending encrypted tokens to providers #1462)
fix(combo): Resolve cross-provider thinking 400 errors and HTTP clipboard issues during combo routing. (fix(combo): skip retries on all-rate-limited 429, fix thinking signature cross-provider 400 #1444)
fix(core): Resolve skills, memory, and encryption system issues affecting startup and runtime stability. (fix: resolve skills, memory, and encryption system issues #1456)
fix(core): Fix model ID parsing for providers with slashes in model names — use
indexOf/substringinstead ofsplitto handle models likemodelscope/moonshotai/Kimi-K2.5.fix(core): Fix reference counting in
ModelStatusContext— changedregisteredModelsfromSettoMap<string, number>to prevent polling stop when one component unmounts while others still track the same model.fix(security): Prompt injection guard failures now return an explicit 500 response instead of silently passing through (fail-closed policy).
fix(security): Encryption now derives new keys from a secret-based salt while falling back to the legacy static-salt key during decryption, preserving existing stored credentials.
fix(combo): Resolve context truncation bug in combo routing to prevent incomplete execution states. (Fix/combo context truncation 1470 #1517)
fix(compression): Implement bidirectional tool_pair cleaning for anthropic inputs (fixes [BUG] v3.6.9 PR #1406 (compression-before-translation) reintroduces orphan tool_call_id for Anthropic-format inputs (Claude Code → kimi/minimax 400) #1592).
fix: Resolve v3.7.0 stabilization issues including dashboard navigation routing, ProxyRegistryManager component layout, and models API response merging ([BUG] OpenAI Compatible node: support multiple connections + Fix Cloudflare model search + Add Chat Playground #1566, [BUG] Editing an existing proxy throws a JavaScript runtime error and blocks saving #1560, [BUG] UI Inconsistency: Model management layout and behavior differs across ALL providers #1559).
fix(cli): Preserve TOML integer/boolean types in Codex config round-trip to prevent
tui.model_availability_nuxvalidation errors.fix(tailscale): Support sudo auth prompts and live daemon socket detection for non-root tunnel management.
fix(dashboard): Stabilize usage tab loading and refresh behavior to prevent empty state flashes.
fix(i18n): Translate 519 untranslated pt-BR keys and add missing Windsurf/Cline/Kimi docs keys.
fix(i18n): Add missing dashboard message keys across all 30 locales.
fix(cli): Align OpenCode config preview and add multi-model selection (fix(cli): align OpenCode config preview and add multi-model selection #1602).
fix(security): Harden management API auth and OpenAPI try-proxy endpoint.
fix(security): Resolve vulnerability scan findings for auth-guarded routes.
♻️ Refactoring
PROVIDER_PROFILESinstead of hardcoded constants, supporting different failure tolerance per provider type. (refactor: unify resilience controls #1449)execFileSync, validate ACP agent IDs, expand shared CORS handling.src/lib/dataPaths.jsartifact. (fix: remove stale compiled dataPaths.js artifact #1541)🧪 Tests
/v1/modelsalias metadata.callVisionModel,extractImageParts,replaceImageParts, andresolveImageAsDataUri.DATA_DIRto prevent schema state collisions.createSettingsApiHarnessfunction for proper temp directory setup and storage reset between tests.INITIAL_PASSWORDandJWT_SECRETin integration tests, handleXDG_CONFIG_HOMEfor guide-settings tests.📚 Documentation
🛠️ Maintenance
.tmp/to.gitignoreto keep local build/test artifacts out of release diffs. (chore: add .tmp/ to .gitignore #1538)📦 Dependencies
@lobehub/iconsto5.5.4, add explicitreact-is@19.2.5for Recharts, pin npm installs to skip unused peer auto-installs, and override Electron's transitive@xmldom/xmldomto0.9.10so audit findings stay closed.Tests