mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-09 17:28:51 +00:00
fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576)
This commit is contained in:
parent
f4fb0d310b
commit
1e3ee5a0c4
5 changed files with 97 additions and 13 deletions
|
|
@ -28,6 +28,7 @@ _Living section — bullets land here as PRs merge into `release/v3.8.47` (paral
|
|||
- **fix(providers):** `fusion` combo strategy silently returned a panel member's raw answer instead of the configured `config.judgeModel` synthesis ([#6455](https://github.com/diegosouzapw/OmniRoute/issues/6455)) — `handleFusionChat()`'s single-survivor "degrade gracefully" path (added for #6454) returned the lone panel answer directly whenever only one panelist succeeded, regardless of whether an explicit `judgeModel` was configured; with the default `minPanel: 2` and a 2-model panel, any single flaky/rate-limited panelist forced this path on every request, so the configured judge (e.g. `auto/claude-opus`) was never invoked and the client-visible `.model` reflected whichever panelist happened to survive. The judge is now still invoked to synthesize a lone surviving answer whenever `judgeModel` is explicitly configured; the cheap direct-answer shortcut is kept only for the implicit case (no `judgeModel` set, where the "judge" is just `panel[0]`). Regression guard: `tests/unit/fusion-judge-model-6455.test.ts` + updated `tests/unit/combo-fusion-strategy.test.ts`. (thanks @chirag127)
|
||||
- **feat(combo):** sanitized diagnostic trace on an auto-combo terminal failure — instead of an opaque 503, a terminal combo failure now returns a whitelist-projected trace (candidate pool size, attempted count, excluded provider/reason codes, attempt order, and a terminal-reason code) via the new `errorResponseWithComboDiagnostics()`/`sanitizeComboDiagnostics()` in `open-sse/utils/error.ts` — provider/model ids and enumerated reason codes only, never keys/tokens/bodies, length- and count-capped. A reasoning-budget-exhausted panel now returns an actionable "increase max_tokens" message rather than a blind retry-limit 503. Regression guard: `tests/unit/combo-diagnostics-trace.test.ts`. ([#6545](https://github.com/diegosouzapw/OmniRoute/pull/6545)) — see PR. (thanks @developerjillur)
|
||||
- **fix(providers):** image/diffusion models discovered from an upstream catalog (e.g. HuggingFace's live `/v1/models`) are no longer advertised as chat models ([#6457](https://github.com/diegosouzapw/OmniRoute/issues/6457)) — the chat catalog builder defaulted synced models with no modality info to `endpoints: ["chat"]`, so `huggingface/stabilityai/stable-diffusion-xl-base-1.0` showed up in the chat `/v1/models` listing and returned `400 "not a chat model"` when called. `catalog.ts` now skips any synced model already registered as an image model for that provider (via the new `isRegisteredImageModel()`), leaving `getAllImageModels()` to list it with the correct `type: "image"`. Regression guard: `tests/unit/image-model-not-in-chat-catalog-6457.test.ts`.
|
||||
- **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(test):** replace the bare `expect(true).toBe(true)` tautology in `playground-api-tab.test.tsx`'s SSE test and close the `check:test-masking` gap that let it slip through for a full cycle ([#6404](https://github.com/diegosouzapw/OmniRoute/issues/6404)) — a prior pass (#6548) had already swapped the literal to `expect(sendBtn).toBeDefined()`, but that stayed just as vacuous: the test's fetch mock returned an empty `/v1/models` list, so `ApiTab`'s Send button is always `disabled` (`!selectedModel`) and the SSE branch never runs — the "SSE infra is verified" comment was never true. The test now mocks a real model, drives the model `<select>` to enable Send, asserts `sendBtn.disabled === false` before clicking, and asserts the streamed SSE delta (`"Hello!"`) actually reached the response editor. Root cause on the detector side: `check-test-masking.mjs`'s tautology subcheck only compares base-vs-HEAD counts within a PR's own diff (`headExtTaut > baseExtTaut`) and no-ops locally when `GITHUB_BASE_SHA`/`GITHUB_BASE_REF` are unset ("sem base ref — pulando") — so a tautology merged once, or checked with a bare local run, was invisible forever after. Added a new always-on, PR-independent absolute-floor scan (`scanBareTautologies` + `countBareTautologies`) over every git-tracked test file for the bare `expect(true).toBe(true)` / `assert.equal(1,1)` / `assert.strictEqual(1,1)` patterns specifically (deliberately excluding `assert.ok(true)`, which has ~15 pre-existing verified-legitimate try/catch-fallback uses repo-wide and stays governed by the lenient diff-only subcheck) — verified zero pre-existing hits repo-wide once this file was fixed, so the new floor is safe to enforce unconditionally. Regression guard: `tests/unit/check-test-masking.test.ts` (new `scanBareTautologies`/`countBareTautologies` cases) + `tests/unit/ui/playground-api-tab.test.tsx`. (thanks @chirag127)
|
||||
- **fix(oauth):** Codex/ChatGPT (and every other OAuth provider) connection stays stuck showing "Auth Failed" even after a genuinely successful token refresh ([#6352](https://github.com/diegosouzapw/OmniRoute/issues/6352)) — `updateProviderCredentials()` (the shared `onPersist` callback for the manual "Refresh token" route, the reactive per-request refresh in `chat.ts`, and the Codex/Claude auth-file importers) correctly reused the stored `refresh_token`, persisted the new `access_token`, and replaced a rotated `refresh_token`, but never cleared the stale `testStatus`/`lastError*`/`errorCode` fields left over from a prior expired/invalid refresh or upstream 401/403 — only the separate background health-check sweep did that clearing. A successful refresh now resets `testStatus` to `"active"` and clears `lastError`, `lastErrorAt`, `lastErrorType`, `lastErrorSource`, and `errorCode` (an explicit `testStatus` from the caller still wins). Regression guard: `tests/unit/codex-oauth-refresh-persist-6352.test.ts`.
|
||||
- **perf(health):** short-TTL (1s) cache for the frequently-polled `GET /api/monitoring/health` payload — rebuilding it every request (DB reads + status aggregation across 8 subsystems) was wasteful under rapid polling; the cache stays near-real-time and is invalidated immediately on `DELETE` (circuit-breaker reset) so a manual reset is reflected at once. Regression guard: `tests/integration/monitoring-health-cache.test.ts`. ([#6553](https://github.com/diegosouzapw/OmniRoute/pull/6553)) — see PR. (thanks @developerjillur)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -837,14 +838,13 @@ 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.
|
||||
const kiroEffort = supportsReasoning(normalizedModel) ? resolveKiroEffort(body) : "";
|
||||
// the Kiro executor's transformRequest allowlist — the graded effort lever,
|
||||
// gated on Kiro's adaptive-thinking allowlist (#6576), not supportsReasoning().
|
||||
const kiroEffort = supportsKiroAdaptiveThinking(normalizedModel) ? resolveKiroEffort(body) : "";
|
||||
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)"
|
||||
);
|
||||
});
|
||||
|
|
@ -1098,7 +1098,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, {
|
||||
|
|
@ -1126,7 +1126,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(
|
||||
|
|
@ -1157,7 +1157,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");
|
||||
|
|
@ -1189,7 +1189,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
|
||||
|
|
@ -1201,7 +1201,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
|
||||
|
|
@ -1216,7 +1216,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",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue