Coalesce prepended tool call history
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { AgentStreamEventPayload } from "@server/shared/messages";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import { hydrateStreamState, type AgentToolCallItem, type StreamItem } from "@/types/stream";
|
||||
import {
|
||||
createAgentStreamReducerQueue,
|
||||
processTimelineResponse,
|
||||
@@ -31,6 +31,28 @@ function makeTimelineEntry(
|
||||
};
|
||||
}
|
||||
|
||||
function makeToolCallTimelineEntry(
|
||||
seq: number,
|
||||
callId: string,
|
||||
status: "running" | "completed",
|
||||
detail: Record<string, unknown>,
|
||||
) {
|
||||
return {
|
||||
seqStart: seq,
|
||||
seqEnd: seq,
|
||||
provider: "claude",
|
||||
item: {
|
||||
type: "tool_call",
|
||||
callId,
|
||||
name: "Read",
|
||||
status,
|
||||
detail,
|
||||
error: null,
|
||||
},
|
||||
timestamp: new Date(1000 + seq).toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
function makeTimelineEvent(
|
||||
text: string,
|
||||
type: string = "assistant_message",
|
||||
@@ -97,6 +119,13 @@ function getUserTexts(items: StreamItem[]): string[] {
|
||||
.map((item) => item.text);
|
||||
}
|
||||
|
||||
function getAgentToolCalls(items: StreamItem[]) {
|
||||
return items.filter(
|
||||
(item): item is AgentToolCallItem =>
|
||||
item.kind === "tool_call" && item.payload.source === "agent",
|
||||
);
|
||||
}
|
||||
|
||||
const baseTimelineInput: ProcessTimelineResponseInput = {
|
||||
payload: {
|
||||
agentId: "agent-1",
|
||||
@@ -510,6 +539,68 @@ describe("processTimelineResponse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("coalesces tool call lifecycle rows across the older-page prepend boundary", () => {
|
||||
const callId = "toolu_boundary";
|
||||
const currentTail = hydrateStreamState(
|
||||
[
|
||||
{
|
||||
event: {
|
||||
type: "timeline",
|
||||
provider: "claude",
|
||||
item: makeToolCallTimelineEntry(3, callId, "completed", {
|
||||
type: "read",
|
||||
filePath: "/tmp/example.ts",
|
||||
}).item,
|
||||
} as AgentStreamEventPayload,
|
||||
timestamp: new Date(3000),
|
||||
},
|
||||
],
|
||||
{ source: "canonical" },
|
||||
);
|
||||
const existingCursor: TimelineCursor = {
|
||||
epoch: "epoch-1",
|
||||
startSeq: 3,
|
||||
endSeq: 5,
|
||||
};
|
||||
|
||||
const result = processTimelineResponse({
|
||||
...baseTimelineInput,
|
||||
currentTail,
|
||||
currentCursor: existingCursor,
|
||||
payload: {
|
||||
...baseTimelineInput.payload,
|
||||
direction: "before",
|
||||
epoch: "epoch-1",
|
||||
startCursor: { seq: 1 },
|
||||
endCursor: { seq: 2 },
|
||||
entries: [
|
||||
makeToolCallTimelineEntry(1, callId, "running", {
|
||||
type: "unknown",
|
||||
input: { file_path: "/tmp/example.ts" },
|
||||
output: null,
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const toolCalls = getAgentToolCalls(result.tail);
|
||||
expect(
|
||||
toolCalls.map((item) => ({
|
||||
id: item.id,
|
||||
callId: item.payload.data.callId,
|
||||
status: item.payload.data.status,
|
||||
detailType: item.payload.data.detail.type,
|
||||
})),
|
||||
).toEqual([
|
||||
{
|
||||
id: `agent_tool_${callId}`,
|
||||
callId,
|
||||
status: "completed",
|
||||
detailType: "read",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("requests canonical catch-up when a projected entry overlaps unseen seqs", () => {
|
||||
const existingCursor: TimelineCursor = {
|
||||
epoch: "epoch-1",
|
||||
|
||||
Reference in New Issue
Block a user