fix(kiro): stabilize conversationId across prompt compression (#2630)

Integrated into release/v3.8.3
This commit is contained in:
HALDRO
2026-05-24 04:07:19 +08:00
committed by GitHub
parent 584bea2cae
commit b150b5f772
3 changed files with 43 additions and 7 deletions

View File

@@ -2106,6 +2106,7 @@ export async function handleChatCore({
const allMessages = compressionBody?.messages || body?.contents || body?.request?.contents || [];
let cavemanOutputModeApplied = false;
let cavemanOutputModeIntensity: string | null = null;
let preCompressionBody: typeof body | null = null;
if (body && Array.isArray(allMessages) && allMessages.length > 0) {
let estimatedTokens = estimateTokens(JSON.stringify(allMessages));
let promptCompressionEnabled = false;
@@ -2557,6 +2558,10 @@ export async function handleChatCore({
`Checking compression: ${estimatedTokens} tokens vs ${threshold} threshold (${contextLimit} limit, ${reservedTokens} reserved)`
);
// Capture pre-compression body so translators can access original message
// content even after compression alters it (e.g. stable Kiro conversationId).
preCompressionBody = body;
if (promptCompressionEnabled && estimatedTokens > threshold) {
log?.info?.(
"CONTEXT",
@@ -2933,6 +2938,7 @@ export async function handleChatCore({
preserveDeveloperRole,
preserveCacheControl,
signatureNamespace: connectionId,
...(preCompressionBody ? { preCompressionBody } : {}),
}
);
}

View File

@@ -132,6 +132,7 @@ export function translateRequest(
preserveDeveloperRole?: boolean;
preserveCacheControl?: boolean;
signatureNamespace?: string | null;
preCompressionBody?: Record<string, unknown> | null;
}
) {
let result = body;
@@ -185,12 +186,16 @@ export function translateRequest(
if (targetFormat !== FORMATS.OPENAI) {
const fromOpenAI = getRequestTranslator(FORMATS.OPENAI, targetFormat);
if (fromOpenAI) {
const translationCredentials = options?.signatureNamespace
? {
...(credentials && typeof credentials === "object" ? credentials : {}),
_signatureNamespace: options.signatureNamespace,
}
: credentials;
const hasNs = options?.signatureNamespace != null;
const hasPreCompression = options?.preCompressionBody != null;
const translationCredentials =
hasNs || hasPreCompression
? {
...(credentials && typeof credentials === "object" ? credentials : {}),
...(hasNs ? { _signatureNamespace: options.signatureNamespace } : {}),
...(hasPreCompression ? { _preCompressionBody: options.preCompressionBody } : {}),
}
: credentials;
result = fromOpenAI(model, result, stream, translationCredentials);
}
}

View File

@@ -746,8 +746,33 @@ export function buildKiroPayload(model, body, stream, credentials) {
// upstream Kiro/AWS conversation context, leaking prior state across
// sessions. See conversionMessages() above for the `__synthetic` marker.
const NAMESPACE_KIRO = "34f7193f-561d-4050-bc84-9547d953d6bf";
// Priority 1: Extract first user message from pre-compression body (passed by chatCore before
// compressContext runs). This keeps conversationId stable even when compression alters content.
// Priority 2: Deterministic hash from first user message in translated history (fallback).
const preCompressionBody = credentials?._preCompressionBody as
| Record<string, unknown>
| null
| undefined;
const preCompressionMessages = Array.isArray(preCompressionBody?.messages)
? preCompressionBody.messages
: null;
const preCompressionFirstUser = preCompressionMessages?.find(
(m: Record<string, unknown>) => m.role === "user"
);
const seedFromPreCompression = preCompressionFirstUser
? typeof preCompressionFirstUser.content === "string"
? preCompressionFirstUser.content
: Array.isArray(preCompressionFirstUser.content)
? (preCompressionFirstUser.content as Array<{ type: string; text?: string }>)
.filter((b) => b.type === "text")
.map((b) => b.text || "")
.join(" ")
: ""
: "";
const firstRealUserTurn = history.find((h) => h?.userInputMessage?.content && !h.__synthetic);
const firstContent = firstRealUserTurn?.userInputMessage?.content || finalContent;
const firstContent =
seedFromPreCompression || firstRealUserTurn?.userInputMessage?.content || finalContent;
// Use uuidv5 with the hash of the system prompt / first message to maintain AWS Builder ID context cache
payload.conversationState.conversationId = uuidv5(