UNPKG

assistant-stream

Version:

Streaming utilities for AI assistants

326 lines (311 loc) • 7.67 kB
import { describe, it, expect } from "vitest"; import { parsePartialJsonObject, getPartialJsonObjectFieldState, getPartialJsonObjectMeta, markPartialJsonObjectComplete, } from "./parse-partial-json-object"; type PartialJsonTest = { input: string; query: (string | number)[]; result: "partial" | "complete" | "undefined"; }; const tests: PartialJsonTest[] = [ // empty query { input: ``, query: [], result: "partial", }, { input: `{`, query: [], result: "partial", }, { input: `{}`, query: [], result: "complete", }, // field query (missing) { input: ``, query: ["test"], result: "partial", }, { input: `{`, query: ["test"], result: "partial", }, { input: `{}`, query: ["test"], result: "complete", }, // field query (partial) { input: `{"foo": `, query: ["foo"], result: "partial", }, { input: `{"foo": "b`, query: ["foo"], result: "partial", }, { input: `{"foo": 123`, query: ["foo"], result: "partial", }, { input: `{"foo": {`, query: ["foo"], result: "partial", }, { input: `{"foo": [`, query: ["foo"], result: "partial", }, // field query (complete) { input: `{"foo": 123,`, query: ["foo"], result: "complete", }, { input: `{"foo": "b"`, query: ["foo"], result: "complete", }, { input: `{"foo": nu`, query: ["foo"], result: "complete", }, { input: `{"foo": fa`, query: ["foo"], result: "complete", }, { input: `{"foo": tr`, query: ["foo"], result: "complete", }, { input: `{"foo": {}`, query: ["foo"], result: "complete", }, { input: `{"foo": []`, query: ["foo"], result: "complete", }, // field query (nested) { input: `{"foo": [{ "bar": "abc`, query: ["foo", "0", "bar"], result: "partial", }, { input: `{"foo": [{ "bar": "abc"`, query: ["foo", "0", "bar"], result: "complete", }, { input: `{"foo": [{ "bar": 123`, query: ["foo", "0", "bar"], result: "partial", }, { input: `{"foo": [{ "bar": nu`, query: ["foo", 0, "bar"], result: "complete", }, // field non-existent { input: `{"bar": "hello"`, query: ["foo"], result: "partial", }, { input: `{"bar": "hello"}`, query: ["foo"], result: "complete", }, // mismatch type { input: `{"foo": 123`, query: ["foo", "bar", "baz"], result: "partial", }, { input: `{"foo": fa`, query: ["foo", "bar", "baz"], result: "complete", }, // numeric keys { input: `{"1": "value","0":"`, query: ["0"], result: "partial", }, { input: `{"1": "value","0":"`, query: ["1"], result: "complete", }, { input: `{"foo": "value", "0": "`, query: ["0"], result: "partial", }, { input: `{"foo": "value", "0": "`, query: ["foo"], result: "complete", }, // duplicate keys { input: `{"foo": "foo","bar":"bar","foo": "`, query: ["foo"], result: "partial", }, { input: `{"foo": "foo","bar":"bar","foo": "`, query: ["bar"], result: "complete", }, { input: `{"foo": [1,"a",{"b":1},[],{},[1,[[2]]],{"1":1,"t":1`, query: ["foo", 6, 1], result: "complete", }, { input: `{"foo": [1,"",{"b":1},[],{},[1,[[2]]],{"1":1,"t":1`, query: ["foo", 6, "t"], result: "partial", }, { input: `{"\\"": "t`, query: ['"'], result: "partial", }, { input: `{"\\"": "t"`, query: ['"'], result: "complete", }, { input: `{"\\u25CF": "t`, query: ["\u25CF"], result: "partial", }, // negative number whose digits have not arrived yet { input: `{"foo": [-`, query: ["foo"], result: "partial", }, { input: `{"foo": [-`, query: ["foo", 0], result: "partial", }, { input: `{"foo": [[-`, query: ["foo", 0, 0], result: "partial", }, // positive exponent whose digits have not arrived yet { input: `{"foo": 1e+`, query: ["foo"], result: "partial", }, { input: `{"foo": 1e+2`, query: ["foo"], result: "partial", }, ]; describe("parsePartialJsonObject and getPartialJsonObjectFieldState", () => { // Test each case in the tests array tests.forEach((testCase, index) => { it(`Test case #${index + 1}: ${testCase.input || `""`} with query ${JSON.stringify(testCase.query)} should return "${testCase.result}"`, () => { const args = parsePartialJsonObject(testCase.input); if (args === undefined) throw new Error("unable to parse args"); const fieldState = getPartialJsonObjectFieldState( args as Record<string, unknown>, testCase.query, ); expect(fieldState).toBe(testCase.result); }); }); }); describe("parsePartialJsonObject inside a unicode escape", () => { const full = `{"a":"x\\uD83D\\uDE00y"}`; it("returns an object for every prefix of a string containing a surrogate-pair escape", () => { for (let cut = 0; cut <= full.length; cut++) { expect( parsePartialJsonObject(full.slice(0, cut)), `cut=${cut}`, ).toBeDefined(); } }); it("omits the unfinished escape from the partial value", () => { expect(parsePartialJsonObject(`{"a":"x\\u`)).toMatchObject({ a: "x" }); expect(parsePartialJsonObject(`{"a":"x\\uD8`)).toMatchObject({ a: "x" }); expect(parsePartialJsonObject(`{"a":"x\\uD83D\\uDE0`)).toMatchObject({ a: "x\uD83D", }); expect(parsePartialJsonObject(`{"a":"x\\uD83D\\uDE00y`)).toMatchObject({ a: "x😀y", }); }); it("keeps the partial path through an escape cut inside a value", () => { const args = parsePartialJsonObject(`{"a":"x\\uD8`)!; expect(getPartialJsonObjectFieldState(args, ["a"])).toBe("partial"); }); it("handles an escape cut inside an object key", () => { expect(Object.keys(parsePartialJsonObject(`{"\\u00e9`)!)).toEqual([]); expect(parsePartialJsonObject(`{"\\u00e9":1`)).toMatchObject({ é: 1 }); expect(parsePartialJsonObject(`{"\\u00e9":"v`)).toMatchObject({ é: "v" }); }); it("keeps malformed escapes unparseable", () => { expect(parsePartialJsonObject(`{"a":"\\uZZ`)).toBeUndefined(); expect(parsePartialJsonObject(`{"a":"\\u"}`)).toBeUndefined(); expect(parsePartialJsonObject(`{"a":"\\u12"}`)).toBeUndefined(); }); it("leaves single-char escapes unchanged", () => { expect(parsePartialJsonObject(`{"a":"x\\n`)).toMatchObject({ a: "x\n" }); expect(parsePartialJsonObject(`{"a":"x\\`)).toMatchObject({ a: "x" }); expect(parsePartialJsonObject(`{"a":"x\\"`)).toMatchObject({ a: 'x"' }); }); }); describe("markPartialJsonObjectComplete", () => { it.each([ '{"__proto__":{"polluted":true}}', '{"constructor":{"prototype":{"polluted":true}}}', ])( "keeps prototype-named fields as own data without relaxing safe parsing: %s", (json) => { const input = JSON.parse(json); expect(parsePartialJsonObject(json)).toBeUndefined(); const complete = markPartialJsonObjectComplete(input); expect(complete).not.toBe(input); expect(Object.entries(complete)).toEqual(Object.entries(input)); expect(Object.getPrototypeOf(complete)).toBe(Object.prototype); expect(Object.prototype).not.toHaveProperty("polluted"); expect(getPartialJsonObjectMeta(complete)?.state).toBe("complete"); expect( getPartialJsonObjectFieldState(complete, [Object.keys(input)[0]!]), ).toBe("complete"); expect(getPartialJsonObjectMeta(input)).toBeUndefined(); }, ); });