fix(kiro): improve direct session cache reuse
Reshape Kiro direct requests so resumed client sessions reuse Kiro's cache-affinity fields instead of starting unrelated CodeWhisperer conversations. - keep conversationState.conversationId stable when the client sends an explicit session id (x-session-id, session_id, conversation_id, Claude Code session metadata) - add a stable conversationState.agentContinuationId per Kiro session - send conversationState.agentTaskType: "vibe" and agentMode: "vibe", matching the normal Kiro CLI/KAS chat path - move Kiro thinking instructions into Kiro-compatible systemPrompt / additionalModelRequestFields instead of generic top-level thinking - keep volatile timestamp context out of the top-level systemPrompt; it remains only in user content fallback - suppress additionalModelRequestFields for legacy 4.5-era Claude/Kiro models that reject it, while defaulting future Claude/Kiro model ids to supported - preserve Kiro meteringEvent credit usage internally for accounting without leaking provider-specific fields into OpenAI-compatible usage - prevent unrelated headerless Kiro requests from sharing one connection-wide continuation - cap/evict continuation sessions so long-running processes do not grow the continuation map unbounded - treat generated headerless Kiro sessions as one-shot so they do not evict real explicit-session continuations - keep credit-only Kiro metering valid for internal persistence when token metrics are unavailable
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { KiroExecutor } from "../../open-sse/executors/kiro.js";
|
||||
import "../translator/registerAll.js";
|
||||
|
||||
function createMockFrame(eventType, payloadObj) {
|
||||
const payloadStr = JSON.stringify(payloadObj);
|
||||
@@ -47,6 +48,13 @@ async function readAllSSE(stream) {
|
||||
return result;
|
||||
}
|
||||
|
||||
async function readNextWithTimeout(reader) {
|
||||
return Promise.race([
|
||||
reader.read(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("timed out waiting for SSE chunk")), 100)),
|
||||
]);
|
||||
}
|
||||
|
||||
describe("KiroExecutor thinking tag stripping", () => {
|
||||
it("strips <thinking> tags from assistantResponseEvent", async () => {
|
||||
const executor = new KiroExecutor();
|
||||
@@ -121,4 +129,53 @@ describe("KiroExecutor thinking tag stripping", () => {
|
||||
const contentChunks = objects.filter(obj => obj.choices[0].delta.content !== undefined);
|
||||
expect(contentChunks.length).toBe(0);
|
||||
});
|
||||
|
||||
it("emits a terminal chunk at messageStop before the upstream stream closes", async () => {
|
||||
const executor = new KiroExecutor();
|
||||
|
||||
const f1 = createMockFrame("assistantResponseEvent", { content: "OK" });
|
||||
const f2 = createMockFrame("messageStopEvent", {});
|
||||
|
||||
const readableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(f1);
|
||||
controller.enqueue(f2);
|
||||
}
|
||||
});
|
||||
|
||||
const transformedResponse = executor.transformEventStreamToSSE({ body: readableStream }, "claude-test");
|
||||
const reader = transformedResponse.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let output = "";
|
||||
for (let i = 0; i < 4 && !output.includes("\"finish_reason\":\"stop\""); i++) {
|
||||
const { value } = await readNextWithTimeout(reader);
|
||||
output += decoder.decode(value, { stream: true });
|
||||
}
|
||||
await reader.cancel();
|
||||
|
||||
expect(output).toContain("\"finish_reason\":\"stop\"");
|
||||
});
|
||||
|
||||
it("uses tool_calls finish reason for tool streams without messageStop", async () => {
|
||||
const executor = new KiroExecutor();
|
||||
|
||||
const f1 = createMockFrame("toolUseEvent", { toolUseId: "tool-1", name: "read_file", input: { path: "a.txt" } });
|
||||
|
||||
const readableStream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(f1);
|
||||
controller.close();
|
||||
}
|
||||
});
|
||||
|
||||
const transformedResponse = executor.transformEventStreamToSSE({ body: readableStream }, "claude-test");
|
||||
const output = await readAllSSE(transformedResponse.body);
|
||||
const objects = output
|
||||
.split("\n")
|
||||
.filter(line => line.startsWith("data: ") && !line.includes("[DONE]"))
|
||||
.map(line => JSON.parse(line.slice(6)));
|
||||
|
||||
const finalChunk = objects.at(-1);
|
||||
expect(finalChunk.choices[0].finish_reason).toBe("tool_calls");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user