Sending messages & streaming
sendMessage() returns a MessageTurn — an async-iterable you loop over to
read tokens as Finn generates them.
const turn = finn.sendMessage('How many active members do I have?');
for await (const chunk of turn) { process.stdout.write(chunk.text); // each chunk is a piece of the answer}Each chunk is a TokenChunk:
{ text: string; messageId: string; conversationId: string }The final result
Section titled “The final result”After the stream ends, await turn.completed for the
TurnResult — the conversation id, token
usage, and final state:
const result = await turn.completed;// {// conversationId: 'conv-…',// messageId: 'msg-…',// tokenUsage: { input: 412, output: 88, total: 500 },// turnState: 'idle',// interrupted: false,// }You can also get the conversation id before the stream finishes — useful for persisting it early:
const conversationId = await turn.conversationId;Not streaming? Use ask()
Section titled “Not streaming? Use ask()”If you just want the final answer in a single call, use ask() instead of
sendMessage(). It sends the message, accumulates the streamed tokens for you,
and resolves once with the full text plus the turn result — no loop required.
const { text, conversationId, tokenUsage } = await finn.ask( 'How many active members do I have?',);console.log(text); // the whole answer, one shotask(text, options) takes the same SendMessageOptions
as sendMessage (so conversationId and systemPrompt work the same way), and
returns an AskResult — a TurnResult plus a
text field. It rejects with a FinnError if the
turn fails, exactly like the stream would throw.
Want JSON? Use askJson()
Section titled “Want JSON? Use askJson()”For programmatic use, askJson<T>() returns parsed JSON instead of text. It
calls ask(), extracts the JSON from the answer (stripping ```json fences and
any prose around it), and JSON.parses it.
const data = await finn.askJson(` How many active members did I have over the last 3 months? Return ONLY a JSON array like [{ "month": "2026-06", "active_members": 0 }]. No prose, no markdown.`);console.log(data[0].active_members); // already an objectWith TypeScript you assert the shape:
type Row = { month: string; active_members: number };const rows = await finn.askJson<Row[]>(prompt);Interrupting a turn
Section titled “Interrupting a turn”Call interrupt() to ask the backend to stop generating the current turn:
const turn = finn.sendMessage('Summarize every transaction this year…');
setTimeout(() => turn.interrupt(), 2000); // change your mind after 2s
for await (const chunk of turn) process.stdout.write(chunk.text);const result = await turn.completed;console.log('interrupted?', result.interrupted); // trueThe stream ends cleanly and result.interrupted is true.