Read a reply as it arrives
A reply comes in pieces. After each one you can read an object of your shape, with as much in it as has arrived.
pnpm add @niscorp/solid zodThe shape
The schema is the shape you asked the model for. Solid needs it before any text arrives.
import { z } from 'zod';
import { createStream } from '@niscorp/solid';
const Suggestion = z.object({
show: z.string(),
seats: z.array(z.string()),
price_cents: z.number(),
why: z.string(),
});A stream
createStream gives you a stream that already holds an object. For this schema that is every string empty, the number 0 and the list empty. current() reads it.
on is called at once with that object, and then after each piece that changed something.
const stream = createStream({ schema: Suggestion });
console.log('before:', JSON.stringify(stream.current()));
stream.on((suggestion) => console.log(JSON.stringify(suggestion)));The pieces
A model sends a reply a few characters at a time, and a piece can end anywhere: in the middle of a word, or of a number.
write hands Solid one piece. A string is there as far as it has been written. So is a number: 7600 reads as 76 until the rest of it arrives.
const pieces = ['{"show":"The Tem', 'pest","seats":["C4",', '"C5"],"price_cents":76', '00,"why":"Row C."}'];
for (const piece of pieces) stream.write(piece);The end
final() resolves when the reply’s closing brace has arrived. close() says no more text is coming.
stream.close();
console.log('final:', JSON.stringify(await stream.final()));Run it.
The first line is the object before any text. The second is on being called at once. Then one line for each piece: the show’s name half written, the first seat, a price of 76, and the whole reply.
npx tsx reply.mtsbefore: {"show":"","seats":[],"price_cents":0,"why":""}
{"show":"","seats":[],"price_cents":0,"why":""}
{"show":"The Tem","seats":[],"price_cents":0,"why":""}
{"show":"The Tempest","seats":["C4"],"price_cents":0,"why":""}
{"show":"The Tempest","seats":["C4","C5"],"price_cents":76,"why":""}
{"show":"The Tempest","seats":["C4","C5"],"price_cents":7600,"why":"Row C."}
final: {"show":"The Tempest","seats":["C4","C5"],"price_cents":7600,"why":"Row C."}