mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* 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:
committed by
GitHub
parent
2e3508186a
commit
039acabad9
@@ -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`.
|
||||
@@ -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)"
|
||||
);
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user