- Fix usageExtractor cached_tokens fallback for Responses API (use cache_read_input_tokens when input_tokens_details is absent)
- Fix truncated claude-native-passthrough-tools.test.mjs that caused parse error
The second translateRequest call in the claude->openai->claude passthrough
path had an extra `translatedBody` argument before `stream`, shifting all
parameters by one. This caused the `stream` field in the upstream request
to be set to an object instead of a boolean, producing:
"stream: Input should be a valid boolean"
Co-Authored-By: Craft Agent <agents-noreply@craft.do>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The openai-to-claude translator was prefixing tool names with 'proxy_'
(e.g. Bash → proxy_Bash) even when routing Claude-format requests to
native Claude/Anthropic providers. Claude rejects unknown tool names,
causing 'No such tool available: proxy_Bash' errors.
Root cause: the _disableToolPrefix condition only disabled the prefix
for non-Claude providers, but it should be disabled for ALL providers
in the Claude passthrough path since tools are already in Claude format.
Fixes#618
- Rename in.json → hi.json: 'in' is Indonesian (ISO 639-1), Hindi is 'hi'.
Fixes Weblate locale conflict where id.json and in.json both claimed Indonesian.
- Move empty tool name filter before Codex passthrough: nativeCodexPassthrough
skipped all input sanitization, causing 400 'empty tool name' from upstream.
- Collapse 3+ consecutive newlines to \n\n in response sanitizer: thinking
models accumulate excessive line breaks between tool call blocks.
- fix(translator): pass toolNameMap to translateNonStreamingResponse so Claude
OAuth proxy_ prefix is correctly stripped from tool_use block names in
non-streaming responses (was only stripped in streaming path)
- fix(validation): add LongCat specialty validator that probes /chat/completions
directly, bypassing the /v1/models endpoint that LongCat does not expose (#592)
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
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
fix(chat): convert tool_result content blocks to [Tool Result: id] text (#527)
- Previously, tool_result blocks in user messages were silently dropped
- This caused an infinite loop when Claude Code + superpowers routed to Codex:
Codex never received the tool response and kept re-requesting the tool
- Now: tool_result → text block '[Tool Result: {id}]\n{content}'
- Handles string, array-of-text, and JSON-serialized content types
docs(issues): add Turbopack postinstall workaround on #509 and #508
docs(issues): note that #464 (API key provisioning) is on the v3.0 roadmap
- Add preserveDeveloperRole option and model compat override
- Normalize developer→system in roleNormalizer when not preserving
- Translator runs normalizeRoles for Responses API with option
- UI: ModelCompatPopover with do not preserve developer toggle
- Add ZWS_README_V2 documenting cause and fix
Made-with: Cursor
Previously resolveModelAlias() output was used only for getModelTargetFormat()
but the original model was sent in translatedBody.model and to the executor.
Now effectiveModel is propagated to all downstream operations.
Includes version bump — v2.6.9 — committed ATOMICALLY with all changes:
fixes:
- fix(ci/t11): Remove 'any' from comments in openai-responses.ts + chatCore.ts
(\bany\b regex counted comment text as explicit any violations)
- fix(chatCore/#409): Normalize unsupported content part types before forwarding
Cursor sends {type:'file'} for .md attachments; Copilot/OpenAI providers reject
with 'type has to be either image_url or text'. Now: file/document→text block,
unknown types dropped with debug log. Fixes claude-* models via github-copilot.
workflow:
- chore(generate-release): ATOMIC COMMIT RULE — npm version patch MUST run before
feature commits so the release tag always points to a commit with full changes
Anthropic API rejects requests containing {"type":"text","text":""} with
400 "text content blocks must be non-empty". Some clients like LiteLLM
passthrough and @ai-sdk/anthropic may forward empty text blocks as-is.
Filter out empty text content blocks from messages before calling
translateRequest, similar to how empty-name tools are already stripped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use shallow copy ({ ...body }) instead of direct reference assignment
so that later translatedBody.model = model does not mutate the
caller's original body object.
When both source and target formats are Claude, skip all request
modification and forward the body untouched. This prevents
prepareClaudeRequest from corrupting valid Claude-native requests
destined for anthropic-compatible provider nodes.
Reasoning models (o1, o1-pro, o3, o3-mini) reject standard parameters
like temperature and top_p with 400 Bad Request. OmniRoute's default
executor forwards all parameters without filtering.
This fix adds declarative parameter filtering:
- Add unsupportedParams[] field to RegistryModel interface
- Add REASONING_UNSUPPORTED frozen constant shared across entries
- Add o1-pro, o3, o3-mini to OpenAI registry (were missing)
- Add getUnsupportedParams() helper with:
- O(1) precomputed map lookup (not O(N×M) scan)
- Cross-provider routing support via precomputed map
- Prefixed model ID support (e.g., "openai/o3" → "o3")
- Strip unsupported params in chatCore.ts before executor call
- Use Object.hasOwn() for safe property check (no prototype chain)
- Log stripped params at WARN level for visibility
The filter introduced in #346 only checked OpenAI-format tool names
(tool.function.name), silently dropping all tools when the request
arrives in Anthropic Messages API format (tool.name without .function).
This happens when LiteLLM proxies requests with anthropic/ model prefix —
it translates to Anthropic format before forwarding, so OmniRoute receives
Claude-format tools. The filter drops them all, causing Anthropic API to
return 400: 'tool_choice.any may only be specified while providing tools'.
Fix: check both formats with fn?.name ?? tool.name.
- fix#355: increase STREAM_IDLE_TIMEOUT_MS from 60s to 300s to prevent
premature stream abortion for extended-thinking models (claude-opus-4-6,
o3, etc.) that can pause >60s during reasoning phases. Configurable via
STREAM_IDLE_TIMEOUT_MS env var.
- fix#350: combo health check test now bypasses REQUIRE_API_KEY=true by
sending X-Internal-Test header, recognized in chat.ts auth pipeline to
skip API key validation for internal admin-side combo tests. Also
extended test timeout from 15s to 20s. Uses OpenAI-compatible format
universally (not Claude-style).
- fix#346: filter out tools with empty function.name before forwarding
to upstream providers. Claude Code sends empty-name tool definitions
that cause '400 Invalid input[N].name: empty string' on OpenAI-compat
providers. Extends existing message/input empty-name filter.
- New: open-sse/services/apiKeyRotator.ts — round-robin rotation
between primary API key + providerSpecificData.extraApiKeys[]
- Modified: open-sse/executors/base.ts — buildHeaders() rotates key
using getRotatingApiKey() when extraApiKeys configured
- Modified: open-sse/handlers/chatCore.ts — injects connectionId into
credentials to enable per-connection rotation index tracking
- Modified: providers/[id]/page.tsx — 'Extra API Keys' UI section in
EditConnectionModal: add/remove keys, persisted in providerSpecificData
T08 (quota window rolling) and T13 (wildcard model routing) confirmed
already implemented in accountFallback.ts and wildcardRouter.ts.
Parse [1m] suffix from model name (e.g. claude-sonnet-4-6[1m]) and
propagate extendedContext flag through the request pipeline to append
context-1m-2025-08-07 to the Anthropic-Beta header.
#315: Import and call resolveModelAlias() in chatCore.ts before the
getModelTargetFormat() lookup so that custom aliases configured in
Settings → Model Aliases → Pattern→Target are actually applied during
routing instead of being silently ignored.
#316: Load persisted custom model aliases from settings DB at server
startup (instrumentation.ts). Previously _customAliases started as an
empty object after every restart since setCustomAliases() was only
called by the PUT /api/settings/model-aliases handler — never at init.
Now aliases are restored from settings.modelAliases JSON field on boot.
OpenAI-compatible providers (OpenAI, Codex) reject name:'' with 400 errors:
- 'Unknown parameter: input[1].name'
- 'Invalid tools[0].name: empty string'
Some clients (e.g. PocketPaw) forward assistant turns with name:'' in
the OpenAI Responses API input[] and chat completions messages[].
Fix: filter out name:'' from messages[] and input[] before translateRequest.
Non-empty non-null name values are preserved per OpenAI spec.
- Issue #199: proxy_ prefix on tool names is now automatically disabled
when routing to non-Claude backends (Gemini, Compatible, etc.).
Prevents tool name mismatches in OpenCode, Cursor, and other clients.
- Issue #198: Added customRpm/customTpm support per provider connection.
Users can configure custom rate limits via connection settings,
overriding the default auto-learned limits from response headers.
- Add type annotations to all providerModels helper functions
- Introduce LegacyProvider interface in providerRegistry
- Refactor translator system to use self-registering module pattern
with bootstrapTranslatorRegistry() and per-file imports
- Simplify translator/index.ts by delegating to modular translators
- Remove hardcoded Gemini OAuth client secret for security
- fix(chatCore): stream defaults to false per OpenAI spec (body.stream === true)
closes#89
- fix(models): custom OpenAI-compatible providers now appear in /v1/models
Added raw providerId check against activeAliases
closes#90
- fix(OAuthModal): Google OAuth providers force localhost redirect URI
Targeted amber warning + redirect_uri_mismatch error guidance for remote deployments
closes#91
- docs: Add 'OAuth em Servidor Remoto' tutorial in README with 7-step Google Cloud guide
- docs: .env.example prominent warning block for Google OAuth remote setup
- chore: bump version 1.0.6 → 1.0.7
- docs: CHANGELOG entry for v1.0.7
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single
OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies,
multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers,
semantic caching, combo fallback chains, real-time health monitoring, and a full
dashboard with provider management, analytics, and CLI tool integration.
Key highlights:
- 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.)
- 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized)
- Export/Import database backup with full archive support
- Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor)
- 100% TypeScript across src/ and open-sse/
- Docker support with multi-stage builds
- Comprehensive documentation and 9 dashboard screenshots