mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-15 19:52:50 +03:00
fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576)
This commit is contained in:
@@ -23,6 +23,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **fix(providers):** Kiro sent the adaptive-thinking `additionalModelRequestFields` envelope for `claude-sonnet-4.5`/`claude-haiku-4.5`, which Kiro/CodeWhisperer rejects upstream with a raw `[400]: additionalModelRequestFields is not supported for this model` ([#6576](https://github.com/diegosouzapw/OmniRoute/issues/6576)) — `buildKiroPayload()` (`open-sse/translator/request/openai-to-kiro.ts`) gated the field on the generic Anthropic-API `supportsReasoning()` capability flag, which is `true` for both models on Anthropic's direct API but does not reflect what Kiro's CodeWhisperer backend actually accepts; only `claude-sonnet-5` is confirmed adaptive-thinking-capable there. A new Kiro-specific allowlist (`supportsKiroAdaptiveThinking()` in `open-sse/translator/request/openai-to-kiro/adaptiveThinking.ts`) now gates the envelope instead. Regression guard: `tests/unit/repro-6576-kiro-thinking-unsupported-model.test.ts`.
|
||||
- **fix(cli):** per-agent AgentBridge DNS toggle was broken for 8 of the 9 supported agents, and a failed MITM startup step could orphan the spawned proxy child — `addDNSEntry`/`removeDNSEntry` (`src/mitm/dns/dnsConfig.ts`) always resolved the legacy Antigravity default hosts regardless of which agent's toggle was flipped, so enabling DNS for Cursor/Codex/Claude Code/etc. silently added only `daily-cloudcode-pa.googleapis.com` while the DB recorded `dns_enabled=true` for the selected agent. Both functions now accept an optional `agentId` and resolve hosts via `ALL_TARGETS`; `POST /api/tools/agent-bridge/agents/[id]/dns` passes the route's `id` through and now returns 404 for an id that doesn't match a known target instead of silently falling back. Separately, `startMitmInternal()` (`src/mitm/manager.ts`) now wraps `generateCert()` (log + rethrow), the `provisionDnsEntries()` call, and the PID-file write in try/catch so a mid-startup failure can't orphan the already-spawned MITM child process. On Windows, `addDNSEntries`/`removeDNSEntries` also batch every missing/present entry into a single elevated PowerShell invocation instead of one UAC prompt per host line. Regression guard: `tests/unit/dns-config-generic.test.ts` (agent-specific resolution + batching), `tests/unit/agent-bridge-dns-route-validation.test.ts` (404 for unknown agent id). ([#6338](https://github.com/diegosouzapw/OmniRoute/pull/6338) — thanks @hamsa0x7)
|
||||
- **fix(guardrails):** Vision Bridge's individual-model auto-reroute (route an image-bearing request straight to a vision-capable model instead of describe-then-forward) could bypass a policy-restricted API key's model allowlist/budget ([#6640](https://github.com/diegosouzapw/OmniRoute/pull/6640)) — `VisionBridgeGuardrail.preCall()` (`src/lib/guardrails/visionBridge.ts`) swaps `body.model` to the best available vision-capable model, but that swap happens in the guardrail pipeline AFTER `chat.ts` already called `enforceApiKeyPolicy()` against the ORIGINAL model, so a key scoped to a narrow `allowedModels` list could still execute against an unvetted (and possibly costlier) vision model the reroute picked. `chat.ts` now re-validates any guardrail-driven model change against the same per-key allowlist (`isModelAllowedForKey`) before honoring it, falling back to the original already-approved model when the reroute target is not allowed. The reroute path also now honors an explicit `settings.visionBridgeModel` operator override (previously ignored, unlike the combo/describe path a few lines below it, which already respects it via `getVisionBridgeConfig`). Regression guard: `tests/unit/guardrails/visionBridge.test.ts` (22 tests). (thanks @herjarsa)
|
||||
- **fix(auth):** an API key restricted via `allowedModels`/`allowedCombos` could bypass that restriction entirely over the Codex Responses-over-WebSocket bridge ([#6564](https://github.com/diegosouzapw/OmniRoute/issues/6564)) — `prepare()` in `src/app/api/internal/codex-responses-ws/route.ts` authenticated the WS bridge's API key (`authenticate()`/`authorizeWebSocketHandshake()`) and honored `allowedConnections`, but never called `enforceApiKeyPolicy()`, the same model/combo policy gate the HTTP `/v1/responses` path enforces via `handleChat()` — so a key scoped to e.g. `combo/model-1.0` could still reach a direct Codex model like `gpt-5.5` through this transport, as long as an eligible Codex OAuth connection existed. The bridge's WS auth token arrives via query params (`api_key`/`token`/`access_token`), not a normal `Authorization` header, so a new `enforceCodexWsApiKeyPolicy()` builds an equivalent `Request` carrying an explicit `Authorization: Bearer <apiKey>` header and calls `enforceApiKeyPolicy()` against the CLIENT-requested model, before any Codex-specific model remapping or credential selection. Regression guard: `tests/unit/codex-ws-policy-enforcement-6564.test.ts` (a model-restricted key is rejected 403 before reaching credential selection; a combo-restricted key is rejected 403 requesting a disallowed combo; a key that DOES allow the requested model still proceeds past policy).
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
import { register } from "../registry.ts";
|
||||
import { FORMATS } from "../formats.ts";
|
||||
import { v4 as uuidv4, v5 as uuidv5 } from "uuid";
|
||||
import { capMaxOutputTokens, capThinkingBudget, supportsReasoning } from "@/lib/modelCapabilities";
|
||||
import { capMaxOutputTokens, capThinkingBudget } from "@/lib/modelCapabilities";
|
||||
import {
|
||||
parseToolInput,
|
||||
normalizeKiroToolSchema,
|
||||
serializeToolResultContent,
|
||||
} from "./openai-to-kiro/messageHelpers.ts";
|
||||
import { supportsKiroAdaptiveThinking } from "./openai-to-kiro/adaptiveThinking.ts";
|
||||
|
||||
/**
|
||||
* Anthropic's direct-provider `[1m]` context-1m beta suffix. Kiro is AWS
|
||||
@@ -858,15 +859,14 @@ export function buildKiroPayload(model, body, stream, credentials) {
|
||||
// Thinking mode for Claude models on Kiro (ported from javargasm/pi-kiro).
|
||||
// Two coordinated signals steer reasoning on the CodeWhisperer surface:
|
||||
// 1. a `<thinking_mode>enabled</thinking_mode><max_thinking_length>N</...>`
|
||||
// directive prepended to the current user message — makes Claude emit its
|
||||
// reasoning INLINE as `<thinking>…</thinking>`, which the Kiro executor
|
||||
// splits back into the OpenAI `reasoning_content` channel (kiroThinking.ts);
|
||||
// directive prepended to the user message — makes Claude emit reasoning
|
||||
// INLINE, split back into `reasoning_content` by the executor (kiroThinking.ts);
|
||||
// 2. top-level `additionalModelRequestFields` (output_config.effort +
|
||||
// thinking:{type:"adaptive"} + a clamped max_tokens), forwarded to AWS by
|
||||
// the Kiro executor's transformRequest allowlist — this is the graded
|
||||
// effort lever. Gated on models that advertise thinking support.
|
||||
// the Kiro executor's transformRequest allowlist — the graded effort lever,
|
||||
// gated on Kiro's adaptive-thinking allowlist (#6576), not supportsReasoning().
|
||||
const requestedEffort = resolveKiroEffort(body) || (modelRequestsThinking ? "high" : "");
|
||||
const kiroEffort = supportsReasoning(normalizedModel) ? requestedEffort : "";
|
||||
const kiroEffort = supportsKiroAdaptiveThinking(normalizedModel) ? requestedEffort : "";
|
||||
if (kiroEffort) {
|
||||
// `<thinking_mode>` / `<max_thinking_length>` are Kiro/CodeWhisperer prompt
|
||||
// conventions (NOT Anthropic API params); the length is a soft hint (the hard
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Kiro/AWS CodeWhisperer only accepts the adaptive-thinking
|
||||
* `additionalModelRequestFields` envelope for a narrow allowlist of models —
|
||||
* NOT the same set the generic Anthropic-API capability table
|
||||
* (`supportsReasoning()` in `@/lib/modelCapabilities`) marks as
|
||||
* thinking-capable. That table is correct for Anthropic's own API, but Kiro
|
||||
* rejects the field for `claude-sonnet-4.5` and `claude-haiku-4.5` with a raw
|
||||
* upstream 400 (`additionalModelRequestFields is not supported for this
|
||||
* model`, issue #6576) even though both ARE thinking-capable on Anthropic's
|
||||
* direct API. Only `claude-sonnet-5` is confirmed to accept the adaptive
|
||||
* envelope on Kiro today — keep this allowlist in sync with
|
||||
* `open-sse/config/providers/registry/kiro/index.ts` if Kiro's catalog or
|
||||
* upstream behavior changes.
|
||||
*/
|
||||
const KIRO_ADAPTIVE_THINKING_MODELS = new Set(["claude-sonnet-5"]);
|
||||
|
||||
export function supportsKiroAdaptiveThinking(normalizedModel: string): boolean {
|
||||
return KIRO_ADAPTIVE_THINKING_MODELS.has(normalizedModel);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Repro probe for GitHub issue #6576.
|
||||
//
|
||||
// Kiro/CodeWhisperer rejects `additionalModelRequestFields` for
|
||||
// claude-sonnet-4.5 / claude-haiku-4.5 with a raw upstream 400:
|
||||
// "[400]: additionalModelRequestFields is not supported for this model"
|
||||
//
|
||||
// buildKiroPayload() gates thinking injection on supportsReasoning(model),
|
||||
// which resolves from the GENERIC Anthropic-API capability data
|
||||
// (MODEL_SPECS["claude-sonnet-4-5-..."].supportsThinking === true,
|
||||
// MODEL_SPECS["claude-haiku-4-5-20251001"].supportsThinking === true).
|
||||
// That flag says nothing about whether the *Kiro/AWS CodeWhisperer*
|
||||
// backend accepts the adaptive-thinking additionalModelRequestFields
|
||||
// envelope for these specific models — only the newer adaptive-only
|
||||
// models (Opus 4.7/4.8, Sonnet 5, Fable 5) are proven to accept it there
|
||||
// (see the existing "drops temperature when thinking is enabled" tests).
|
||||
//
|
||||
// This test asserts the payload for claude-sonnet-4.5 (the reporter's own
|
||||
// model) must NOT carry additionalModelRequestFields when reasoning is
|
||||
// requested, matching what Kiro's upstream actually accepts. It currently
|
||||
// FAILS because buildKiroPayload has no Kiro-specific allowlist/exclusion
|
||||
// and blindly forwards the field whenever the generic capability flag says
|
||||
// supportsThinking:true.
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const { buildKiroPayload } = await import(
|
||||
"../../open-sse/translator/request/openai-to-kiro.ts"
|
||||
);
|
||||
|
||||
test("[repro #6576] buildKiroPayload must not attach additionalModelRequestFields for claude-sonnet-4.5 (Kiro rejects it)", () => {
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "Calculate 51818+62218, and reply with result only." }],
|
||||
reasoning_effort: "medium",
|
||||
max_tokens: 2048,
|
||||
stream: false,
|
||||
};
|
||||
|
||||
const result = buildKiroPayload("claude-sonnet-4.5", body, false, null);
|
||||
|
||||
assert.equal(
|
||||
result.additionalModelRequestFields,
|
||||
undefined,
|
||||
"additionalModelRequestFields must not be sent for claude-sonnet-4.5 — " +
|
||||
"Kiro/CodeWhisperer rejects it upstream with " +
|
||||
"'[400]: additionalModelRequestFields is not supported for this model' (issue #6576)"
|
||||
);
|
||||
});
|
||||
|
||||
test("[repro #6576] buildKiroPayload must not attach additionalModelRequestFields for claude-haiku-4.5 (Kiro rejects it)", () => {
|
||||
const body = {
|
||||
messages: [{ role: "user", content: "hi" }],
|
||||
thinking: { type: "adaptive" },
|
||||
};
|
||||
|
||||
const result = buildKiroPayload("claude-haiku-4.5", body, false, null);
|
||||
|
||||
assert.equal(
|
||||
result.additionalModelRequestFields,
|
||||
undefined,
|
||||
"additionalModelRequestFields must not be sent for claude-haiku-4.5 — " +
|
||||
"Kiro/CodeWhisperer rejects it upstream (issue #6576 comment by fenix007: " +
|
||||
"9/9 production requests with reasoning params 400'd for this exact model)"
|
||||
);
|
||||
});
|
||||
@@ -1121,7 +1121,7 @@ test("buildKiroPayload enables thinking mode for Claude models via reasoning_eff
|
||||
max_tokens: 64000,
|
||||
};
|
||||
|
||||
const result = buildKiroPayload("claude-opus-4.8", body, false, null);
|
||||
const result = buildKiroPayload("claude-sonnet-5", body, false, null); // only Kiro model accepting adaptive thinking (#6576)
|
||||
|
||||
assert.ok(result.additionalModelRequestFields, "additionalModelRequestFields must be set");
|
||||
assert.deepEqual(result.additionalModelRequestFields.thinking, {
|
||||
@@ -1149,7 +1149,7 @@ test("buildKiroPayload drops temperature when thinking is enabled", () => {
|
||||
temperature: 0.5,
|
||||
};
|
||||
|
||||
const result = buildKiroPayload("claude-opus-4.8", body, false, null);
|
||||
const result = buildKiroPayload("claude-sonnet-5", body, false, null);
|
||||
|
||||
assert.ok(result.additionalModelRequestFields, "thinking must be enabled");
|
||||
assert.equal(
|
||||
@@ -1180,7 +1180,7 @@ test("buildKiroPayload maps body.thinking budget_tokens to effort level", () =>
|
||||
thinking: { type: "enabled", budget_tokens: 50000 },
|
||||
};
|
||||
|
||||
const result = buildKiroPayload("claude-opus-4.7", body, false, null);
|
||||
const result = buildKiroPayload("claude-sonnet-5", body, false, null);
|
||||
|
||||
assert.ok(result.additionalModelRequestFields, "thinking must be enabled from budget_tokens");
|
||||
assert.equal(result.additionalModelRequestFields.output_config.effort, "high");
|
||||
@@ -1212,7 +1212,7 @@ test("buildKiroPayload maps reasoning_effort to the same Kiro effort level (no +
|
||||
|
||||
test("buildKiroPayload reads effort from Anthropic output_config.effort", () => {
|
||||
const result = buildKiroPayload(
|
||||
"claude-opus-4.8",
|
||||
"claude-sonnet-5",
|
||||
{ messages: [{ role: "user", content: "hard" }], output_config: { effort: "xhigh" } },
|
||||
false,
|
||||
null
|
||||
@@ -1224,7 +1224,7 @@ test("buildKiroPayload reads effort from Anthropic output_config.effort", () =>
|
||||
|
||||
test("buildKiroPayload defaults adaptive thinking (no effort) to high", () => {
|
||||
const result = buildKiroPayload(
|
||||
"claude-opus-4.8",
|
||||
"claude-sonnet-5",
|
||||
{ messages: [{ role: "user", content: "hard" }], thinking: { type: "adaptive" } },
|
||||
false,
|
||||
null
|
||||
@@ -1239,7 +1239,7 @@ test("buildKiroPayload defaults adaptive thinking (no effort) to high", () => {
|
||||
|
||||
test("buildKiroPayload drops both temperature and top_p when thinking is enabled", () => {
|
||||
const result = buildKiroPayload(
|
||||
"claude-opus-4.8",
|
||||
"claude-sonnet-5",
|
||||
{
|
||||
messages: [{ role: "user", content: "hard" }],
|
||||
reasoning_effort: "high",
|
||||
|
||||
Reference in New Issue
Block a user