fix(kiro): normalize dashboard thinking intensity models
Strip the generic dashboard model(level) suffix before resolving Kiro synthetic -thinking/-agentic variants so the upstream request no longer carries an invalid parenthesized model id. Map explicit levels to native Kiro effort fields only for supported Claude/GPT model families, and stop advertising native levels for unsupported legacy Kiro models. Applies to both OpenAI→Kiro and direct Claude→Kiro routes.
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* fiction. The suffix is stripped before the request leaves this process.
|
||||
*/
|
||||
|
||||
import { extractThinking } from "../translator/concerns/thinkingUnified.js";
|
||||
import { extractThinking, parseSuffix } from "../translator/concerns/thinkingUnified.js";
|
||||
import { effortToBudget } from "../translator/concerns/thinking.js";
|
||||
|
||||
export const KIRO_AGENTIC_SUFFIX = "-agentic";
|
||||
@@ -40,6 +40,39 @@ export function resolveDefaultProfileArn(authMethod) {
|
||||
|
||||
export const KIRO_THINKING_BUDGET_DEFAULT = 16000;
|
||||
|
||||
/**
|
||||
* Resolve a Kiro model after consuming the generic model(level) suffix.
|
||||
* The suffix is a 9router request override, not part of Kiro's upstream model id.
|
||||
*/
|
||||
export function resolveKiroModelIntent(model) {
|
||||
const { cleanModel, override } = parseSuffix(model);
|
||||
return {
|
||||
model: cleanModel,
|
||||
...resolveKiroModel(cleanModel),
|
||||
thinkingOverride: override,
|
||||
};
|
||||
}
|
||||
|
||||
/** Apply a parsed model(level) override without mutating the caller's body. */
|
||||
export function applyKiroThinkingOverride(body, override) {
|
||||
if (!override) return body;
|
||||
|
||||
const next = { ...body };
|
||||
if (override.mode === "budget") {
|
||||
delete next.output_config;
|
||||
delete next.reasoning_effort;
|
||||
delete next.reasoning;
|
||||
next.thinking = { type: "enabled", budget_tokens: override.budget };
|
||||
return next;
|
||||
}
|
||||
|
||||
next.output_config = {
|
||||
...(body.output_config || {}),
|
||||
effort: override.mode === "level" ? override.level : override.mode,
|
||||
};
|
||||
return next;
|
||||
}
|
||||
|
||||
export const KIRO_AGENTIC_SYSTEM_PROMPT = `
|
||||
# CRITICAL: CHUNKED WRITE PROTOCOL (MANDATORY)
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Reuses capabilities.js (thinkingFormat/canDisable) so this file only maps format→levels (DRY).
|
||||
import { getCapabilitiesForModel } from "./capabilities.js";
|
||||
import { matchPattern } from "./pricing.js";
|
||||
import { resolveKiroEffortPath } from "../config/kiroConstants.js";
|
||||
|
||||
// Shared level sets (deduped) — verified against provider docs + wire in thinkingUnified.applyFormat.
|
||||
const L = {
|
||||
@@ -39,6 +40,7 @@ const PATTERN_THINKING = [
|
||||
|
||||
// Returns valid thinking levels for a model, or null when the model has no reasoning.
|
||||
export function getThinkingLevels(provider, model) {
|
||||
if (provider === "kiro" && resolveKiroEffortPath(model) === null) return null;
|
||||
const caps = getCapabilitiesForModel(provider, model);
|
||||
if (!caps.reasoning) return null;
|
||||
const hit = PATTERN_THINKING.find((p) => matchPattern(p.pattern, model));
|
||||
|
||||
@@ -27,7 +27,8 @@ import { FORMATS } from "../formats.js";
|
||||
import { applyKiroSessionReplay } from "../../utils/kiroSessionReplay.js";
|
||||
import { resolveContinuationId, resolveSessionIdentity } from "../../utils/sessionManager.js";
|
||||
import {
|
||||
resolveKiroModel,
|
||||
resolveKiroModelIntent,
|
||||
applyKiroThinkingOverride,
|
||||
resolveKiroThinkingBudget,
|
||||
buildThinkingSystemPrefix,
|
||||
KIRO_AGENTIC_SYSTEM_PROMPT,
|
||||
@@ -389,10 +390,12 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
|
||||
const temperature = body.temperature;
|
||||
const topP = body.top_p;
|
||||
|
||||
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
|
||||
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
|
||||
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
|
||||
const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
|
||||
const modelIntent = resolveKiroModelIntent(model);
|
||||
const { upstream: upstreamModel, agentic } = modelIntent;
|
||||
const thinkingBody = applyKiroThinkingOverride(body, modelIntent.thinkingOverride);
|
||||
const thinkingBudget = resolveKiroThinkingBudget(thinkingBody, credentials?.rawHeaders, modelIntent.model);
|
||||
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(thinkingBody, upstreamModel);
|
||||
const usesNativeGptEffort = usesKiroNativeGptEffort(thinkingBody, upstreamModel);
|
||||
|
||||
// Guard 1: no client tools → flatten all tool interactions to text.
|
||||
if (!clientProvidedTools) {
|
||||
|
||||
@@ -8,7 +8,8 @@ import { v4 as uuidv4 } from "uuid";
|
||||
import { applyKiroSessionReplay } from "../../utils/kiroSessionReplay.js";
|
||||
import { resolveContinuationId, resolveSessionIdentity } from "../../utils/sessionManager.js";
|
||||
import {
|
||||
resolveKiroModel,
|
||||
resolveKiroModelIntent,
|
||||
applyKiroThinkingOverride,
|
||||
resolveKiroThinkingBudget,
|
||||
buildThinkingSystemPrefix,
|
||||
KIRO_AGENTIC_SYSTEM_PROMPT,
|
||||
@@ -524,10 +525,12 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
|
||||
const temperature = body.temperature;
|
||||
const topP = body.top_p;
|
||||
|
||||
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
|
||||
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
|
||||
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
|
||||
const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
|
||||
const modelIntent = resolveKiroModelIntent(model);
|
||||
const { upstream: upstreamModel, agentic } = modelIntent;
|
||||
const thinkingBody = applyKiroThinkingOverride(body, modelIntent.thinkingOverride);
|
||||
const thinkingBudget = resolveKiroThinkingBudget(thinkingBody, credentials?.rawHeaders, modelIntent.model);
|
||||
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(thinkingBody, upstreamModel);
|
||||
const usesNativeGptEffort = usesKiroNativeGptEffort(thinkingBody, upstreamModel);
|
||||
|
||||
const { history, currentMessage } = convertMessages(messages, tools, upstreamModel);
|
||||
|
||||
|
||||
@@ -98,6 +98,18 @@ describe("Claude → Kiro (direct route)", () => {
|
||||
expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>");
|
||||
});
|
||||
|
||||
it("normalizes an unsupported Kiro intensity suffix while preserving agentic behavior", () => {
|
||||
const out = C2K(
|
||||
{ messages: [{ role: "user", content: "hello" }] },
|
||||
null,
|
||||
"claude-sonnet-4.5-thinking-agentic(high)",
|
||||
);
|
||||
|
||||
expect(out.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-4.5");
|
||||
expect(out.additionalModelRequestFields).toBeUndefined();
|
||||
expect(out.systemPrompt).toContain("CHUNKED WRITE PROTOCOL");
|
||||
});
|
||||
|
||||
it("maps output_config.effort high to Kiro CLI-style additionalModelRequestFields for effort models", () => {
|
||||
const out = C2K({
|
||||
output_config: { effort: "high" },
|
||||
|
||||
@@ -424,6 +424,31 @@ describe("openaiToKiroRequest", () => {
|
||||
expect(result.additionalModelRequestFields).toBeUndefined();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["claude-sonnet-4.5-thinking-agentic(high)", "claude-sonnet-4.5"],
|
||||
["glm-5-thinking-agentic(medium)", "glm-5"],
|
||||
])("normalizes unsupported Kiro intensity suffix for %s", (model, upstream) => {
|
||||
const result = openaiToKiroRequest(model, {
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
}, true, {});
|
||||
|
||||
expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe(upstream);
|
||||
expect(result.additionalModelRequestFields).toBeUndefined();
|
||||
expect(systemPromptOf(result)).toContain("CHUNKED WRITE PROTOCOL");
|
||||
});
|
||||
|
||||
it("maps a supported Kiro Claude intensity suffix to native effort fields", () => {
|
||||
const result = openaiToKiroRequest("claude-sonnet-5-thinking-agentic(high)", {
|
||||
messages: [{ role: "user", content: "hello" }],
|
||||
}, true, {});
|
||||
|
||||
expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-5");
|
||||
expect(result.additionalModelRequestFields).toEqual({
|
||||
thinking: { type: "adaptive", display: "summarized" },
|
||||
output_config: { effort: "high" },
|
||||
});
|
||||
});
|
||||
|
||||
it("does not send additionalModelRequestFields for date-suffixed Claude 4 model ids", () => {
|
||||
const body = {
|
||||
reasoning_effort: "high",
|
||||
|
||||
14
tests/unit/thinking-levels-kiro.test.js
Normal file
14
tests/unit/thinking-levels-kiro.test.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
|
||||
|
||||
describe("getThinkingLevels for Kiro", () => {
|
||||
it("does not advertise native intensity for legacy Kiro models", () => {
|
||||
expect(getThinkingLevels("kiro", "claude-sonnet-4.5")).toBeNull();
|
||||
expect(getThinkingLevels("kiro", "glm-5")).toBeNull();
|
||||
});
|
||||
|
||||
it("advertises native levels for supported Kiro models", () => {
|
||||
expect(getThinkingLevels("kiro", "claude-sonnet-5")).toContain("high");
|
||||
expect(getThinkingLevels("kiro", "gpt-5.6-sol")).toContain("xhigh");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user