fix(kiro): normalize dashboard thinking intensity models

Strip the generic dashboard model(level) suffix before resolving Kiro
synthetic -thinking/-agentic variants so the upstream request no longer
carries an invalid parenthesized model id. Map explicit levels to native
Kiro effort fields only for supported Claude/GPT model families, and stop
advertising native levels for unsupported legacy Kiro models. Applies to
both OpenAI→Kiro and direct Claude→Kiro routes.
This commit is contained in:
ankit1324
2026-07-23 16:26:22 +07:00
committed by decolua
parent e45bd73d6e
commit 007d372724
7 changed files with 103 additions and 11 deletions

View File

@@ -15,7 +15,7 @@
* fiction. The suffix is stripped before the request leaves this process.
*/
import { extractThinking } from "../translator/concerns/thinkingUnified.js";
import { extractThinking, parseSuffix } from "../translator/concerns/thinkingUnified.js";
import { effortToBudget } from "../translator/concerns/thinking.js";
export const KIRO_AGENTIC_SUFFIX = "-agentic";
@@ -40,6 +40,39 @@ export function resolveDefaultProfileArn(authMethod) {
export const KIRO_THINKING_BUDGET_DEFAULT = 16000;
/**
* Resolve a Kiro model after consuming the generic model(level) suffix.
* The suffix is a 9router request override, not part of Kiro's upstream model id.
*/
export function resolveKiroModelIntent(model) {
const { cleanModel, override } = parseSuffix(model);
return {
model: cleanModel,
...resolveKiroModel(cleanModel),
thinkingOverride: override,
};
}
/** Apply a parsed model(level) override without mutating the caller's body. */
export function applyKiroThinkingOverride(body, override) {
if (!override) return body;
const next = { ...body };
if (override.mode === "budget") {
delete next.output_config;
delete next.reasoning_effort;
delete next.reasoning;
next.thinking = { type: "enabled", budget_tokens: override.budget };
return next;
}
next.output_config = {
...(body.output_config || {}),
effort: override.mode === "level" ? override.level : override.mode,
};
return next;
}
export const KIRO_AGENTIC_SYSTEM_PROMPT = `
# CRITICAL: CHUNKED WRITE PROTOCOL (MANDATORY)

View File

@@ -2,6 +2,7 @@
// Reuses capabilities.js (thinkingFormat/canDisable) so this file only maps format→levels (DRY).
import { getCapabilitiesForModel } from "./capabilities.js";
import { matchPattern } from "./pricing.js";
import { resolveKiroEffortPath } from "../config/kiroConstants.js";
// Shared level sets (deduped) — verified against provider docs + wire in thinkingUnified.applyFormat.
const L = {
@@ -39,6 +40,7 @@ const PATTERN_THINKING = [
// Returns valid thinking levels for a model, or null when the model has no reasoning.
export function getThinkingLevels(provider, model) {
if (provider === "kiro" && resolveKiroEffortPath(model) === null) return null;
const caps = getCapabilitiesForModel(provider, model);
if (!caps.reasoning) return null;
const hit = PATTERN_THINKING.find((p) => matchPattern(p.pattern, model));

View File

@@ -27,7 +27,8 @@ import { FORMATS } from "../formats.js";
import { applyKiroSessionReplay } from "../../utils/kiroSessionReplay.js";
import { resolveContinuationId, resolveSessionIdentity } from "../../utils/sessionManager.js";
import {
resolveKiroModel,
resolveKiroModelIntent,
applyKiroThinkingOverride,
resolveKiroThinkingBudget,
buildThinkingSystemPrefix,
KIRO_AGENTIC_SYSTEM_PROMPT,
@@ -389,10 +390,12 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
const temperature = body.temperature;
const topP = body.top_p;
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
const modelIntent = resolveKiroModelIntent(model);
const { upstream: upstreamModel, agentic } = modelIntent;
const thinkingBody = applyKiroThinkingOverride(body, modelIntent.thinkingOverride);
const thinkingBudget = resolveKiroThinkingBudget(thinkingBody, credentials?.rawHeaders, modelIntent.model);
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(thinkingBody, upstreamModel);
const usesNativeGptEffort = usesKiroNativeGptEffort(thinkingBody, upstreamModel);
// Guard 1: no client tools → flatten all tool interactions to text.
if (!clientProvidedTools) {

View File

@@ -8,7 +8,8 @@ import { v4 as uuidv4 } from "uuid";
import { applyKiroSessionReplay } from "../../utils/kiroSessionReplay.js";
import { resolveContinuationId, resolveSessionIdentity } from "../../utils/sessionManager.js";
import {
resolveKiroModel,
resolveKiroModelIntent,
applyKiroThinkingOverride,
resolveKiroThinkingBudget,
buildThinkingSystemPrefix,
KIRO_AGENTIC_SYSTEM_PROMPT,
@@ -524,10 +525,12 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
const temperature = body.temperature;
const topP = body.top_p;
const { upstream: upstreamModel, agentic } = resolveKiroModel(model);
const thinkingBudget = resolveKiroThinkingBudget(body, credentials?.rawHeaders, model);
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(body, upstreamModel);
const usesNativeGptEffort = usesKiroNativeGptEffort(body, upstreamModel);
const modelIntent = resolveKiroModelIntent(model);
const { upstream: upstreamModel, agentic } = modelIntent;
const thinkingBody = applyKiroThinkingOverride(body, modelIntent.thinkingOverride);
const thinkingBudget = resolveKiroThinkingBudget(thinkingBody, credentials?.rawHeaders, modelIntent.model);
const additionalModelRequestFields = buildKiroAdditionalModelRequestFieldsForModel(thinkingBody, upstreamModel);
const usesNativeGptEffort = usesKiroNativeGptEffort(thinkingBody, upstreamModel);
const { history, currentMessage } = convertMessages(messages, tools, upstreamModel);

View File

@@ -98,6 +98,18 @@ describe("Claude → Kiro (direct route)", () => {
expect(out.systemPrompt).toContain("<max_thinking_length>24576</max_thinking_length>");
});
it("normalizes an unsupported Kiro intensity suffix while preserving agentic behavior", () => {
const out = C2K(
{ messages: [{ role: "user", content: "hello" }] },
null,
"claude-sonnet-4.5-thinking-agentic(high)",
);
expect(out.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-4.5");
expect(out.additionalModelRequestFields).toBeUndefined();
expect(out.systemPrompt).toContain("CHUNKED WRITE PROTOCOL");
});
it("maps output_config.effort high to Kiro CLI-style additionalModelRequestFields for effort models", () => {
const out = C2K({
output_config: { effort: "high" },

View File

@@ -424,6 +424,31 @@ describe("openaiToKiroRequest", () => {
expect(result.additionalModelRequestFields).toBeUndefined();
});
it.each([
["claude-sonnet-4.5-thinking-agentic(high)", "claude-sonnet-4.5"],
["glm-5-thinking-agentic(medium)", "glm-5"],
])("normalizes unsupported Kiro intensity suffix for %s", (model, upstream) => {
const result = openaiToKiroRequest(model, {
messages: [{ role: "user", content: "hello" }],
}, true, {});
expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe(upstream);
expect(result.additionalModelRequestFields).toBeUndefined();
expect(systemPromptOf(result)).toContain("CHUNKED WRITE PROTOCOL");
});
it("maps a supported Kiro Claude intensity suffix to native effort fields", () => {
const result = openaiToKiroRequest("claude-sonnet-5-thinking-agentic(high)", {
messages: [{ role: "user", content: "hello" }],
}, true, {});
expect(result.conversationState.currentMessage.userInputMessage.modelId).toBe("claude-sonnet-5");
expect(result.additionalModelRequestFields).toEqual({
thinking: { type: "adaptive", display: "summarized" },
output_config: { effort: "high" },
});
});
it("does not send additionalModelRequestFields for date-suffixed Claude 4 model ids", () => {
const body = {
reasoning_effort: "high",

View File

@@ -0,0 +1,14 @@
import { describe, it, expect } from "vitest";
import { getThinkingLevels } from "../../open-sse/providers/thinkingLevels.js";
describe("getThinkingLevels for Kiro", () => {
it("does not advertise native intensity for legacy Kiro models", () => {
expect(getThinkingLevels("kiro", "claude-sonnet-4.5")).toBeNull();
expect(getThinkingLevels("kiro", "glm-5")).toBeNull();
});
it("advertises native levels for supported Kiro models", () => {
expect(getThinkingLevels("kiro", "claude-sonnet-5")).toContain("high");
expect(getThinkingLevels("kiro", "gpt-5.6-sol")).toContain("xhigh");
});
});