mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 11:43:10 +03:00
fix(executors): strip client context_management on 400 (port from 9router#1468)
Claude Code always sends a top-level `context_management` field. Strict anthropic-compatible gateways reject it with 400 "context_management: Extra inputs are not permitted". The dedicated context-editing 400-fallback in base.ts only fires when OmniRoute's own `contextEditing` feature is enabled (default off), so a client-sent field passed through untouched and 400'd with no recovery. Add `context_management` to KNOWN_OFFENDING_FIELDS so the generic reactive 400 field-downgrade strips-and-retries it once, independent of the feature flag (the generic path already re-signs for claude-compatible relays). Regression guard: tests/unit/provider-field-strips.test.ts. Reported-by: ohahe52-dot (https://github.com/decolua/9router/issues/1468)
This commit is contained in:
@@ -20,6 +20,8 @@
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(executors):** recover from a strict gateway's `context_management: Extra inputs are not permitted` 400. **Claude Code** always sends a top-level `context_management` field; strict anthropic-compatible gateways reject it. The dedicated context-editing 400-fallback only fired when OmniRoute's own `contextEditing` feature was enabled (default off), so a client-sent field passed through untouched and 400'd. `context_management` is now in the generic reactive field-strip list, so it's stripped-and-retried once regardless of the feature flag (with correct request re-signing for claude-compatible relays). Regression guard: `tests/unit/provider-field-strips.test.ts`. (thanks @ohahe52-dot)
|
||||
|
||||
- **fix(live-ws):** the Live Dashboard WebSocket server now **rejects on bind failure** (e.g. `EADDRINUSE` when the API bridge already holds the port) instead of letting the error surface as an unhandled `error` event that crash-loops the process — the `error` listener is attached to `wss` (not `server`) and releases the EventBus subscription on a failed start ([#6324](https://github.com/diegosouzapw/OmniRoute/issues/6324)). Regression guard: `tests/unit/live-ws-eaddrinuse-6324.test.ts`. (thanks @vinayakkulkarni)
|
||||
- **fix(dashboard):** the Home provider-topology widget now trusts the live provider-metrics snapshot — it uses `topology.errorProvider` and live `activeRequests` directly instead of re-deriving state from a stale `lastErrorAt` or applying a frontend timeout filter, so the topology reflects real-time provider health. Regression guard: `tests/unit/home-provider-topology-live-state.test.ts`. (thanks @xz-dev)
|
||||
- **fix(sse):** strip zero-width markers from streamed **tool-call arguments** — a follow-up to [#5857](https://github.com/diegosouzapw/OmniRoute/pull/5857). That PR removed injected zero-width joiners (U+200D) from streamed assistant text/reasoning but deliberately left tool-call argument JSON byte-exact. The request-side obfuscation (`open-sse/services/claudeCodeObfuscation.ts`) injects ZWJ into agent words — including the temp path inside the Bash tool description — and Claude models copy that verbatim into generated commands, which are delivered as tool-call arguments rather than assistant text. As a result the ZWJ survived and corrupted code blocks (e.g. a temp path rendered with an invisible joiner). Now `open-sse/handlers/responseSanitizer.ts` strips zero-width code points from tool-call argument strings at every emit site (OpenAI non-stream/stream chat `tool_calls` + legacy `function_call`, native Responses `function_call` items, the OpenAI→Responses conversion, and the native Responses streaming `response.function_call_arguments.delta/.done` events). Only zero-width code points are removed; JSON structure and all other bytes stay identical (no parse/restringify), so normal arguments remain byte-exact. Regression guard: 6 new cases in `tests/unit/response-sanitizer.test.ts` (suite 50/50).
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
// Fields that, when literally named in an upstream 400 body, are safe to strip and
|
||||
// retry once (FCC NIM-style recovery). Mirrors the existing context_management 400
|
||||
// fallback in base.ts, generalized to these OpenAI-compat / NIM reasoning fields.
|
||||
// `context_management` (9router#1468): Claude Code sends it top-level; strict
|
||||
// anthropic-compatible gateways 400 with "context_management: Extra inputs are not
|
||||
// permitted". The dedicated base.ts fallback only fires when OmniRoute's own
|
||||
// contextEditing feature is enabled, so a client-sent field passed through
|
||||
// untouched when the feature is off — this generic strip covers that case.
|
||||
export const KNOWN_OFFENDING_FIELDS: readonly string[] = [
|
||||
"reasoning_budget",
|
||||
"chat_template",
|
||||
"reasoning_content",
|
||||
"context_management",
|
||||
];
|
||||
|
||||
/** Return the first known-offending field literally named in a 400 body, or null. */
|
||||
|
||||
@@ -9,6 +9,12 @@ test("findOffendingField matches known field names in a 400 body", () => {
|
||||
assert.equal(findOffendingField("Invalid argument: reasoning_budget not supported"), "reasoning_budget");
|
||||
assert.equal(findOffendingField("unexpected field chat_template"), "chat_template");
|
||||
assert.equal(findOffendingField("reasoning_content is not allowed"), "reasoning_content");
|
||||
// #1468: Claude Code's top-level context_management field rejected by strict
|
||||
// anthropic-compatible gateways → strip + retry regardless of the contextEditing flag.
|
||||
assert.equal(
|
||||
findOffendingField("context_management: Extra inputs are not permitted"),
|
||||
"context_management"
|
||||
);
|
||||
assert.equal(findOffendingField("all good"), null);
|
||||
assert.equal(findOffendingField(""), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user