DB Migrations (zero-breaking, ADD COLUMN DEFAULT NULL + new table):
- 005_combo_agent_fields.sql: system_message, tool_filter_regex, context_cache_protection on combos
- 006_detailed_request_logs.sql: ring-buffer table (500 entries) for full pipeline body capture
Features:
- #399 System Message Override + Tool Filter Regex per Combo
- applyComboAgentMiddleware() injected into handleComboChat/handleRoundRobinCombo
- Supports both OpenAI and Anthropic tool name formats
- #401 Context Caching Protection (Stateless)
- injectModelTag() appends <omniModel>provider/model</omniModel> to responses
- extractPinnedModel() reads tag from history and pins model for session
- #320 Auto-Update via Settings
- GET /api/system/version — current vs latest npm
- POST /api/system/update — fire-and-forget npm install + pm2 restart
- #378 Detailed Request Logs
- saveRequestDetailLog() captures bodies at 4 pipeline stages (opt-in toggle)
- GET/POST /api/logs/detail — list logs + enable/disable toggle
- #336 MITM Kiro IDE
- src/mitm/targets/kiro.ts: MitmTarget profile for api.anthropic.com interception
Audio endpoints (/v1/audio/speech and /v1/audio/transcriptions) only
supported hardcoded providers from audioRegistry.ts. Local inference
backends configured as provider_nodes (e.g., MLX-Audio, oMLX) could
not serve audio through OmniRoute.
This adds a Phase 3 fallback in the audio model parser that consults
provider_nodes from the database. Local providers with api_type=openai
are automatically available for audio routing via their prefix
(e.g., mlx-audio/tts-model, omlx/whisper-large-v3-turbo).
Design: injection pattern — Next.js route handlers load provider_nodes
(async DB query) and pass them to the sync parser as a parameter.
No cross-workspace imports, no breaking changes to existing parsers.
Changes:
- Add buildDynamicAudioProvider() in audioRegistry.ts
- Add Phase 3 (provider_nodes prefix match) to parseAudioModel()
- Extend parseSpeechModel/parseTranscriptionModel with optional
dynamicProviders parameter (backward compatible)
- Load and inject provider_nodes in speech/transcription route handlers
- Dynamic providers use authType=none (local, no credentials needed)
Claude OAuth tokens are short-lived and require refresh. The runtime
HealthCheck (open-sse) already refreshes them successfully, but the
Dashboard test endpoint was missing `refreshable: true` in its config.
This caused the Dashboard to show "auth failed / Token expired" for
Claude providers even though the tokens were being refreshed correctly
at runtime. The codex provider already had this flag set.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local inference backends (oMLX, Ollama, LM Studio) configured as
provider_nodes have no health monitoring. When a local provider is
down, OmniRoute waits the full timeout before failing.
This adds a background health check that polls local provider_nodes:
- GET /models with 5s timeout for each local node (localhost only)
- In-memory health cache (no DB migration needed)
- Promise.allSettled for parallel checks (one slow node doesn't block)
- Exponential backoff on failures: 30s → 60s → 120s → 300s max
- Reset to 30s on first success after failure
- State transition logging (healthy ↔ unhealthy)
- Expose health status via GET /api/monitoring/health (localProviders)
- Auto-init on first import (same pattern as tokenHealthCheck)
- 401 treated as healthy (server up, auth required)
- isNodeHealthy() returns true if never checked (optimistic default)
Embedding endpoint (/v1/embeddings) only supports 6 hardcoded cloud
providers. Local inference backends (oMLX, Ollama) serving embeddings
via provider_nodes are inaccessible through OmniRoute.
This adds dynamic provider_node support for embeddings:
- Add EmbeddingProvider interface and buildDynamicEmbeddingProvider()
- Add Phase 2 (provider_nodes prefix match) in parseEmbeddingModel()
- Handler accepts resolvedProvider/resolvedModel from route (injection pattern)
- Handler supports authType=none for local providers (was missing — critical gap)
- Route loads local provider_nodes (localhost only — prevents auth bypass/SSRF)
- Route filters by apiType=chat|responses and localhost hostname
- buildDynamicEmbeddingProvider validates inputs (prefix + baseUrl required)
- Per-node try/catch in map — one bad row doesn't block all providers
- DB errors logged and fall back to hardcoded providers
- Remove apiKey===null heuristic (too broad — could match cloud providers
with non-standard auth). Use URL-based detection only.
- Guard local 404 branch with provider && model check — if either is null,
fall through to standard connection lockout (safer behavior).
- Document LOCAL_HOSTNAMES as module-load-time constant (restart required).
- Document PROVIDER_PROFILES.local as intentionally not yet wired.
When a local inference backend (oMLX, Ollama, LM Studio) returns 404
for an unknown model, OmniRoute previously locked the entire connection
for 2 minutes — blocking all valid models on that connection.
This fix introduces local provider detection and changes the 404
behavior for local providers:
- Model-only lockout (5s) instead of connection-level lockout (2min)
- Connection stays active — other models continue working immediately
- Detection via URL heuristic (localhost/127.0.0.1) + apiKey===null fallback
- Configurable via LOCAL_HOSTNAMES env var for Docker setups
Also fixes a pre-existing bug where the model parameter was not passed
to markAccountUnavailable() from chat.ts, preventing per-model lockouts
from working at all.
Changes:
- Add isLocalProvider(baseUrl) helper in providerRegistry.ts
- Add COOLDOWN_MS.notFoundLocal (5s) and PROVIDER_PROFILES.local
- Add local 404 branch in markAccountUnavailable() in auth.ts
- Pass model param to markAccountUnavailable() in chat.ts (bug fix)
Kilo Gateway (api.kilo.ai/api/gateway) is an OpenAI-compatible API
offering 335+ models via a single API key, including 6 free models
and 3 auto-routing models (frontier/balanced/free).
This is distinct from the existing KiloCode provider which uses
OAuth + /api/openrouter/ endpoint.
- Register kilo-gateway in providerRegistry.ts (alias: kg)
- Add to APIKEY_PROVIDERS in providers.ts
- Add models endpoint config in route.ts
- Add official Kilo AI icon (favicon)
Add Synthetic (synthetic.new) as a privacy-focused LLM provider
with OpenAI-compatible API, dynamic model catalog via /models
endpoint, and passthrough model support.
- Register provider in providerRegistry.ts with 6 initial models
- Add APIKEY_PROVIDERS entry with verified_user icon (#6366F1)
- Add models listing config for /api/providers/[id]/models endpoint
- passthroughModels enabled for dynamic model catalog
Allow provider_nodes to configure custom chat and models endpoint
paths via chatPath/modelsPath fields. This enables providers with
non-standard versioned APIs (e.g. /v4/chat/completions) to work
without embedding the version prefix in base_url.
- Add migration 003: chat_path and models_path columns
- Update Zod schemas (create, update, validate)
- Update CRUD in providers.ts (INSERT/UPDATE)
- Wire chatPath/modelsPath through API routes and providerSpecificData cascade
- Read chatPath in DefaultExecutor and BaseExecutor buildUrl()
- Use modelsPath in validate endpoint
- Add Advanced Settings UI section (collapsible) in create/edit modals
- Update base URL hint to reference Advanced Settings
- Add i18n keys across all 30 locales
- Add unit tests for buildUrl with custom paths
Backward compatible: NULL chatPath/modelsPath = default behavior.
Stabilize the bootstrap metadata test by clearing
INITIAL_PASSWORD before each run and add focused coverage
for env-backed and stored-password states.
Log settings lookup failures before returning the
bootstrap-safe fallback payload so operational errors are
still visible on the server side.
Normalize numeric pino levels correctly in the console log API so the logger transport fix does not misclassify info, warn, and error entries in file-backed logs.
Add a targeted regression test for numeric log entries.
Keep the existing level formatter for direct logger paths, but drop
that formatter from transport-backed configs because pino rejects it
when transport.targets is used.
This restores the intended stdout+file transport path and avoids the
startup fallback warning on every boot.
Point the login page at the existing public bootstrap endpoint
instead of the protected /api/settings route.
Also extend the public bootstrap response with hasPassword and
setupComplete so unauthenticated users get the correct first-run
or password-setup flow without triggering a 401.
Thanks @kfiramar! 🎉 Critical fix — stale error metadata on recovered provider accounts was preventing valid accounts from being selected properly after recovery.
Improvement added on top: documented the two valid success-check patterns (`result.success` for open-sse handlers vs `response?.ok` for fetch-based handlers) to address the kilo-code-bot review warning — both patterns are correct by design, now explicitly documented.
5 commits total, 2 test files (+168 lines of coverage). Merged!
Thanks @kfiramar! Perfect minimal fix — `t("deleteConnection")` was requesting a non-existent key across all 30 locales, causing `MISSING_MESSAGE: providers.deleteConnection` runtime errors on every provider detail page load. Reusing the existing `providers.delete` key is the correct fix. Merged!
Tighten the helper signatures added for recovered provider cleanup.
This removes the new any-typed recovery parameters called out in
review without broadening the PR into unrelated auth typing work.
Clear recovered provider error metadata after successful
credentialed requests in non-chat API routes as well.
Add route-level regression tests covering a Response-based
success path and a result-object success path.
Pass errorCode, lastErrorType, and lastErrorSource through the
runtime credentials object so clearAccountError can clear stale
provider error metadata after a real successful request.
Also update the regression test to use getProviderCredentials,
matching the production call path.
Add the missing provider_connections.group column to both the
base schema and the runtime column backfill path.
Also add a regression test covering upgrade from an older
database that does not yet have the column.
Clear errorCode, lastErrorType, and lastErrorSource when an
account recovers so provider state returns to a fully clean
active status.
Add a focused regression test for recovered-account cleanup.