fix(kiro): never send a top-level systemPrompt (400 REQUEST_BODY_INVALID)

kiro.dev rejects any body carrying a top-level systemPrompt with
400 REQUEST_BODY_INVALID. The translators stopped emitting the field in
v0.5.59 (the prompt travels in the first user turn via contentPrefix),
but two paths kept writing it back downstream of the translator:

- rtk/systemInject.js::injectKiroSystem() appended the RTK prompt to
  body.systemPrompt, so every kr/ model failed whenever an RTK injector
  (caveman, ponytail) was active. It now appends to the first history
  user turn's content (else currentMessage), reusing
  dedupStringAppend/hasPrompt so retries stay idempotent.
- executors/kiro.js::appendRepairInstruction() wrote the tool-call repair
  instruction to systemPrompt on the retry, turning every repair into a
  hard failure. It now appends to currentMessage.userInputMessage.content.

isKiroBody() no longer requires a string body.systemPrompt — that marker
is gone from the wire shape — and sniffs the conversation turn shape
instead, keeping the stray-conversationState guard intact. Stale comments
in both kiro translators corrected: the systemPrompt local is only a
session-replay cache key, not a wire field.

Also drops the mirror/rollback repair heuristic the injector no longer
needs: net -52 lines.

Fixes #3641, #3845, #2890, #2901, #2939, #3109, #3459, #3749
This commit is contained in:
Federico Liva
2026-09-10 22:23:01 +07:00
committed by decolua
parent 1f10f9e5c4
commit 1892ed77c8
6 changed files with 109 additions and 161 deletions

View File

@@ -242,9 +242,9 @@ export function claudeToKiroRequest(model, body, stream, credentials) {
? (credentials?.providerSpecificData?.profileArn || "")
: (credentials?.providerSpecificData?.profileArn || resolveDefaultProfileArn(authMethod));
// Kiro CLI/KAS sends system prompt as top-level `systemPrompt`. Keep a
// content fallback too because the CodeWhisperer surface does not always
// enforce top-level systemPrompt for direct calls.
// The system prompt travels inside the first user turn's content (contentPrefix):
// the CodeWhisperer surface rejects a top-level `systemPrompt` with
// 400 REQUEST_BODY_INVALID, so the value below is only a replay cache key.
const timestamp = new Date().toISOString();
const systemPromptParts = [];
if (thinkingBudget !== null && !usesNativeGptEffort) {

View File

@@ -340,9 +340,9 @@ export function openaiToKiroRequest(model, body, stream, credentials) {
const timestamp = new Date().toISOString();
// Kiro CLI/KAS sends these as top-level systemPrompt. Keep a content fallback
// too because the CodeWhisperer surface does not always enforce top-level
// systemPrompt for direct calls.
// The system prompt travels inside the first user turn's content (contentPrefix):
// the CodeWhisperer surface rejects a top-level `systemPrompt` with
// 400 REQUEST_BODY_INVALID, so the value below is only a replay cache key.
const systemPromptParts = [];
if (thinkingBudget !== null && !usesNativeGptEffort) {
systemPromptParts.push(buildThinkingSystemPrefix(thinkingBudget));