diff --git a/open-sse/config/kiroConstants.js b/open-sse/config/kiroConstants.js
index 93b8f4569e..511ae91935 100644
--- a/open-sse/config/kiroConstants.js
+++ b/open-sse/config/kiroConstants.js
@@ -8,8 +8,8 @@
* - `-agentic` model suffix detection + chunked-write system prompt
* - reasoning / thinking trigger detection (Anthropic-Beta header,
* Claude `thinking`, OpenAI `reasoning_effort`, AMP/Cursor magic tag)
- * - the `enabled` system-prompt injection
- * that turns Kiro reasoning on
+ * - schema-specific native effort fields for supported GPT and Claude models
+ * - legacy `` system-prompt injection for other models
*
* Kiro upstream does not advertise `-agentic` model IDs; they are a 9router
* fiction. The suffix is stripped before the request leaves this process.
@@ -109,6 +109,7 @@ export function resolveKiroThinkingBudget(body, headers, model) {
const cfg = extractThinking(body);
if (cfg) {
if (cfg.mode === "none") return null;
+ if (cfg.mode === "level" && cfg.level === "disabled") return null;
if (cfg.mode === "budget") return cfg.budget;
if (cfg.mode === "level") return effortToBudget(cfg.level) ?? KIRO_THINKING_BUDGET_DEFAULT;
return KIRO_THINKING_BUDGET_DEFAULT;
@@ -144,9 +145,30 @@ export function extractKiroEffortLevel(body) {
return null;
}
-export function buildKiroAdditionalModelRequestFields(body) {
- const effort = extractKiroEffortLevel(body);
+function extractKiroGptEffortLevel(body) {
+ const effort =
+ body?.output_config?.effort ??
+ body?.reasoning_effort ??
+ (typeof body?.reasoning === "object" ? body.reasoning?.effort : null);
+ if (typeof effort !== "string") return null;
+ const normalized = effort.toLowerCase();
+ if (normalized === "max") return "xhigh";
+ // Kiro CLI does not advertise an explicit GPT "none" wire value; omit it.
+ if (["low", "medium", "high", "xhigh"].includes(normalized)) {
+ return normalized;
+ }
+ return null;
+}
+
+export function buildKiroAdditionalModelRequestFields(body, effortPath = "output_config") {
+ const effort = effortPath === "reasoning"
+ ? extractKiroGptEffortLevel(body)
+ : 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 +176,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 +192,24 @@ 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 usesKiroNativeGptEffort(body, model) {
+ return resolveKiroEffortPath(model) === "reasoning"
+ && extractKiroGptEffortLevel(body) !== 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/claude-to-kiro.js b/open-sse/translator/request/claude-to-kiro.js
index bcb8b97fe0..98531c736d 100644
--- a/open-sse/translator/request/claude-to-kiro.js
+++ b/open-sse/translator/request/claude-to-kiro.js
@@ -33,6 +33,7 @@ import {
KIRO_AGENTIC_SYSTEM_PROMPT,
resolveDefaultProfileArn,
buildKiroAdditionalModelRequestFieldsForModel,
+ usesKiroNativeGptEffort,
} from "../../config/kiroConstants.js";
import { DEFAULT_IMAGE_MIME } from "../schema/index.js";
import { ROLE, CLAUDE_BLOCK } from "../schema/index.js";
@@ -390,6 +391,8 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
+ const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
+ const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
// Guard 1: no client tools → flatten all tool interactions to text.
if (!clientProvidedTools) {
@@ -421,7 +424,9 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
// enforce top-level systemPrompt for direct calls.
const timestamp = new Date().toISOString();
const systemPromptParts = [];
- if (thinkingBudget !== null) systemPromptParts.push(buildThinkingSystemPrefix(thinkingBudget));
+ if (thinkingBudget !== null && !usesNativeGptEffort) {
+ systemPromptParts.push(buildThinkingSystemPrefix(thinkingBudget));
+ }
if (agentic) systemPromptParts.push(KIRO_AGENTIC_SYSTEM_PROMPT);
const systemInstruction = extractClaudeSystemText(body.system);
if (systemInstruction) systemPromptParts.push(systemInstruction);
@@ -481,7 +486,6 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
if (profileArn) payload.profileArn = profileArn;
if (systemPrompt) payload.systemPrompt = systemPrompt;
- const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
if (additionalModelRequestFields) {
payload.additionalModelRequestFields = additionalModelRequestFields;
}
diff --git a/open-sse/translator/request/openai-responses.js b/open-sse/translator/request/openai-responses.js
index 1fc058250a..64740c12cf 100644
--- a/open-sse/translator/request/openai-responses.js
+++ b/open-sse/translator/request/openai-responses.js
@@ -200,6 +200,9 @@ export function openaiResponsesToOpenAIRequest(model, body, stream, credentials)
delete result.include;
delete result.prompt_cache_key;
delete result.store;
+ if (typeof result.reasoning?.effort === "string") {
+ result.reasoning_effort = result.reasoning.effort;
+ }
delete result.reasoning;
delete result.client_metadata;
diff --git a/open-sse/translator/request/openai-to-kiro.js b/open-sse/translator/request/openai-to-kiro.js
index a2681b5a7d..069feffa9a 100644
--- a/open-sse/translator/request/openai-to-kiro.js
+++ b/open-sse/translator/request/openai-to-kiro.js
@@ -13,7 +13,8 @@ import {
buildThinkingSystemPrefix,
KIRO_AGENTIC_SYSTEM_PROMPT,
resolveDefaultProfileArn,
- buildKiroAdditionalModelRequestFieldsForModel
+ buildKiroAdditionalModelRequestFieldsForModel,
+ usesKiroNativeGptEffort
} from "../../config/kiroConstants.js";
import { parseDataUri } from "../concerns/image.js";
import { DEFAULT_IMAGE_MIME } from "../schema/index.js";
@@ -511,12 +512,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. Supported models receive Kiro's schema-specific effort fields;
+ * legacy prompt tags remain only for models that need them.
*/
export function openaiToKiroRequest(model, body, stream, credentials) {
const messages = body.messages || [];
@@ -527,6 +526,8 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
+ const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
+ const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
const { history, currentMessage } = convertMessages(messages, tools, upstreamModel);
@@ -554,7 +555,7 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
// too because the CodeWhisperer surface does not always enforce top-level
// systemPrompt for direct calls.
const systemPromptParts = [];
- if (thinkingBudget !== null) {
+ if (thinkingBudget !== null && !usesNativeGptEffort) {
systemPromptParts.push(buildThinkingSystemPrefix(thinkingBudget));
}
if (agentic) {
@@ -612,7 +613,6 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
payload.profileArn = profileArn;
}
if (systemPrompt) payload.systemPrompt = systemPrompt;
- const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
if (additionalModelRequestFields) {
payload.additionalModelRequestFields = additionalModelRequestFields;
}
diff --git a/tests/translator/bugs-kiro.test.js b/tests/translator/bugs-kiro.test.js
index 7f3677facc..c3f777d475 100644
--- a/tests/translator/bugs-kiro.test.js
+++ b/tests/translator/bugs-kiro.test.js
@@ -5,8 +5,34 @@ import { translateRequest } from "../../open-sse/translator/index.js";
import { FORMATS } from "../../open-sse/translator/formats.js";
const O2K = (body) => translateRequest(FORMATS.OPENAI, FORMATS.KIRO, "m", body, true, null, "kiro");
+const R2K = (model, body) => translateRequest(
+ FORMATS.OPENAI_RESPONSES,
+ FORMATS.KIRO,
+ model,
+ body,
+ true,
+ null,
+ "kiro"
+);
describe("OpenAI → Kiro", () => {
+ it.each([
+ ["high", "gpt-5.6-sol"],
+ ["medium", "gpt-5.6-terra"],
+ ["low", "gpt-5.6-luna"],
+ ])("preserves Responses reasoning.effort %s through the full Kiro route", (effort, model) => {
+ const out = R2K(model, {
+ input: "Use the requested effort",
+ reasoning: { effort },
+ });
+
+ expect(out.additionalModelRequestFields).toEqual({
+ reasoning: { effort },
+ });
+ expect(out.systemPrompt || "").not.toContain("");
+ expect(out.systemPrompt || "").not.toContain("");
+ });
+
// openai-to-kiro.js — safeJSONParse guards bad tool-call JSON (fixed in PR #1582)
it("malformed tool arguments do not throw the whole request", () => {
expect(() =>
diff --git a/tests/translator/claude-kiro-direct.test.js b/tests/translator/claude-kiro-direct.test.js
index 01e217092a..3c2d964b33 100644
--- a/tests/translator/claude-kiro-direct.test.js
+++ b/tests/translator/claude-kiro-direct.test.js
@@ -112,6 +112,59 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("24576");
});
+ it("maps Claude-format effort to GPT-5.6 reasoning fields without legacy prompt tags", () => {
+ 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" },
+ });
+ expect(out.systemPrompt || "").not.toContain("");
+ expect(out.systemPrompt || "").not.toContain("");
+ });
+
+ it.each(["auto", "minimal", "ultra"])(
+ "keeps the legacy thinking fallback for unsupported GPT-5.6 effort %s",
+ (effort) => {
+ const out = C2K({
+ output_config: { effort },
+ messages: [{ role: "user", content: "Use legacy thinking" }],
+ }, null, "gpt-5.6-sol");
+
+ expect(out.additionalModelRequestFields).toBeUndefined();
+ expect(out.systemPrompt).toContain("enabled");
+ expect(out.systemPrompt).toContain("");
+ }
+ );
+
+ it.each(["none", "off", "disabled"])(
+ "keeps GPT-5.6 reasoning intentionally disabled for effort %s",
+ (effort) => {
+ const out = C2K({
+ output_config: { effort },
+ messages: [{ role: "user", content: "Do not reason" }],
+ }, null, "gpt-5.6-sol");
+
+ expect(out.additionalModelRequestFields).toBeUndefined();
+ expect(out.systemPrompt || "").not.toContain("");
+ expect(out.systemPrompt || "").not.toContain("");
+ }
+ );
+
+ it("keeps explicit Claude effort ahead of an injected OpenAI effort", () => {
+ const out = C2K({
+ output_config: { effort: "low" },
+ reasoning_effort: "high",
+ messages: [{ role: "user", content: "honor the client effort" }],
+ }, 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..dbaf7c85c7 100644
--- a/tests/unit/openai-to-kiro.test.js
+++ b/tests/unit/openai-to-kiro.test.js
@@ -316,6 +316,102 @@ describe("openaiToKiroRequest", () => {
});
});
+ it.each([
+ ["high", "gpt-5.6-sol"],
+ ["medium", "kiro/gpt-5.6-terra"],
+ ["low", "gpt-5.6-luna"],
+ ])("maps GPT-5.6 reasoning.effort %s without legacy prompt tags", (effort, model) => {
+ const body = {
+ reasoning: { effort },
+ messages: [{ role: "user", content: "Use the requested effort" }]
+ };
+
+ const result = openaiToKiroRequest(model, body, true, {});
+
+ expect(result.additionalModelRequestFields).toEqual({
+ reasoning: { effort },
+ });
+ expect(systemPromptOf(result)).not.toContain("");
+ expect(systemPromptOf(result)).not.toContain("");
+ expect(contentOf(result)).not.toContain("");
+ expect(contentOf(result)).not.toContain("");
+ });
+
+ it.each([
+ ["xhigh", "gpt-5.6-terra", "xhigh"],
+ ["max", "gpt-5.6-sol", "xhigh"],
+ ])("preserves GPT-5.6 effort %s as supported wire effort %s", (effort, model, wireEffort) => {
+ const body = {
+ reasoning: { effort },
+ messages: [{ role: "user", content: "Use extended effort" }]
+ };
+
+ const result = openaiToKiroRequest(model, body, true, {});
+
+ expect(result.additionalModelRequestFields).toEqual({
+ reasoning: { effort: wireEffort },
+ });
+ expect(systemPromptOf(result)).not.toContain("");
+ expect(systemPromptOf(result)).not.toContain("");
+ });
+
+ it("omits GPT-5.6 effort fields and legacy prompt tags when effort is absent", () => {
+ const body = {
+ messages: [{ role: "user", content: "No explicit reasoning effort" }]
+ };
+
+ const result = openaiToKiroRequest("gpt-5.6-sol", body, true, {});
+
+ expect(result.additionalModelRequestFields).toBeUndefined();
+ expect(systemPromptOf(result)).not.toContain("");
+ expect(systemPromptOf(result)).not.toContain("");
+ });
+
+ it.each(["auto", "minimal", "ultra"])(
+ "keeps the legacy thinking fallback for unsupported GPT-5.6 effort %s",
+ (effort) => {
+ const body = {
+ reasoning: { effort },
+ messages: [{ role: "user", content: "Use legacy thinking" }]
+ };
+
+ const result = openaiToKiroRequest("gpt-5.6-luna", body, true, {});
+
+ expect(result.additionalModelRequestFields).toBeUndefined();
+ expect(systemPromptOf(result)).toContain("enabled");
+ expect(systemPromptOf(result)).toContain("");
+ }
+ );
+
+ it.each(["none", "off", "disabled"])(
+ "keeps GPT-5.6 reasoning intentionally disabled for effort %s",
+ (effort) => {
+ const body = {
+ reasoning: { effort },
+ messages: [{ role: "user", content: "Do not reason" }]
+ };
+
+ const result = openaiToKiroRequest("gpt-5.6-luna", body, true, {});
+
+ expect(result.additionalModelRequestFields).toBeUndefined();
+ expect(systemPromptOf(result)).not.toContain("");
+ expect(systemPromptOf(result)).not.toContain("");
+ }
+ );
+
+ it("keeps the thinking-alias fallback when GPT effort is blank", () => {
+ const body = {
+ reasoning: { effort: "" },
+ messages: [{ role: "user", content: "Use the thinking alias" }]
+ };
+
+ const result = openaiToKiroRequest("gpt-5.6-sol-thinking", body, true, {});
+
+ expect(result.additionalModelRequestFields).toBeUndefined();
+ expect(systemPromptOf(result)).toContain("enabled");
+ expect(systemPromptOf(result)).toContain("");
+ });
+
it("does not send additionalModelRequestFields for legacy Kiro model ids", () => {
const body = {
reasoning_effort: "high",