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:33:25 +07:00
committed by decolua
parent 43d4abbcf2
commit eb00222c4f
4 changed files with 74 additions and 13 deletions

View File

@@ -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);
}
/**

View File

@@ -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 `<thinking_mode>enabled</thinking_mode>` 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 || [];

View File

@@ -112,6 +112,17 @@ describe("Claude → Kiro (direct route)", () => {
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", () => {
const out = C2K({
system: "system-only instruction",

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", () => {
const body = {
reasoning_effort: "high",