diff --git a/open-sse/config/kiroConstants.js b/open-sse/config/kiroConstants.js index 93b8f4569e..120b5e38df 100644 --- a/open-sse/config/kiroConstants.js +++ b/open-sse/config/kiroConstants.js @@ -144,9 +144,13 @@ export function extractKiroEffortLevel(body) { return null; } -export function buildKiroAdditionalModelRequestFields(body) { +export function buildKiroAdditionalModelRequestFields(body, effortPath = "output_config") { const effort = extractKiroEffortLevel(body); if (!effort) return undefined; + if (effortPath === "reasoning") { + // Mirrors Kiro CLI/KAS buildEffortRequestFields("reasoning") for GPT. + return { reasoning: { effort } }; + } // Mirrors Kiro CLI/KAS buildEffortRequestFields("output_config"). return { thinking: { type: "adaptive", display: "summarized" }, @@ -154,12 +158,15 @@ export function buildKiroAdditionalModelRequestFields(body) { }; } -export function supportsKiroAdditionalModelRequestFields(model) { - if (typeof model !== "string") return false; +export function resolveKiroEffortPath(model) { + if (typeof model !== "string") return null; const normalized = model.toLowerCase().replace(/-/g, "."); - if (!normalized.includes("claude")) return false; + if (/(?:^|[/.])gpt[/.]5[/.]6(?:[/.]|$)/.test(normalized)) { + return "reasoning"; + } + if (!normalized.includes("claude")) return null; const match = normalized.match(/(?:^|[/.])claude(?:[/.][a-z]+)*[/.](\d+)(?:[/.](\d+))?(?:[/.]|$)/); - if (!match) return false; + if (!match) return null; const [, majorText, minorText] = match; const major = Number(majorText); const minor = minorText === undefined ? null : Number(minorText); @@ -167,12 +174,19 @@ export function supportsKiroAdditionalModelRequestFields(model) { // Kiro rejected additionalModelRequestFields on legacy 4.5 models in live smoke. // Default future Claude/Kiro models to supported so new model releases do not // need a code allowlist update. - return !(major < 4 || (major === 4 && (minor === null || minor <= 5 || dateSuffixMinor))); + return major < 4 || (major === 4 && (minor === null || minor <= 5 || dateSuffixMinor)) + ? null + : "output_config"; +} + +export function supportsKiroAdditionalModelRequestFields(model) { + return resolveKiroEffortPath(model) !== null; } export function buildKiroAdditionalModelRequestFieldsForModel(body, model) { - if (!supportsKiroAdditionalModelRequestFields(model)) return undefined; - return buildKiroAdditionalModelRequestFields(body); + const effortPath = resolveKiroEffortPath(model); + if (!effortPath) return undefined; + return buildKiroAdditionalModelRequestFields(body, effortPath); } /** diff --git a/open-sse/translator/request/openai-to-kiro.js b/open-sse/translator/request/openai-to-kiro.js index a2681b5a7d..b49185374b 100644 --- a/open-sse/translator/request/openai-to-kiro.js +++ b/open-sse/translator/request/openai-to-kiro.js @@ -511,12 +511,10 @@ function convertMessages(messages, tools, model) { * Kiro's 2-3 minute server timeout. The suffix is stripped before being * sent upstream. * - * 2. Thinking / reasoning. Kiro does not accept `thinking.type` or - * `reasoning_effort` natively. The only way to enable reasoning is to - * inject `enabled` into the user content - * sent upstream. Detection covers Anthropic-Beta header, Claude API + * 2. Thinking / reasoning. Detection covers Anthropic-Beta header, Claude API * `thinking`, OpenAI `reasoning_effort`, AMP/Cursor magic tags, and model - * name hints. + * name hints. Kiro's prompt tags remain for compatibility, while supported + * models also receive the same schema-specific effort fields as Kiro CLI. */ export function openaiToKiroRequest(model, body, stream, credentials) { const messages = body.messages || []; diff --git a/tests/translator/claude-kiro-direct.test.js b/tests/translator/claude-kiro-direct.test.js index 01e217092a..00d7745621 100644 --- a/tests/translator/claude-kiro-direct.test.js +++ b/tests/translator/claude-kiro-direct.test.js @@ -112,6 +112,17 @@ describe("Claude → Kiro (direct route)", () => { expect(out.systemPrompt).toContain("24576"); }); + it("maps Claude-format effort to GPT-5.6 Kiro CLI reasoning fields", () => { + const out = C2K({ + output_config: { effort: "low" }, + messages: [{ role: "user", content: "think lightly" }], + }, null, "gpt-5.6-sol"); + + expect(out.additionalModelRequestFields).toEqual({ + reasoning: { effort: "low" }, + }); + }); + it("sends Claude system as top-level systemPrompt and keeps a user-content fallback", () => { const out = C2K({ system: "system-only instruction", diff --git a/tests/unit/openai-to-kiro.test.js b/tests/unit/openai-to-kiro.test.js index 8b1c97e868..13f67bef72 100644 --- a/tests/unit/openai-to-kiro.test.js +++ b/tests/unit/openai-to-kiro.test.js @@ -316,6 +316,44 @@ describe("openaiToKiroRequest", () => { }); }); + it("maps GPT-5.6 reasoning.effort high to Kiro CLI reasoning fields", () => { + const body = { + reasoning: { effort: "high" }, + messages: [{ role: "user", content: "Think deeply" }] + }; + + const result = openaiToKiroRequest("gpt-5.6-sol", body, true, {}); + + expect(systemPromptOf(result)).toContain("24576"); + expect(result.additionalModelRequestFields).toEqual({ + reasoning: { effort: "high" }, + }); + }); + + it("maps prefixed GPT-5.6 reasoning_effort medium to Kiro CLI reasoning fields", () => { + const body = { + reasoning_effort: "medium", + messages: [{ role: "user", content: "Think normally" }] + }; + + const result = openaiToKiroRequest("kiro/gpt-5.6-terra", body, true, {}); + + expect(result.additionalModelRequestFields).toEqual({ + reasoning: { effort: "medium" }, + }); + }); + + it("does not forward unsupported GPT-5.6 effort values", () => { + const body = { + reasoning: { effort: "ultra" }, + messages: [{ role: "user", content: "Unknown effort" }] + }; + + const result = openaiToKiroRequest("gpt-5.6-luna", body, true, {}); + + expect(result.additionalModelRequestFields).toBeUndefined(); + }); + it("does not send additionalModelRequestFields for legacy Kiro model ids", () => { const body = { reasoning_effort: "high",