Files
oh-my-pi/packages/coding-agent/test/tool-execution-args.test.ts
T
oldschoola 3a733c480b perf(agent,ai,coding-agent): in-place state mutation + per-delta json-parse throttle + drop hot-path structuredClone
F3 (agent): mutate state.messages and state.pendingToolCalls in place on
appendMessage/popMessage/clearMessages/reset/tool_execution_start/_end
instead of allocating a fresh array/Set on every transition. Subscribers
that capture state.messages by reference now observe updates directly.
Public type signature unchanged.

F5 (ai): add parseStreamingJsonThrottled to utils/json-parse — a per-delta
wrapper around parseStreamingJson that skips the re-parse until the
buffer has grown by minGrowthBytes (default 256). Wired into every
provider's tool-call argument accumulator (anthropic, amazon-bedrock,
openai-completions, openai-codex-responses, openai-responses-shared) so
per-delta cost becomes O(N) in total buffer length instead of O(N²).
Every provider's toolcall_end still runs a final unthrottled parse, so
the published block.arguments is unchanged.

F8 (coding-agent): drop the per-delta structuredClone of streaming tool
arguments in ToolExecutionComponent.updateArgs. event-controller.ts and
ui-helpers.ts already spread their input into a fresh object on each
delta, so cloning here was dead work on the rendering hot path. Added a
reference-equality short-circuit so repeat calls with the same args
object skip the preview-diff and display refresh.
2026-05-31 04:46:06 +02:00

54 lines
1.8 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "bun:test";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
import type { TUI } from "@oh-my-pi/pi-tui";
import { ToolExecutionComponent } from "../src/modes/components/tool-execution";
describe("ToolExecutionComponent.updateArgs (F8 — no clone, ref-eq fast path)", () => {
let initialized = false;
afterEach(() => {
vi.restoreAllMocks();
});
async function makeComponent(args: unknown) {
if (!initialized) {
await initTheme();
initialized = true;
}
const uiStub = { requestRender() {} } as unknown as TUI;
return new ToolExecutionComponent("bash", args, {}, undefined, uiStub);
}
it("does NOT call structuredClone in updateArgs (caller already owns isolation)", async () => {
const cloneSpy = vi.spyOn(globalThis, "structuredClone");
const component = await makeComponent({ command: "ls" });
cloneSpy.mockClear();
// Simulate event-controller.ts: each delta builds a fresh spread.
for (let i = 0; i < 5; i++) {
component.updateArgs({ command: `ls -l ${i}` });
}
expect(cloneSpy).not.toHaveBeenCalled();
});
it("short-circuits when called with the exact same args reference", async () => {
const component = await makeComponent({ command: "ls" });
const args = { command: "ls -al" };
component.updateArgs(args);
// Second call with the SAME object reference should be a no-op.
// (Render bookkeeping doesn't re-fire — assert via #args not changing.)
component.updateArgs(args);
component.updateArgs(args);
// Different object content → must NOT be short-circuited.
const next = { command: "echo hi" };
component.updateArgs(next);
// Re-issuing the prior reference is now stale but still ref-distinct.
// The component must accept it without crashing.
expect(() => component.updateArgs(args)).not.toThrow();
});
});