fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576) (#6726)

* fix(providers): Kiro adaptive-thinking allowlist excludes sonnet-4.5/haiku-4.5 (#6576)

* chore(6726): re-sync onto release tip; CHANGELOG entry → changelog.d fragment (fragments-first)

* test(kiro): migrate selector-strip test to claude-sonnet-5 (only Kiro adaptive-thinking model, #6576)

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-10 19:54:17 -03:00
committed by GitHub
parent 2e3508186a
commit 039acabad9
5 changed files with 99 additions and 15 deletions

View File

@@ -0,0 +1 @@
- **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`.

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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)"
);
});

View File

@@ -1049,10 +1049,10 @@ test("buildKiroPayload accepts kr/* model ids without the [1m] suffix", () => {
test("buildKiroPayload strips local Kiro selector suffixes before upstream", () => {
const body = { messages: [{ role: "user", content: "Hello" }] };
const result = buildKiroPayload("claude-opus-4.8-thinking-agentic", body, true, {});
const result = buildKiroPayload("claude-sonnet-5-thinking-agentic", body, true, {});
assert.equal(
result.conversationState.currentMessage.userInputMessage.modelId,
"claude-opus-4.8",
"claude-sonnet-5",
"local -thinking/-agentic aliases must not be forwarded to Kiro"
);
assert.equal(
@@ -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",