Skip to content

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 }

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;

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 shot

ask(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.

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 object

With TypeScript you assert the shape:

type Row = { month: string; active_members: number };
const rows = await finn.askJson<Row[]>(prompt);

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); // true

The stream ends cleanly and result.interrupted is true.