UNPKG

assistant-stream

Version:

Streaming utilities for AI assistants

208 lines (195 loc) • 8.11 kB
import { afterEach, expect, it, vi } from "vitest"; import { registerJsonParserBackend, type JsonParserBackend, type JsonParserBackendContext, } from "./json-parser-backend"; import { IncrementalJsonObjectParser } from "./incremental-json-object-parser"; import { getPartialJsonObjectMeta, parsePartialJsonObject, } from "./parse-partial-json-object"; const cleanups: (() => void)[] = []; afterEach(() => { for (const cleanup of cleanups.splice(0).reverse()) cleanup(); }); const referenceBackend = ( context: JsonParserBackendContext, ): JsonParserBackend => { const { text, fallback, createSnapshot } = context; const parsed = text.trim() ? parsePartialJsonObject(text) : undefined; const meta = parsed && getPartialJsonObjectMeta(parsed); const args = parsed && meta ? createSnapshot(parsed, meta.state, meta.partialPath) : fallback; return { currentArgs: args, append: (delta) => referenceBackend({ ...context, text: text + delta, fallback: args }), }; }; it("registers through the public entry without a provider-specific import", async () => { const { registerJsonParserBackend: register } = await import("../../index"); const factory = vi.fn(referenceBackend); cleanups.push(register(factory)); const parser = IncrementalJsonObjectParser.from('{"value":"hel'); expect(factory).toHaveBeenCalledOnce(); expect(parser.append('lo"}').currentArgs).toMatchObject({ value: "hello" }); expect(parser.currentArgs).toMatchObject({ value: "hel" }); }); it("keeps existing parsers and independently unregisters overlapping registrations", () => { const before = IncrementalJsonObjectParser.from('{"value":"'); const first = vi.fn(referenceBackend); const second = vi.fn(referenceBackend); const removeFirst = registerJsonParserBackend(first); cleanups.push(removeFirst); const retained = IncrementalJsonObjectParser.from('{"value":"hel'); const removeSecond = registerJsonParserBackend(second); cleanups.push(removeSecond); removeFirst(); removeFirst(); const latest = IncrementalJsonObjectParser.from('{"value":"new'); expect(first).toHaveBeenCalledOnce(); expect(second).toHaveBeenCalledOnce(); removeSecond(); expect(retained.append('lo"}').currentArgs).toMatchObject({ value: "hello" }); expect(latest.append('est"}').currentArgs).toMatchObject({ value: "newest" }); expect(before.append('native"}').currentArgs).toMatchObject({ value: "native", }); expect( IncrementalJsonObjectParser.from('{"value":').append("1}").currentArgs, ).toMatchObject({ value: 1 }); expect(first).toHaveBeenCalledOnce(); expect(second).toHaveBeenCalledOnce(); }); it("restores an earlier registration and distinguishes repeated factory registrations", () => { const first = vi.fn(referenceBackend); const second = vi.fn(referenceBackend); const removeFirst = registerJsonParserBackend(first); const removeDuplicate = registerJsonParserBackend(first); const removeSecond = registerJsonParserBackend(second); cleanups.push(removeFirst, removeDuplicate, removeSecond); removeFirst(); IncrementalJsonObjectParser.from('{"value":'); expect(second).toHaveBeenCalledOnce(); removeSecond(); IncrementalJsonObjectParser.from('{"value":'); expect(first).toHaveBeenCalledOnce(); removeDuplicate(); IncrementalJsonObjectParser.from('{"value":'); expect(first).toHaveBeenCalledOnce(); }); it("shares registration across separately loaded package copies", async () => { vi.resetModules(); const duplicate = await import("./json-parser-backend"); const factory = vi.fn(referenceBackend); cleanups.push(duplicate.registerJsonParserBackend(factory)); const parser = IncrementalJsonObjectParser.from('{"value":"hel'); expect(factory).toHaveBeenCalledOnce(); expect(getPartialJsonObjectMeta(parser.currentArgs)).toEqual({ state: "partial", partialPath: ["value"], }); expect(parser.append('lo"}').currentArgs).toMatchObject({ value: "hello" }); const other = await import("./incremental-json-object-parser"); const otherMetadata = await import("./parse-partial-json-object"); const otherParser = other.IncrementalJsonObjectParser.from('{"value":"other'); expect( otherMetadata.getPartialJsonObjectMeta(otherParser.currentArgs), ).toEqual({ state: "partial", partialPath: ["value"] }); expect(otherParser.append(' copy"}').currentArgs).toMatchObject({ value: "other copy", }); }); it.each(["decline", "throw"])( "falls back when backend creation fails (%s)", (failure) => { cleanups.push( registerJsonParserBackend(() => { if (failure === "throw") throw new Error("backend unavailable"); return undefined; }), ); const parser = IncrementalJsonObjectParser.from('{"nested":{"value":"hel'); expect(parser.append('lo"}}').currentArgs).toMatchObject({ nested: { value: "hello" }, }); expect(parser.currentArgs).toMatchObject({ nested: { value: "hel" } }); }, ); it.each(["decline", "throw"])( "replays the accepted prefix when append fails (%s)", (failure) => { cleanups.push( registerJsonParserBackend((context) => ({ ...referenceBackend(context), append: (delta) => ({ ...referenceBackend({ ...context, text: context.text + delta }), append: () => { if (failure === "throw") throw new Error("unsupported delta"); return undefined; }, }), })), ); const initial = IncrementalJsonObjectParser.from('{"value":"hel'); const parser = initial.append("lo"); const left = parser.append(' world"}'); const right = parser.append(' there"}'); expect(left.currentArgs).toMatchObject({ value: "hello world" }); expect(right.currentArgs).toMatchObject({ value: "hello there" }); expect(initial.currentArgs).toMatchObject({ value: "hel" }); expect(parser.currentArgs).toMatchObject({ value: "hello" }); expect(parser.currentTextLength).toBe('{"value":"hello'.length); expect(getPartialJsonObjectMeta(left.currentArgs)?.state).toBe("complete"); }, ); it("detaches backend snapshots and attaches metadata without mutating backend values", () => { const root = { nested: { value: "hel" } }; cleanups.push( registerJsonParserBackend((context) => ({ currentArgs: context.createSnapshot(root, "partial", ["nested", "value"]), append: () => undefined, })), ); const parser = IncrementalJsonObjectParser.from('{"nested":{"value":"hel'); root.nested.value = "changed"; expect(parser.currentArgs).toMatchObject({ nested: { value: "hel" } }); expect(getPartialJsonObjectMeta(parser.currentArgs)).toEqual({ state: "partial", partialPath: ["nested", "value"], }); (parser.currentArgs.nested as { value: string }).value = "caller"; expect(root.nested.value).toBe("changed"); }); it("preserves the complete-input fast path", () => { const factory = vi.fn(referenceBackend); cleanups.push(registerJsonParserBackend(factory)); expect( IncrementalJsonObjectParser.from('{"value":"a }"}').currentArgs, ).toMatchObject({ value: "a }" }); expect(factory).not.toHaveBeenCalled(); const partial = IncrementalJsonObjectParser.from('{"value":"a }'); expect(factory).toHaveBeenCalledOnce(); expect(partial.append(' b"}').currentArgs).toMatchObject({ value: "a } b" }); }); it.each([ '{"__proto__":{"polluted":true}}', '{"nested":[{"__proto__":{"polluted":true}}]}', '{"nested":{"constructor":{"prototype":{"polluted":true}}}}', ])("rejects unsafe backend snapshot keys and replays natively: %s", (input) => { const root = JSON.parse(input); cleanups.push( registerJsonParserBackend((context) => ({ currentArgs: context.createSnapshot(root, "partial", []), append: () => undefined, })), ); const fallback = { value: "safe" }; const parser = IncrementalJsonObjectParser.from(input, fallback); expect(parser.currentArgs).toBe(fallback); expect(Object.getPrototypeOf(parser.currentArgs)).toBe(Object.prototype); expect(parser.append(" ").currentArgs).toBe(fallback); });