Custom embedding models on the DeepInfra provider (e.g.
Qwen/Qwen3-Embedding-8B) were rejected by createEmbeddingResponse with
'Unknown embedding provider: deepinfra. No matching hardcoded or local
provider found.' because the registry only included Nebius/OpenAI/Together/
Fireworks/NVIDIA/Mistral/Voyage/Jina/Gemini. The fallback to
provider_nodes only resolves localhost/172.x dev URLs, so remote DeepInfra
keys had no path to /v1/embeddings.
Adds DeepInfra with 8 popular embedding models (Qwen3-Embedding-8B/4B/0.6B,
BGE Large/Base/M3, E5 Large v2, GTE Large) routed through
https://api.deepinfra.com/v1/openai/embeddings.
Part 1 (incomplete model list import) still needs reproduction details
from the reporter and will be tracked separately.
The existing /api/combos GET requires a management token, which broke
read-only integrations (opencode-omniroute-auth plugin and similar) that
need to enrich combo capabilities from a normal Bearer API key. Those
clients got 403 AUTH_001 'Invalid management token' even though the same
API key could list models via /v1/models.
This adds GET /v1/combos with the same auth model as /v1/models:
- Accepts valid Bearer API key OR dashboard session cookie.
- Falls back to anonymous when REQUIRE_API_KEY=false (single-user local).
- Projects ONLY public metadata: name, strategy, description, model id,
providerId, comboName (for combo-refs). Internal routing details
(connectionId, weights, labels, sortOrder, config) are stripped.
/api/combos (management writes) is unchanged.
The OpenAI Responses API path emits 'developer' role messages. The previous
default preserved that role for any targetFormat=openai upstream, which broke
DeepSeek, MiniMax, Mimo, GLM, Fireworks, Together, and most other
OpenAI-compatible gateways with '[400]: unknown variant developer, expected
one of system/user/assistant/tool'.
New default: preserve developer only for the openai-family allowlist
(openai, azure-openai, azure, github, or any id containing 'openai').
Convert developer→system for everyone else when preserveDeveloperRole is
not explicitly set. The dashboard 'Compatibility → preserveOpenAIDeveloperRole
= true' toggle still forces preservation when needed.
- Promote prevHeaders from per-batch local to module-level global with 60s TTL
- Share rate-limit throttle state across sequential batches
- Use 24h time-based retry limit (MAX_RETRY_DURATION_MS) instead of count-only
- Increase baseMs to 5s and maxMs to 1h for batch-appropriate backoff
- Add getCachedHeaders/resetCachedHeaders test helpers
- 7/7 unit tests pass
Authored-by: Markus Hartung <hartmark@users.noreply.github.com>
- DeepSeekWebExecutor with ds_session_id cookie auth
- DeepSeekWebWithAutoRefreshExecutor for session management
- Keccak-based PoW solver (DeepSeekHashV1)
- SSE stream transformation to OpenAI format
- Provider constant and alias (ds-web)
- 23 unit tests + live integration test
Authored-by: Paijo <oyi77@users.noreply.github.com>
Removes default daily/weekly/monthly request caps (1K/5K/20K) that were
silently applied to API keys without explicit rate limits, causing
surprise 429s in production aggregator deployments.
Authored-by: josephvoxone <josephvoxone@users.noreply.github.com>
Adds a complete executor for DeepSeek's native web API with:
- Authentication via ds_session_id cookie → Bearer token from /users/current
- Proof-of-Work (DeepSeekHashV1) solver using custom Keccak sponge
- SSE stream response parsing with OpenAI-compatible output
- Web search toggle (search_enabled)
- Deep thinking toggle (thinking_enabled + x-thinking-enabled header)
- File attachment support (ref_file_ids)
- Auto-refresh executor for session management
- 23 unit tests covering all features + live integration test
API flow: /users/current → /chat_session/create → /create_pow_challenge
→ PoW solve → /chat/completion with native DeepSeek body format
Co-Authored-By: OpenClaude (mimo-v2.5-pro) <openclaude@gitlawb.com>
- Add missing column to provider_connections CREATE TABLE baseline so
fresh/in-memory databases include it from the start
- Call ensureProviderConnectionsColumns for in-memory instances to match
the file-backed path
Compresses the explanatory NOTE in claudeCodeToolRemapper.ts from 6 lines
to 4 while keeping the actionable why: the flag has no readers, would
leak into the Anthropic request body causing HTTP 400 (Extra inputs are
not permitted), and the response-side remap is unconditional.
Addresses gemini-code-assist review feedback on PR #2290.
Fixes 'first-time save silently dropped' when adding a fresh op with
required-but-empty fields (e.g. replace_regex.pattern). Server returns 400
with field-level zod errors; previously the UI never applied the local
edit because setSettings was gated on res.ok. Now:
- updateSetting applies the patch to local state FIRST (optimistic).
- On 400 it surfaces a 'Server rejected save:' banner per provider listing
each failing field, with copy telling the user their edit is kept.
- Next valid PATCH clears the banner.
Also: every op kind gets an italic description paragraph above its editor,
and every field gets a plain-English hint underneath explaining what it
does and when to use it. Drift-prone refs softened (no 'v1.7.5 ex-machina'
internal jargon, no false 'server validates regex compiles' claim).
59/59 unit tests green; tsc clean.
Reduces vertical footprint of the System-block Transform Pipeline card so
long pipelines (cc-bridge ships 9 ops) do not dominate the Routing tab.
- New Collapsible component (src/shared/components/Collapsible.tsx) used as
a shared primitive. Open/closed state lives in local component state; does
NOT persist across reloads (per UX brief: always-collapsed default).
- Each provider tile is now collapsible (closed by default).
- Each pipeline op inside a provider is collapsible (closed by default) —
click to expand the per-kind editor.
- 'Add provider' Select+Button moved from BOTTOM to TOP of the card with a
dashed border, so it is the first thing the user sees.
- Trailing controls (Toggle, Button) render as siblings of the toggle button
(not nested), avoiding invalid <button> inside <button> HTML.
59/59 unit tests green.
Hand-maintained DEFAULT_SYSTEM_TRANSFORMS_CLIENT mirror in RoutingTab.tsx
drifts silently from server DEFAULT_SYSTEM_TRANSFORMS_CONFIG. New test asserts
deepEqual against the JSON-shape of the server export and points at the UI
file in the failure message so contributors update both in the same commit.
23/23 green.
`fetch()` always transparently decompresses the upstream body before
exposing it via `.text()` or the stream reader, so forwarding the
upstream `content-encoding` header (e.g. `gzip`) to the downstream
OpenAI/Anthropic-compatible client makes the client attempt to gunzip
plain text and fail with `ZlibError: incorrect header check`.
Similarly, `content-length` becomes stale once we transform the response
body (non-stream path repacks via `new Response()`; stream path
re-streams through our SSE heartbeat / shape transforms), and
`transfer-encoding` is owned by the Node/Next runtime, not us.
This patch adds `open-sse/utils/upstreamResponseHeaders.ts` exporting:
- `stripStaleEncodingHeaders(input: Headers)` — returns a new `Headers`
with content-encoding, content-length, transfer-encoding removed
(case-insensitive, does not mutate input).
- `filterUpstreamResponseHeaderEntries(entries, extraToStrip)` — same
semantics for the entries-array path used by the streaming response
builder, plus user-supplied additional names (case-insensitive).
- `STRIP_UPSTREAM_HEADER_NAMES` — the canonical set, exported for tests.
`open-sse/handlers/chatCore.ts` now uses these helpers in two places:
1. Non-stream path (~L3211): replaces `new Headers(rawResult.response.headers)`
with `stripStaleEncodingHeaders(...)` so the repacked Response below
doesn't claim a stale encoding/length.
2. Stream path (~L4371): replaces an inline IIFE+filter that only
removed `content-type` with `filterUpstreamResponseHeaderEntries(...,
["content-type"])` so the stale encoding/length are also stripped
before we set our own `text/event-stream` content-type.
Both call sites carry a comment explaining the underlying fetch
decompression behavior.
Tests
-----
New `tests/unit/upstream-response-headers-strip.test.ts` adds 10 cases
covering: lowercase strip, mixed-case strip, input non-mutation, empty
input, default-set filter, case-insensitive extraToStrip, empty
extraToStrip preservation, empty entries, mixed-case default names, and
canonical set identity.
Verified locally
----------------
- `npx eslint open-sse/utils/upstreamResponseHeaders.ts open-sse/handlers/chatCore.ts tests/unit/upstream-response-headers-strip.test.ts` — clean.
- `npm run typecheck:core` — clean.
- `node --import tsx/esm --test tests/unit/upstream-response-headers-strip.test.ts tests/unit/upstream-headers-sanitize.test.ts tests/unit/chatcore-sanitization.test.ts tests/unit/chat-route-edge-cases.test.ts tests/unit/chatcore-translation-paths.test.ts tests/unit/chatcore-compression-integration.test.ts` — 95/95 pass.
- Full `npm run test:unit` / `test:coverage` deferred to upstream CI
(122 test files, exceeds local timeout window).
remapToolNamesInRequest() set body._claudeCodeRequiresLowercaseToolNames = true
when a request contained only lowercase tool names. The flag had no readers in
src/ or open-sse/ (verified by repo-wide grep) and leaked into the outgoing
Anthropic /v1/messages payload, causing HTTP 400:
"_claudeCodeRequiresLowercaseToolNames: Extra inputs are not permitted"
The response-side lowercase remap via remapToolNamesInResponse(text, true) is
unconditional and does not depend on this flag, so removing it is a no-op for
the response path while fixing the request path.
Adds tests/unit/claude-code-tool-remapper-flag-leak.test.ts as a regression
guard with 5 cases covering all-lowercase, all-TitleCase, mixed-case, and a
flag-leak sweep across all input shapes.
Caveman-review iteration on commit 4fde71a4:
1. Provider selection uses Select dropdown populated from AI_PROVIDERS
registry (the canonical provider catalog) instead of free-text Input.
Filters out providers already configured under systemTransforms.providers.
Drops PROVIDER_ID_PATTERN regex + newProviderError state — dropdown
makes invalid input impossible.
2. Per-op move-up / move-down / delete buttons now use the shared Button
component with material icons (keyboard_arrow_up, keyboard_arrow_down,
delete) + i18n titles, instead of raw <button> with emoji glyphs.
Matches the rest of the OmniRoute UI.
3. BUILTIN_PROVIDERS Set was being recreated every render inside the
component body. Moved to module scope.
i18n: added systemTransformsAddProviderAllConfigured, systemTransformsOpMoveUp,
systemTransformsOpMoveDown, systemTransformsOpDelete. Re-purposed
systemTransformsAddProviderPlaceholder as the dropdown's empty-state label
('Select a provider…').
Tests: 58/58 green (cc-bridge-transforms + system-transforms +
claude-code-compatible-helpers).
Refs PR #2286.
- System-block transform pipeline is now a dedicated Card (was nested
subsection inside the CLI Fingerprint Matching Card).
- Any provider ID can be added via the 'Add provider' input at the
bottom of the transforms Card; the new provider starts with
enabled=false and an empty pipeline.
- Custom (non-builtin) providers have a delete button to remove them.
- Builtin providers (claude, anthropic-compatible-cc) are protected from
deletion (removeProvider guard).
- Add systemTransforms i18n keys for the new section title, description,
add-provider label/placeholder, remove label, empty state.
- The CLI Fingerprint Matching Card is now a clean standalone Card.
applyPrependSystemBlock and applyAppendSystemBlock were not using the
idempotencyKey when set — the old check used op.text as the idempotency
prefix and only examined the first/last block.
Fix: scan ALL existing text blocks; use idempotencyKey as prefix when set,
fall back to op.text (prepend) / exact match (append).
- Replace raw <input>/<select>/<textarea>/<label> in OpEditor with shared
Input, Select, Toggle components — matches the pattern used by every
other settings tab in the app.
- New StringListEditor helper consolidates the three list-of-strings
forms (needles, prefixes, words) into one consistent component.
- Replace the peer-checkbox custom provider-enable toggle with the
shared Toggle component (same look as Adaptive Volume, LKGP, etc).
- Replace the raw add-op <select> with shared Select.
- Drop nine unused 'ccBridgeTransforms*' i18n keys left over from the
Phase 2 v1 UI — current UI uses inline strings under the
'CLI Fingerprint Matching' card.
- Document why systemTransforms keeps its own looser RequestBody shape
(legacy ccBridgeTransforms RequestBody is stricter; cast at boundary).
- claude provider enabled:false by default (opt-in; was incorrectly true)
- add OpEditor component: per-op form editor for all 9 DSL kinds
(add/move-up/move-down/delete via UI buttons, JSON editor collapsed)
- rename card back to 'CLI Fingerprint Matching' per user feedback
- JSON import/export now collapsible under '▸ Import / export JSON'
- tests updated to explicitly enable claude provider where pipeline behavior
is under test (not the opt-in default)
feat(cli): suporte i18n completo — 42 locales, --lang flag, config lang get/set/list
- 42 locale files in bin/cli/locales/ (en + pt-BR fully translated, 29 with common/program, 11 scaffolds)
- --lang <code> global flag for per-execution override
- config lang get/set/list subcommands
- Locale persistence via ~/.omniroute/.env
- Path traversal protection via regex validation in normalize()
- Script generate-locales.mjs for scaffolding new locales
- Unit tests for lang commands + normalization security
Integrated into release/v3.8.0
The sentinel field set by remapToolNamesInRequest() was being included in
the JSON body sent to Anthropic, which rejects unknown top-level fields
with 400 invalid_request_error. Stripped before JSON.stringify in both
code paths:
- buildAndSignClaudeCodeRequest (CC bridge / anthropic-compatible-cc-*)
- base executor serialization path (native claude/ provider)
Pre-existing bug, not introduced by issue #2260 changes.
- Remove dead setWordsForOp function (unused, acknowledged in comment)
- Remove unused obfuscateSensitiveWords import and re-export from systemTransforms
- Increase textarea rows cap from 20→40 for long CC-bridge pipelines (~100 lines JSON)
- Add [SystemTransforms] console.log at both call sites (cc-bridge step 5b + claude native path)
Tests: 58/58 green
v2 of the CC bridge body transforms (issue #2260). Generalizes the
single-provider `ccBridgeTransforms` config (commit e3e962db) into a
per-provider registry keyed by OmniRoute provider id, and wires the
native `claude` OAuth path into the same DSL so raw `claude/<model>`
requests no longer bypass the sanitization layer.
Reference: comment 4459544580 reported a 429 on raw `claude/<model>`
from Open WebUI; CC-bridge-only v1 didn't help that path. v2 closes
three gaps named in the comment:
1. `openwebui` / `open-webui` added to the default obfuscation
word list (new `obfuscate_words` op kind, configurable).
2. Native `claude` provider path now runs the per-provider pipeline
after its existing billing+sentinel prepend (executors/base.ts).
Its default pipeline is cosmetic only (Open WebUI paragraph
anchors + identity-prefix drop + ZWJ obfuscation); it deliberately
omits `inject_billing_header` so it never collides with the native
prepend.
3. Open WebUI paragraph anchors (github.com/open-webui/open-webui,
openwebui.com, docs.openwebui.com) and identity prefix
("You are Open WebUI") added to both `claude` and CC bridge
default pipelines.
API surface:
- New module: open-sse/services/systemTransforms.ts
* `SystemTransformsConfig { providers: Record<id, { enabled, pipeline }> }`
* `TransformOp` extends the base CC bridge op set with
`obfuscate_words { words[], targets[] }`.
* `applySystemTransformPipeline(providerId, body, config?)`
routes to the right per-provider pipeline (with CC bridge prefix
match so `anthropic-compatible-cc-*` all share one config).
* `setSystemTransformsConfig` accepts both legacy single-provider
shape (migrates into providers[anthropic-compatible-cc]) and the
new per-provider shape (merges with defaults for unset providers).
- Native claude wedge: executors/base.ts now calls
`applySystemTransformPipeline(PROVIDER_CLAUDE, tb)` after the
existing billing+sentinel prepend (line ~789).
- CC bridge step 5b: claudeCodeCompatible.ts step 5b switched from
`applyCcBridgeTransformPipeline` (single-config) to
`applySystemTransformPipeline(PROVIDER_CC_BRIDGE, body)`. The
underlying ccBridgeTransforms.ts module remains the base executor
that systemTransforms.ts delegates to for the shared op kinds —
no rename to keep the diff reviewable, and all 30 base-op tests
stay green.
- Settings:
* Zod schema gains `systemTransforms.providers[*]` with full
discriminated union over the 9 op kinds (including
obfuscate_words).
* Legacy `ccBridgeTransforms` zod field kept for back-compat read;
runtime now feeds it through `setSystemTransformsConfig` migration
shim so persisted Phase-2 data keeps working. v2 `systemTransforms`
wins on conflict (applied last).
* Runtime settings registry gains `systemTransforms` reload section
next to legacy `ccBridgeTransforms`.
- UI rewrite (RoutingTab.tsx): the two standalone cards (CLI
Fingerprint + CC Bridge Transforms) collapse into one
"Provider Upstream Compatibility" card. Per-provider tiles render
the pipeline summary + JSON editor with client-side shape
validation + Apply / Reset. Copy clarifies that no local Claude
Code binary is required (the lock badge on the Claude tile means
the fingerprint is force-applied for OAuth account safety, not
that the user must install the CLI).
Tests:
- tests/unit/system-transforms.test.ts (22 new tests covering
defaults, per-op semantics, ordering, per-provider routing, opt-in
pass-through, Open WebUI fixture, migration shim, idempotency).
- tests/unit/cc-bridge-transforms.test.ts (30 base-op tests
unchanged, still green).
- tests/unit/claude-code-compatible-{helpers,request}.test.ts and
8 related claude/cc/executor test files all green (140 tests).
Closes#2260.
The default import + destructuring pattern was being mangled by webpack's
static analysis during Next.js standalone builds, causing 'Cannot destructure
property machineIdSync of undefined' errors in production.
Using require() bypasses webpack's interop wrapper (c.n(...)) and loads the
module's exports directly at runtime.
- next-themes: required by TierFlowDiagram.tsx (onboarding)
- sql.js: required by CLI runtime sqliteRuntime.mjs
- Add sql.js to serverExternalPackages in next.config.mjs to prevent
webpack static resolution failures during build
There was an asymmetry in the CLIENT_API policy: with REQUIRE_API_KEY
off, a request with no bearer was allowed as anonymous, but a request
with an *invalid* bearer was rejected with 401 "Invalid API key". That
surprises CLI integrations (Codex Desktop auto-config, Hermes Agent)
that ship a stale Bearer in their saved config — they'd see a 401 even
though the operator explicitly opted out of auth.
Fix: when validateApiKey fails and REQUIRE_API_KEY != "true", log a
single warning carrying the masked key id (last-4) and fall through to
anonymous. When REQUIRE_API_KEY is "true", the strict 401 path is
preserved.
The warning preserves observability:
[clientApiPolicy] invalid bearer presented to /api/v1/responses
but REQUIRE_API_KEY=false — falling through to anonymous
(key_id=key_XYZW)
Tests are added in a standalone file because the existing
client-api-policy.test.ts shares a DB-backed setup with a pre-existing
SQLite migration race (5/7 of its tests already failed on baseline).
The new file mocks validateApiKey via a require-resolve interceptor so
the policy's invalid-bearer branch is exercised without touching SQLite.
Reported by @k00shi on the Codex Desktop auto-config path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
C1: sanitize opts.name and backupId against path traversal (replace /\\ with _)
C2: read backup files locally as base64 instead of sending local path to cloud API
C3: implement markOAuthDone/markOAuthFailed via module-level callbacks registered
in useEffect — TUI now transitions to DONE/FAILED when polling signals completion
I1: fix matchesGlob — equality-only for non-glob patterns (removes false-positive
startsWith), and support multi-wildcard patterns (e.g. **.json) by iterating parts
I3: replace two hardcoded English strings in tunnel.mjs with t() calls; add
tunnel.notAvailable and tunnel.noTunnels to en.json and pt-BR.json
I4: log HTTP 4xx errors in runKeysAddCommand and return 1 instead of falling
through silently to DB write on client errors
I5: truncate err.message to 100 chars in ProvidersTestAll.jsx and test-provider.mjs
m1: fix EvalWatch StatusBadge — completed state now uses status="ok" instead of "running"
m2: replace hardcoded ~/.omniroute/backup-schedule.json with resolveDataDir()-based path
m4: remove ...opts spread from all apiFetch calls in keys.mjs — pass only explicit options
(method, body, retry, acceptNotOk) to avoid leaking apiKey/baseUrl/yes/etc.
Adiciona 3 componentes TUI Ink faltantes da spec 8.10:
- OAuthFlow.jsx: spinner + URL interativo para fluxo browser OAuth
- EvalWatch.jsx: monitor live de eval run com ProgressBar e DataTable
- ProvidersTestAll.jsx: teste paralelo com concorrência configurável
Integração nos comandos:
- oauth start browser flow → OAuthFlow quando TTY
- eval run --watch → EvalWatch quando TTY (fallback texto sem TTY)
- test --all-providers → ProvidersTestAll quando TTY (fallback tabela sem TTY)
Adds a config-driven DSL pipeline for normalizing the system blocks sent
to Anthropic via the Claude Code bridge. Removes third-party agent CLI
fingerprints (OpenCode, Cline, Cursor, Continue) and prepends the SDK
identity + signed billing header so the request looks classifier-correct
regardless of which client originally sent it.
Why a DSL: future fingerprint defenses ship as config rows, not new code.
Mode presets (basic/aggressive/custom), reorder, add, delete, reset — all
hot-reloaded through the existing runtimeSettings registry.
Files
- open-sse/services/ccBridgeTransforms.ts (~440 LOC; 8 op-kinds, executor,
buildBillingHeaderValue ported from ex-machina cch.ts, singleton config
matching cliFingerprints pattern, T4-200 fixture-matching defaults)
- tests/unit/cc-bridge-transforms.test.ts (30 tests; per-op, idempotency,
ordering, verbatim-OpenCode regression)
- open-sse/services/claudeCodeCompatible.ts (step 5b in buildAndSign
pipeline + re-exports for downstream)
- open-sse/executors/base.ts (maintainer comment: native
OAuth path is intentionally not routed through the DSL; it has its own
billing prepend at lines 744-773)
- src/lib/config/runtimeSettings.ts (RuntimeReloadSection +
snapshot + default + normalize + apply + change-detection)
- src/shared/validation/settingsSchemas.ts (zod discriminatedUnion over
the 8 op-kinds, max 50 ops per pipeline)
- src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx
(Settings UI Card: enabled toggle, ordered pipeline list with reorder
buttons, add-op dropdown, reset-to-defaults)
- src/i18n/messages/en.json (10 new strings under
settings.ccBridgeTransforms.*)
Defaults match the empirical T4=200 layout: [billing, identity, sanitized,
extras]. Verified against prod call logs (1c1946a2 = plugin-200,
de8ab5bd = raw-429) and four-variant live A/B test (T1=429 baseline,
T2=429 phrase-only, T3=429 sanitizer-only, T4=200 with billing+identity).
Test result: 30/30 ccBridgeTransforms + 6/6 baseline claude-code-compatible-helpers green.
Scope: CC bridge surface only (open-sse/services/claudeCodeCompatible.ts).
Native claude OAuth path is NOT routed through the DSL — it has its own
billing prepend mechanism that already works.
Refs: https://github.com/diegosouzapw/OmniRoute/issues/2260