fix(server,app): coalesce tool call stream events to prevent JS thread freeze

Pi's ACP binary streams 200+ redundant tool_call_update events per tool call,
bypassing the server coalescer and flooding the app with synchronous state
updates. Two-layer fix:

Server: extend AgentStreamCoalescer to handle tool_call items via last-write-wins
per callId (200ms window, up from 33ms). Terminal statuses flush immediately.

App: mergeToolCallDetail and appendAgentToolCall now return existing references
when fields are unchanged, preserving React.memo equality checks.
This commit is contained in:
Mohamed Boudra
2026-04-19 14:26:50 +07:00
parent 2238171955
commit 5a232d8cf1
5 changed files with 682 additions and 102 deletions
+189 -1
View File
@@ -3,6 +3,7 @@ import { describe, it } from "vitest";
import {
hydrateStreamState,
mergeToolCallDetail,
reduceStreamUpdate,
type AgentToolCallItem,
type StreamItem,
@@ -10,7 +11,7 @@ import {
} from "./stream";
import type { AgentProvider, ToolCallDetail } from "@server/server/agent/agent-sdk-types";
import type { AgentStreamEventPayload } from "@server/shared/messages";
import { buildToolCallDisplayModel } from "@/utils/tool-call-display";
import { buildToolCallDisplayModel } from "../../../server/src/shared/tool-call-display";
type CanonicalToolStatus = "running" | "completed" | "failed" | "canceled";
@@ -99,6 +100,193 @@ function findToolByCallId(state: StreamItem[], callId: string): AgentToolCallIte
);
}
describe("stream reducer tool call idempotency", () => {
it("returns the same detail reference when tool call detail is identical", () => {
const existing: ToolCallDetail = {
type: "shell",
command: "npm test",
cwd: "/tmp/repo",
};
const incoming: ToolCallDetail = {
type: "shell",
command: "npm test",
cwd: "/tmp/repo",
};
const merged = mergeToolCallDetail(existing, incoming);
assert.strictEqual(merged, existing);
});
it("returns a new detail reference when tool call detail changes", () => {
const existing: ToolCallDetail = {
type: "shell",
command: "npm test",
cwd: "/tmp/repo",
};
const incoming: ToolCallDetail = {
type: "shell",
command: "npm run typecheck",
cwd: "/tmp/repo",
};
const merged = mergeToolCallDetail(existing, incoming);
assert.notStrictEqual(merged, existing);
assert.deepStrictEqual(merged, incoming);
});
it("returns the same state array when status, error, detail, and metadata are identical", () => {
const callId = "idempotent-tool-call";
const initialState = reduceStreamUpdate(
[],
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "running",
detail: {
type: "shell",
command: "npm test",
cwd: "/tmp/repo",
},
metadata: {
paneId: "%1",
},
}),
new Date("2025-01-01T12:00:00Z"),
);
const nextState = reduceStreamUpdate(
initialState,
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "running",
detail: {
type: "shell",
command: "npm test",
cwd: "/tmp/repo",
},
metadata: {
paneId: "%1",
},
}),
new Date("2025-01-01T12:00:01Z"),
);
assert.strictEqual(nextState, initialState);
});
it("returns a new state array when tool call status changes", () => {
const callId = "status-change-tool-call";
const initialState = reduceStreamUpdate(
[],
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "running",
detail: {
type: "shell",
command: "npm test",
},
}),
new Date("2025-01-01T12:10:00Z"),
);
const nextState = reduceStreamUpdate(
initialState,
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "completed",
detail: {
type: "shell",
command: "npm test",
},
}),
new Date("2025-01-01T12:10:01Z"),
);
assert.notStrictEqual(nextState, initialState);
});
it("returns a new state array when tool call detail changes", () => {
const callId = "detail-change-tool-call";
const initialState = reduceStreamUpdate(
[],
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "running",
detail: {
type: "shell",
command: "npm test",
},
}),
new Date("2025-01-01T12:20:00Z"),
);
const nextState = reduceStreamUpdate(
initialState,
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "running",
detail: {
type: "shell",
command: "npm run typecheck",
},
}),
new Date("2025-01-01T12:20:01Z"),
);
assert.notStrictEqual(nextState, initialState);
});
it("returns a new state array when tool call error changes", () => {
const callId = "error-change-tool-call";
const initialState = reduceStreamUpdate(
[],
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "failed",
error: { message: "first failure" },
detail: {
type: "shell",
command: "npm test",
},
}),
new Date("2025-01-01T12:30:00Z"),
);
const nextState = reduceStreamUpdate(
initialState,
canonicalToolTimeline({
provider: "codex",
callId,
name: "shell",
status: "failed",
error: { message: "second failure" },
detail: {
type: "shell",
command: "npm test",
},
}),
new Date("2025-01-01T12:30:01Z"),
);
assert.notStrictEqual(nextState, initialState);
});
});
describe("stream reducer canonical tool calls", () => {
it("is deterministic for equivalent hydration sequences", () => {
const updates = [
+57 -9
View File
@@ -336,7 +336,36 @@ function mergeUnknownValue(existing: unknown | null, incoming: unknown | null):
return incoming;
}
function mergeToolCallDetail(existing: ToolCallDetail, incoming: ToolCallDetail): ToolCallDetail {
function hasSameIncomingFields<T extends Record<string, unknown>>(
existing: T,
incoming: T,
): boolean {
return Object.entries(incoming).every(([key, value]) => existing[key] === value);
}
function mergeToolCallMetadata(
existing: Record<string, unknown> | undefined,
incoming: Record<string, unknown> | undefined,
): Record<string, unknown> | undefined {
if (!incoming) {
return existing;
}
if (!existing) {
return incoming;
}
if (hasSameIncomingFields(existing, incoming)) {
return existing;
}
return { ...existing, ...incoming };
}
export function mergeToolCallDetail(
existing: ToolCallDetail,
incoming: ToolCallDetail,
): ToolCallDetail {
if (existing.type === "unknown" && incoming.type !== "unknown") {
return incoming;
}
@@ -346,14 +375,24 @@ function mergeToolCallDetail(existing: ToolCallDetail, incoming: ToolCallDetail)
}
if (existing.type === "unknown" && incoming.type === "unknown") {
const input = mergeUnknownValue(existing.input, incoming.input);
const output = mergeUnknownValue(existing.output, incoming.output);
if (input === existing.input && output === existing.output) {
return existing;
}
return {
type: "unknown",
input: mergeUnknownValue(existing.input, incoming.input),
output: mergeUnknownValue(existing.output, incoming.output),
input,
output,
};
}
if (existing.type === incoming.type) {
if (hasSameIncomingFields(existing, incoming)) {
return existing;
}
return { ...existing, ...incoming } as ToolCallDetail;
}
@@ -391,8 +430,7 @@ function appendAgentToolCall(
const existingIndex = findExistingAgentToolCallIndex(state, data.callId);
if (existingIndex >= 0) {
const next = [...state];
const existing = next[existingIndex];
const existing = state[existingIndex];
if (!existing || !isAgentToolCallItem(existing)) {
return state;
}
@@ -401,12 +439,22 @@ function appendAgentToolCall(
mergedStatus === "failed"
? (data.error ?? existing.payload.data.error ?? { message: "Tool call failed" })
: null;
const mergedMetadata =
data.metadata || existing.payload.data.metadata
? { ...existing.payload.data.metadata, ...data.metadata }
: undefined;
const mergedMetadata = mergeToolCallMetadata(existing.payload.data.metadata, data.metadata);
const mergedDetail = mergeToolCallDetail(existing.payload.data.detail, data.detail);
if (
data.provider === existing.payload.data.provider &&
data.callId === existing.payload.data.callId &&
data.name === existing.payload.data.name &&
mergedStatus === existing.payload.data.status &&
mergedError === existing.payload.data.error &&
mergedDetail === existing.payload.data.detail &&
mergedMetadata === existing.payload.data.metadata
) {
return state;
}
const next = [...state];
next[existingIndex] = {
...existing,
timestamp,