pi-mono/packages/ai/test/memory-helpers.ts
Mario Zechner f715a15c10 fix(ai,chord,durable,coding-agent): parse streamed tool-call arguments incrementally (fixes #9265)
- ai: StreamingJsonParser / ToolCallArgumentsStream read each argument delta once (tolerant,
  immutable snapshots, linear cost); every provider, pi-messages and the faux provider use it
  and keep stream state (partialJson, partialArgs, index, customInput, streamIndex, Bedrock
  reasoning bytes) off the content blocks; grammar tool input appends per delta
- agent: streamProxy uses the same parser
- chord: Delta append ops own their text (ownedSlice), so ops and replicas no longer keep the
  whole growing string alive
- durable, ai, mcp: bounded error excerpts and summaries own their characters
- coding-agent: tool calls render streamed arguments once per frame, not once per delta
2026-10-24 08:00:07 +00:00

39 lines
1.5 KiB
TypeScript

// GC-based measurements for cost guards and leak tests: bytes a function allocates, and collecting garbage on demand.
import { GCProfiler, getHeapStatistics, setFlagsFromString } from "node:v8";
import { runInNewContext } from "node:vm";
setFlagsFromString("--expose-gc");
const gc = runInNewContext("gc") as () => void;
/** Run a full garbage collection a few times, yielding in between so finalizers and weak references settle. */
export async function collectGarbage(): Promise<void> {
for (let round = 0; round < 4; round++) {
await new Promise((resolve) => setImmediate(resolve));
gc();
}
}
/**
* Bytes `run` allocates on the JavaScript heap, including garbage collected while it ran: the heap growth plus what
* every collection during the run freed.
*/
export async function allocatedBytes(run: () => unknown): Promise<number> {
gc();
const profiler = new GCProfiler();
profiler.start();
const before = getHeapStatistics().used_heap_size;
await run();
const after = getHeapStatistics().used_heap_size;
const { statistics } = profiler.stop();
let freed = 0;
for (const collection of statistics) {
freed += collection.beforeGC.heapStatistics.usedHeapSize - collection.afterGC.heapStatistics.usedHeapSize;
}
return after - before + freed;
}
/** Whether `reference` is collected once nothing else holds its target. */
export async function isCollected(reference: WeakRef<object>): Promise<boolean> {
await collectGarbage();
return reference.deref() === undefined;
}