Sending messages & streaming
sendMessage() (Node.js) / send_message() (Python) 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}turn = await finn.send_message("How many active members do I have?")
async for chunk in turn: print(chunk.text, end="", flush=True) # each chunk is a piece of the answerEach chunk is a TokenChunk — text plus
messageId/message_id and conversationId/conversation_id.
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,// }result = await turn.completed# TurnResult(# conversation_id='conv-…',# message_id='msg-…',# token_usage={'input': 412, 'output': 88, 'total': 500},# turn_state='idle',# interrupted=False,# )You can also get the conversation id before the stream finishes — useful for persisting it early:
const conversationId = await turn.conversationId;conversation_id = await turn.conversation_idNot streaming? Use ask()
Section titled “Not streaming? Use ask()”If you just want the final answer in a single call, use ask() instead. 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 shotresult = await finn.ask("How many active members do I have?")print(result.text) # the whole answer, one shot# result also has .conversation_id, .token_usage, ...ask takes the same options as sendMessage/send_message (so conversationId
and systemPrompt work the same way) and returns an
AskResult — a TurnResult plus a text
field. It fails with a FinnError if the turn
breaks, exactly like the stream would.
Want JSON? Use askJson()
Section titled “Want JSON? Use askJson()”For programmatic use, askJson() (Node.js) / ask_json() (Python)
returns parsed JSON instead of text. It calls ask(), extracts the JSON from the
answer (stripping ```json fences and any prose around it), and 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);data = await finn.ask_json( "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.")print(data[0]["active_members"]) # already a Python object (list/dict)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); // trueimport asyncio
turn = await finn.send_message("Summarize every transaction this year…")
loop = asyncio.get_event_loop()loop.call_later(2, turn.interrupt) # change your mind after 2s
async for chunk in turn: print(chunk.text, end="", flush=True)result = await turn.completedprint("interrupted?", result.interrupted) # TrueThe stream ends cleanly and interrupted is true/True.