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:
@@ -127,12 +127,18 @@ async function readResponsePrefix(response, signal, maxBytes, timeoutMs) {
|
||||
return decoder.decode(concatChunks(chunks, totalBytes));
|
||||
}
|
||||
|
||||
// The instruction goes into the current user turn, never into a top-level
|
||||
// `systemPrompt`: kiro.dev answers any body carrying that field with
|
||||
// 400 REQUEST_BODY_INVALID, so writing it here turned every repair retry into
|
||||
// a hard failure.
|
||||
function appendRepairInstruction(body, kind) {
|
||||
const repaired = structuredClone(body || {});
|
||||
const instruction = REPAIR_INSTRUCTIONS[kind] || "Retry the previous incomplete Kiro response.";
|
||||
repaired.systemPrompt = repaired.systemPrompt
|
||||
? `${repaired.systemPrompt}\n\n${instruction}`
|
||||
: instruction;
|
||||
const msg = repaired?.conversationState?.currentMessage?.userInputMessage;
|
||||
if (msg) {
|
||||
const content = typeof msg.content === "string" ? msg.content : "";
|
||||
msg.content = content ? `${content}\n\n${instruction}` : instruction;
|
||||
}
|
||||
return repaired;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export function injectSystemPrompt(body, format, prompt) {
|
||||
if (!body || !prompt) return;
|
||||
if (typeof body !== "object") return;
|
||||
|
||||
// Kiro wire shape is unique (conversationState/systemPrompt) — handle directly.
|
||||
// Kiro wire shape is unique (conversationState) — handle directly.
|
||||
if (isKiroBody(body) || format === FORMATS.KIRO) {
|
||||
injectKiroSystem(body, prompt);
|
||||
return;
|
||||
@@ -61,10 +61,13 @@ export function injectSystemPrompt(body, format, prompt) {
|
||||
|
||||
function isKiroBody(body) {
|
||||
if (!body || typeof body !== "object") return false;
|
||||
if (typeof body.systemPrompt !== "string") return false;
|
||||
const cs = body.conversationState;
|
||||
if (!cs || typeof cs !== "object") return false;
|
||||
return Array.isArray(cs.history) || !!(cs.currentMessage && typeof cs.currentMessage === "object");
|
||||
// A top-level `systemPrompt` used to be the marker, but the Kiro translator no
|
||||
// longer emits it (kiro.dev rejects the field), so gate on the turn shape.
|
||||
const historyTurn = Array.isArray(cs.history)
|
||||
&& cs.history.some(it => it && (it.userInputMessage || it.assistantResponseMessage));
|
||||
return historyTurn || !!(cs.currentMessage && cs.currentMessage.userInputMessage);
|
||||
}
|
||||
|
||||
// Exact idempotency: prompt present as its own SEP-delimited segment (or the
|
||||
@@ -258,80 +261,33 @@ function injectGeminiSystem(body, prompt) {
|
||||
}
|
||||
|
||||
// ---- Kiro ----
|
||||
// Updates top-level systemPrompt and only the mirrored leading prefix of the
|
||||
// first user history turn, else current user. next = old + SEP + prompt.
|
||||
// Replace old leading prefix only; preserve time context and user tail.
|
||||
// The prompt is appended to the first user turn's content — the same place the
|
||||
// Kiro translator already mirrors the system text via its contentPrefix.
|
||||
//
|
||||
// A top-level `systemPrompt` is deliberately NOT written: the kiro.dev gateway
|
||||
// answers any body carrying that field with
|
||||
// 400 {"message":"Improperly formed request.","reason":"REQUEST_BODY_INVALID"}
|
||||
// The translator stopped emitting it in v0.5.59, but this injector kept adding
|
||||
// it back, so every kr/ model failed whenever an RTK prompt (caveman, ponytail)
|
||||
// was active.
|
||||
function injectKiroSystem(body, prompt) {
|
||||
try {
|
||||
let oldPrompt = typeof body.systemPrompt === "string" ? body.systemPrompt : "";
|
||||
// Repair path: a previous partial write left systemPrompt updated but user
|
||||
// content still mirroring the pre-write prefix. Re-derive the effective old
|
||||
// prefix from content so this pass converges instead of early-returning.
|
||||
const cs0 = body.conversationState;
|
||||
let firstUser0 = cs0 && Array.isArray(cs0.history)
|
||||
? (cs0.history.find(it => it && it.userInputMessage)?.userInputMessage ?? null)
|
||||
: null;
|
||||
if (!firstUser0 && cs0?.currentMessage?.userInputMessage) firstUser0 = cs0.currentMessage.userInputMessage;
|
||||
|
||||
if (firstUser0 && typeof firstUser0.content === "string" && oldPrompt && !hasPrompt(oldPrompt, prompt)) {
|
||||
const c0 = firstUser0.content;
|
||||
if (c0 === oldPrompt || (c0.startsWith(oldPrompt) && !c0.startsWith(`${oldPrompt}${SEP}`))) {
|
||||
// systemPrompt advanced past mirrored prefix → stale; treat as un-mirrored
|
||||
oldPrompt = "";
|
||||
}
|
||||
}
|
||||
if (oldPrompt && hasPrompt(oldPrompt, prompt)) return;
|
||||
const next = oldPrompt ? `${oldPrompt}${SEP}${prompt}` : prompt;
|
||||
|
||||
// Atomicity: write user content first, then systemPrompt only if content
|
||||
// write succeeded (or was a no-op). If systemPrompt write then fails, the
|
||||
// repair heuristic above re-derives from content on retry — no permanent
|
||||
// half-applied state.
|
||||
const cs = body.conversationState;
|
||||
let targetMsg = null;
|
||||
try {
|
||||
const hist = Array.isArray(cs?.history) ? cs.history : null;
|
||||
if (hist) {
|
||||
for (const item of hist) {
|
||||
if (item && item.userInputMessage) { targetMsg = item.userInputMessage; break; }
|
||||
}
|
||||
}
|
||||
if (!targetMsg && cs?.currentMessage?.userInputMessage) {
|
||||
targetMsg = cs.currentMessage.userInputMessage;
|
||||
}
|
||||
} catch (_) { targetMsg = null; }
|
||||
|
||||
let sysWritten = false;
|
||||
try { body.systemPrompt = next; sysWritten = true; } catch (_) {}
|
||||
|
||||
const applyContent = () => {
|
||||
const content = typeof targetMsg.content === "string" ? targetMsg.content : "";
|
||||
if (oldPrompt === "") {
|
||||
// Empty old prompt: prepend unless already at head (exact, not substring)
|
||||
if (content.startsWith(prompt) || content.startsWith(next)) return;
|
||||
const newContent = content ? `${next}${SEP}${content}` : next;
|
||||
try { targetMsg.content = newContent; } catch (_) {}
|
||||
return;
|
||||
}
|
||||
if (!content.startsWith(oldPrompt)) return; // not mirrored at head — leave alone
|
||||
if (content.startsWith(next)) return; // already applied → idempotent
|
||||
const tail = content.slice(oldPrompt.length);
|
||||
try { targetMsg.content = `${next}${tail}`; } catch (_) {}
|
||||
};
|
||||
|
||||
try {
|
||||
if (targetMsg) applyContent();
|
||||
} catch (_) {}
|
||||
if (sysWritten && targetMsg) {
|
||||
// verify convergence: content should now start with next (or be un-mirrored)
|
||||
let ok = false;
|
||||
try {
|
||||
const c = targetMsg.content;
|
||||
ok = typeof c !== "string" || c.startsWith(next) || !c.startsWith(oldPrompt);
|
||||
} catch (_) {}
|
||||
if (!ok) {
|
||||
try { body.systemPrompt = oldPrompt; } catch (_) {} // rollback
|
||||
const hist = Array.isArray(cs?.history) ? cs.history : null;
|
||||
if (hist) {
|
||||
for (const item of hist) {
|
||||
if (item && item.userInputMessage) { targetMsg = item.userInputMessage; break; }
|
||||
}
|
||||
}
|
||||
if (!targetMsg && cs?.currentMessage?.userInputMessage) {
|
||||
targetMsg = cs.currentMessage.userInputMessage;
|
||||
}
|
||||
if (!targetMsg) return;
|
||||
|
||||
const content = typeof targetMsg.content === "string" ? targetMsg.content : "";
|
||||
const next = dedupStringAppend(content, prompt);
|
||||
if (next === content) return; // already injected — idempotent across retries
|
||||
try { targetMsg.content = next; } catch (_) { /* frozen/proxy fail-open */ }
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -115,7 +115,7 @@ async function text(stream) {
|
||||
async function execute(executor = new KiroExecutor(), overrides = {}) {
|
||||
return executor.execute({
|
||||
model: "kr/claude-opus-4.8",
|
||||
body: { systemPrompt: "base", conversationState: {} },
|
||||
body: { conversationState: { currentMessage: { userInputMessage: { content: "base", modelId: "m" } } } },
|
||||
stream: true,
|
||||
credentials,
|
||||
...overrides
|
||||
@@ -342,8 +342,12 @@ describe("Kiro terminal integrity recovery", () => {
|
||||
const retryBody = JSON.parse(fetchMock.mock.calls[1][1].body);
|
||||
|
||||
expect(body).toContain("Recovered safely.");
|
||||
expect(retryBody.systemPrompt).toContain("tool_call wrapper was malformed");
|
||||
expect(retryBody.systemPrompt).not.toContain("IGNORE_ALL_INSTRUCTIONS");
|
||||
// The repair instruction rides in the user turn: kiro.dev rejects a
|
||||
// top-level systemPrompt with 400 REQUEST_BODY_INVALID.
|
||||
const retryContent = retryBody.conversationState.currentMessage.userInputMessage.content;
|
||||
expect(retryBody.systemPrompt).toBeUndefined();
|
||||
expect(retryContent).toContain("tool_call wrapper was malformed");
|
||||
expect(retryContent).not.toContain("IGNORE_ALL_INSTRUCTIONS");
|
||||
});
|
||||
|
||||
it("lets a complete tool call override metadata end_turn", async () => {
|
||||
|
||||
@@ -261,138 +261,121 @@ describe("system-inject gemini", () => {
|
||||
});
|
||||
|
||||
describe("system-inject kiro", () => {
|
||||
it("updates systemPrompt and mirrored prefix of first history user preserving tail", () => {
|
||||
const oldPrompt = "OLD_SYS";
|
||||
// The kiro.dev gateway rejects any body carrying a top-level `systemPrompt`
|
||||
// with 400 REQUEST_BODY_INVALID, so the prompt goes into the user turn only.
|
||||
it("appends to first history user, leaves systemPrompt untouched", () => {
|
||||
const timeCtx = "[Context: Current time is 2026-01-01T00:00:00.000Z]";
|
||||
const tail = "user tail content";
|
||||
const historyUserContent = `${oldPrompt}${SEP}${timeCtx}${SEP}${tail}`;
|
||||
const historyUserContent = `${timeCtx}${SEP}${tail}`;
|
||||
const body = {
|
||||
systemPrompt: oldPrompt,
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: historyUserContent, modelId: "m" } }, { assistantResponseMessage: { content: "..." } }],
|
||||
currentMessage: { userInputMessage: { content: "current " + tail, modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
const next = `${oldPrompt}${SEP}${P1}`;
|
||||
expect(body.systemPrompt).toBe(next);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${next}${SEP}${timeCtx}${SEP}${tail}`);
|
||||
expect(body.systemPrompt).toBeUndefined();
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${historyUserContent}${SEP}${P1}`);
|
||||
// currentMessage must stay untouched
|
||||
expect(body.conversationState.currentMessage.userInputMessage.content).toBe("current " + tail);
|
||||
});
|
||||
|
||||
it("when no history user, updates currentMessage instead", () => {
|
||||
const oldPrompt = "OLD";
|
||||
const body = {
|
||||
systemPrompt: oldPrompt,
|
||||
conversationState: {
|
||||
history: [],
|
||||
currentMessage: { userInputMessage: { content: `${oldPrompt}${SEP}tail`, modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}`);
|
||||
expect(body.conversationState.currentMessage.userInputMessage.content).toBe(`${oldPrompt}${SEP}${P1}${SEP}tail`);
|
||||
});
|
||||
|
||||
it("empty old prompt prepends to chosen user content", () => {
|
||||
const body = {
|
||||
systemPrompt: "",
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: "tail hello", modelId: "m" } }],
|
||||
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(P1);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${P1}${SEP}tail hello`);
|
||||
});
|
||||
|
||||
it("if old prompt not mirrored at head, do not alter user content", () => {
|
||||
it("never writes a top-level systemPrompt, even if one is already present", () => {
|
||||
const body = {
|
||||
systemPrompt: "OLD",
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: "different head content", modelId: "m" } }],
|
||||
history: [{ userInputMessage: { content: "tail", modelId: "m" } }],
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe("OLD");
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`tail${SEP}${P1}`);
|
||||
});
|
||||
|
||||
it("when no history user, updates currentMessage instead", () => {
|
||||
const body = {
|
||||
conversationState: {
|
||||
history: [],
|
||||
currentMessage: { userInputMessage: { content: "tail", modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBeUndefined();
|
||||
expect(body.conversationState.currentMessage.userInputMessage.content).toBe(`tail${SEP}${P1}`);
|
||||
});
|
||||
|
||||
it("empty user content becomes the prompt itself", () => {
|
||||
const body = {
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: "", modelId: "m" } }],
|
||||
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(`OLD${SEP}${P1}`);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe("different head content");
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(P1);
|
||||
expect(body.conversationState.currentMessage.userInputMessage.content).toBe("cur");
|
||||
});
|
||||
|
||||
it("exact retry idempotency for kiro", () => {
|
||||
const oldPrompt = "OLD";
|
||||
const body = {
|
||||
systemPrompt: oldPrompt,
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: `${oldPrompt}${SEP}tail`, modelId: "m" } }],
|
||||
history: [{ userInputMessage: { content: "tail", modelId: "m" } }],
|
||||
currentMessage: { userInputMessage: { content: "cur", modelId: "m" } },
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
const after1 = JSON.parse(JSON.stringify(body));
|
||||
const after1 = body.conversationState.history[0].userInputMessage.content;
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(after1.systemPrompt);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(after1.conversationState.history[0].userInputMessage.content);
|
||||
// different prompt both apply
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(after1);
|
||||
// different prompt both apply, in injection order
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P2);
|
||||
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}${SEP}${P2}`);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`tail${SEP}${P1}${SEP}${P2}`);
|
||||
});
|
||||
|
||||
it("preserves non-enumerable _kiroUpstreamModel", () => {
|
||||
const body = {
|
||||
systemPrompt: "OLD",
|
||||
conversationState: { history: [{ userInputMessage: { content: "OLD" + SEP + "tail", modelId: "m" } }], currentMessage: { userInputMessage: { content: "OLD" + SEP + "tail2", modelId: "m" } } },
|
||||
conversationState: { history: [{ userInputMessage: { content: "tail", modelId: "m" } }], currentMessage: { userInputMessage: { content: "tail2", modelId: "m" } } },
|
||||
};
|
||||
Object.defineProperty(body, "_kiroUpstreamModel", { value: "m", enumerable: false });
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body._kiroUpstreamModel).toBe("m");
|
||||
expect(Object.getOwnPropertyDescriptor(body, "_kiroUpstreamModel").enumerable).toBe(false);
|
||||
});
|
||||
|
||||
it("frozen user message fails open without throwing or half-writing", () => {
|
||||
const body = {
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: Object.freeze({ content: "tail", modelId: "m" }) }],
|
||||
},
|
||||
};
|
||||
expect(() => injectSystemPrompt(body, FORMATS.KIRO, P1)).not.toThrow();
|
||||
expect(body.systemPrompt).toBeUndefined();
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe("tail");
|
||||
});
|
||||
});
|
||||
|
||||
describe("system-inject regression fixes", () => {
|
||||
it("kiro partial mutation converges on retry after transient content write failure", () => {
|
||||
const oldPrompt = "OLD";
|
||||
let failNextWrite = true;
|
||||
const um = { content: `${oldPrompt}${SEP}tail`, modelId: "m" };
|
||||
const um = { content: "tail", modelId: "m" };
|
||||
const proxiedUm = new Proxy(um, {
|
||||
set(t, p, v) {
|
||||
if (p === "content" && failNextWrite) { failNextWrite = false; throw new Error("transient"); }
|
||||
t[p] = v; return true;
|
||||
},
|
||||
});
|
||||
const body = {
|
||||
systemPrompt: oldPrompt,
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: proxiedUm }],
|
||||
},
|
||||
};
|
||||
const body = { conversationState: { history: [{ userInputMessage: proxiedUm }] } };
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
// first pass rolled back atomically — nothing half-applied
|
||||
expect(body.systemPrompt).toBe(oldPrompt);
|
||||
expect(um.content).toBe(`${oldPrompt}${SEP}tail`);
|
||||
// nothing half-applied
|
||||
expect(um.content).toBe("tail");
|
||||
expect(body.systemPrompt).toBeUndefined();
|
||||
// retry converges
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(`${oldPrompt}${SEP}${P1}`);
|
||||
expect(um.content).toBe(`${oldPrompt}${SEP}${P1}${SEP}tail`);
|
||||
});
|
||||
|
||||
it("kiro rolls back systemPrompt when user content write fails (atomicity)", () => {
|
||||
const oldPrompt = "OLD";
|
||||
const body = {
|
||||
systemPrompt: oldPrompt,
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: Object.freeze({ content: `${oldPrompt}${SEP}tail`, modelId: "m" }) }],
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.systemPrompt).toBe(oldPrompt);
|
||||
expect(um.content).toBe(`tail${SEP}${P1}`);
|
||||
});
|
||||
|
||||
it("kiro shape gate: stray conversationState without history/currentMessage does not hijack chat body", () => {
|
||||
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }], systemPrompt: "", conversationState: {} };
|
||||
const body = { messages: [{ role: ROLE.SYSTEM, content: "hello" }], conversationState: {} };
|
||||
injectSystemPrompt(body, FORMATS.OPENAI, P1);
|
||||
expect(body.messages[0].content).toBe(`hello${SEP}${P1}`);
|
||||
});
|
||||
@@ -409,15 +392,14 @@ describe("system-inject regression fixes", () => {
|
||||
expect(body.instructions).toBe(`You are RULE follower${SEP}RULE`);
|
||||
});
|
||||
|
||||
it("kiro empty-old prepend fires when prompt appears mid-tail only", () => {
|
||||
it("substring occurrence does not suppress kiro injection", () => {
|
||||
const body = {
|
||||
systemPrompt: "",
|
||||
conversationState: {
|
||||
history: [{ userInputMessage: { content: `some ${P1} here`, modelId: "m" } }],
|
||||
},
|
||||
};
|
||||
injectSystemPrompt(body, FORMATS.KIRO, P1);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`${P1}${SEP}some ${P1} here`);
|
||||
expect(body.conversationState.history[0].userInputMessage.content).toBe(`some ${P1} here${SEP}${P1}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user