fix(kiro): map GPT-5.6 reasoning effort fields

Route GPT-5.6 reasoning effort through Kiro's native reasoning.effort field instead of the legacy Claude output_config.effort path. GPT-5.6 models now emit reasoning.effort for low/medium/high/xhigh, with max mapped to the xhigh wire value.

Preserve the Responses API reasoning.effort through the OpenAI intermediate by copying it to reasoning_effort before the field is dropped. Skip legacy thinking_mode prompt tags when a supported native GPT effort is emitted, while keeping the legacy fallback for unsupported values (auto/minimal/ultra) and explicit disable semantics (none/off/disabled). Claude adaptive effort continues to use thinking plus output_config.effort.
This commit is contained in:
Edison42
2026-07-20 11:11:37 +07:00
parent d587b2a487
commit cef5dd4d61
7 changed files with 193 additions and 35 deletions
+26 -3
View File
@@ -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 `<thinking_mode>enabled</thinking_mode>` system-prompt injection
* that turns Kiro reasoning on
* - schema-specific native effort fields for supported GPT and Claude models
* - legacy `<thinking_mode>` 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,8 +145,25 @@ export function extractKiroEffortLevel(body) {
return null;
}
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 = extractKiroEffortLevel(body);
const effort = effortPath === "reasoning"
? extractKiroGptEffortLevel(body)
: extractKiroEffortLevel(body);
if (!effort) return undefined;
if (effortPath === "reasoning") {
// Mirrors Kiro CLI/KAS buildEffortRequestFields("reasoning") for GPT.
@@ -183,6 +201,11 @@ 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) {
const effortPath = resolveKiroEffortPath(model);
if (!effortPath) return undefined;
@@ -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;
}
@@ -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;
@@ -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";
@@ -513,8 +514,8 @@ function convertMessages(messages, tools, model) {
*
* 2. Thinking / reasoning. Detection covers Anthropic-Beta header, Claude API
* `thinking`, OpenAI `reasoning_effort`, AMP/Cursor magic tags, and model
* name hints. Kiro's prompt tags remain for compatibility, while supported
* models also receive the same schema-specific effort fields as Kiro CLI.
* 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 || [];
@@ -525,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);
@@ -552,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) {
@@ -610,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;
}