From 007d372724341fe1ced899fb3c110c60968fd046 Mon Sep 17 00:00:00 2001 From: ankit1324 Date: Thu, 23 Jul 2026 16:26:22 +0700 Subject: [PATCH] fix(kiro): normalize dashboard thinking intensity models MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- open-sse/config/kiroConstants.js | 35 ++++++++++++++++++- open-sse/providers/thinkingLevels.js | 2 ++ open-sse/translator/request/claude-to-kiro.js | 13 ++++--- open-sse/translator/request/openai-to-kiro.js | 13 ++++--- tests/translator/claude-kiro-direct.test.js | 12 +++++++ tests/unit/openai-to-kiro.test.js | 25 +++++++++++++ tests/unit/thinking-levels-kiro.test.js | 14 ++++++++ 7 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 tests/unit/thinking-levels-kiro.test.js diff --git a/open-sse/config/kiroConstants.js b/open-sse/config/kiroConstants.js index 511ae919..2f97256d 100644 --- a/open-sse/config/kiroConstants.js +++ b/open-sse/config/kiroConstants.js @@ -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) diff --git a/open-sse/providers/thinkingLevels.js b/open-sse/providers/thinkingLevels.js index ba998ee7..7b638700 100644 --- a/open-sse/providers/thinkingLevels.js +++ b/open-sse/providers/thinkingLevels.js @@ -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)); diff --git a/open-sse/translator/request/claude-to-kiro.js b/open-sse/translator/request/claude-to-kiro.js index 98531c73..51b9c7f8 100644 --- a/open-sse/translator/request/claude-to-kiro.js +++ b/open-sse/translator/request/claude-to-kiro.js @@ -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) { diff --git a/open-sse/translator/request/openai-to-kiro.js b/open-sse/translator/request/openai-to-kiro.js index 069feffa..798c827f 100644 --- a/open-sse/translator/request/openai-to-kiro.js +++ b/open-sse/translator/request/openai-to-kiro.js @@ -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); diff --git a/tests/translator/claude-kiro-direct.test.js b/tests/translator/claude-kiro-direct.test.js index 3c2d964b..3fca7be1 100644 --- a/tests/translator/claude-kiro-direct.test.js +++ b/tests/translator/claude-kiro-direct.test.js @@ -98,6 +98,18 @@ describe("Claude → Kiro (direct route)", () => { expect(out.systemPrompt).toContain("24576"); }); + 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" }, diff --git a/tests/unit/openai-to-kiro.test.js b/tests/unit/openai-to-kiro.test.js index dbaf7c85..17773bdb 100644 --- a/tests/unit/openai-to-kiro.test.js +++ b/tests/unit/openai-to-kiro.test.js @@ -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", diff --git a/tests/unit/thinking-levels-kiro.test.js b/tests/unit/thinking-levels-kiro.test.js new file mode 100644 index 00000000..edb3b7aa --- /dev/null +++ b/tests/unit/thinking-levels-kiro.test.js @@ -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"); + }); +});