fix(kiro): map GPT reasoning effort fields

GPT-5.6 via Kiro needs reasoning.effort while Claude uses output_config.effort.
Resolve the effort path per-model schema (like Kiro CLI/KAS) so GPT-5.6
receives the correct structured thinking level. Claude path unchanged.

- Add resolveKiroEffortPath returning "reasoning" | "output_config" | null
- buildKiroAdditionalModelRequestFields emits schema-specific shape
- Keep prompt tags for backward compatibility
- Add OpenAI/Claude translator coverage for GPT-5.6 effort mapping
This commit is contained in:
Edison42
2026-07-19 13:53:30 +07:00
committed by decolua
parent 43d4abbcf2
commit eb00222c4f
4 changed files with 74 additions and 13 deletions
+22 -8
View File
@@ -144,9 +144,13 @@ export function extractKiroEffortLevel(body) {
return null; return null;
} }
export function buildKiroAdditionalModelRequestFields(body) { export function buildKiroAdditionalModelRequestFields(body, effortPath = "output_config") {
const effort = extractKiroEffortLevel(body); const effort = extractKiroEffortLevel(body);
if (!effort) return undefined; 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"). // Mirrors Kiro CLI/KAS buildEffortRequestFields("output_config").
return { return {
thinking: { type: "adaptive", display: "summarized" }, thinking: { type: "adaptive", display: "summarized" },
@@ -154,12 +158,15 @@ export function buildKiroAdditionalModelRequestFields(body) {
}; };
} }
export function supportsKiroAdditionalModelRequestFields(model) { export function resolveKiroEffortPath(model) {
if (typeof model !== "string") return false; if (typeof model !== "string") return null;
const normalized = model.toLowerCase().replace(/-/g, "."); 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+))?(?:[/.]|$)/); const match = normalized.match(/(?:^|[/.])claude(?:[/.][a-z]+)*[/.](\d+)(?:[/.](\d+))?(?:[/.]|$)/);
if (!match) return false; if (!match) return null;
const [, majorText, minorText] = match; const [, majorText, minorText] = match;
const major = Number(majorText); const major = Number(majorText);
const minor = minorText === undefined ? null : Number(minorText); 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. // Kiro rejected additionalModelRequestFields on legacy 4.5 models in live smoke.
// Default future Claude/Kiro models to supported so new model releases do not // Default future Claude/Kiro models to supported so new model releases do not
// need a code allowlist update. // 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) { export function buildKiroAdditionalModelRequestFieldsForModel(body, model) {
if (!supportsKiroAdditionalModelRequestFields(model)) return undefined; const effortPath = resolveKiroEffortPath(model);
return buildKiroAdditionalModelRequestFields(body); if (!effortPath) return undefined;
return buildKiroAdditionalModelRequestFields(body, effortPath);
} }
/** /**
@@ -511,12 +511,10 @@ function convertMessages(messages, tools, model) {
* Kiro's 2-3 minute server timeout. The suffix is stripped before being * Kiro's 2-3 minute server timeout. The suffix is stripped before being
* sent upstream. * sent upstream.
* *
* 2. Thinking / reasoning. Kiro does not accept `thinking.type` or * 2. Thinking / reasoning. Detection covers Anthropic-Beta header, Claude API
* `reasoning_effort` natively. The only way to enable reasoning is to
* inject `<thinking_mode>enabled</thinking_mode>` into the user content
* sent upstream. Detection covers Anthropic-Beta header, Claude API
* `thinking`, OpenAI `reasoning_effort`, AMP/Cursor magic tags, and model * `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) { export function openaiToKiroRequest(model, body, stream, credentials) {
const messages = body.messages || []; const messages = body.messages || [];
@@ -112,6 +112,17 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>"); expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>");
}); });
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", () => { it("sends Claude system as top-level systemPrompt and keeps a user-content fallback", () => {
const out = C2K({ const out = C2K({
system: "system-only instruction", system: "system-only instruction",
+38
View File
@@ -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("<max_thinking_length>24576</max_thinking_length>");
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", () => { it("does not send additionalModelRequestFields for legacy Kiro model ids", () => {
const body = { const body = {
reasoning_effort: "high", reasoning_effort: "high",