assistant-stream
Version:
Streaming utilities for AI assistants
52 lines (45 loc) • 2 kB
text/typescript
import type { ReadonlyJSONObject } from "./json-value";
/** Retained backends must preserve their arguments. */
export type JsonParserBackend = {
readonly currentArgs: ReadonlyJSONObject;
append(delta: string): JsonParserBackend | undefined;
};
export type JsonParserBackendContext = {
readonly text: string;
readonly fallback: ReadonlyJSONObject;
/** Detaches mutable values and attaches assistant-stream's partial-field metadata; build every `currentArgs` snapshot with it. */
readonly createSnapshot: (
value: ReadonlyJSONObject,
state: "partial" | "complete",
partialPath: readonly (string | number)[],
) => ReadonlyJSONObject;
};
/** Return undefined for input that cannot preserve the native parser's values and metadata. */
export type JsonParserBackendFactory = (
context: JsonParserBackendContext,
) => JsonParserBackend | undefined;
const registryKey = Symbol.for("assistant-stream.json-parser-backends.v1");
type Registration = { factory: JsonParserBackendFactory };
const registryHost = globalThis as typeof globalThis & {
[registryKey]?: Registration[];
};
/**
* Registers a backend for new shared JSON parsers in this JavaScript realm.
* The most recent registration takes precedence, including across package copies.
* The backend must preserve native values, metadata, and independent branches;
* returning undefined or throwing falls back to native parsing.
* Unregistering removes only this registration and leaves existing parsers intact.
*/
export const registerJsonParserBackend = (
factory: JsonParserBackendFactory,
): (() => void) => {
const registrations = (registryHost[registryKey] ??= []);
const registration = { factory };
registrations.push(registration);
return () => {
const index = registrations.indexOf(registration);
if (index !== -1) registrations.splice(index, 1);
};
};
export const createJsonParserBackend = (context: JsonParserBackendContext) =>
registryHost[registryKey]?.at(-1)?.factory(context);