Streaming
A completion arrives as a sequence of events. The client accumulates chunks into a streamingMessage and commits it to the conversation when the completion ends.
The lifecycle
completion.started → isStreaming = true
streamingMessage = { role: 'assistant', message: '' }
completion_stream * → streamingMessage.message += chunk
function_call.begin * → record a pending function call
function_call.end * → record its result
function_call_iteration → reset for the next round (the autopilot may chain calls)
completion.ended → push streamingMessage onto conversationMessages
streamingMessage = null
completion.finished → isStreaming = false
user_input → server is waiting for user input* means the event can fire any number of times.
Reading streaming state
ts
client.isStreaming(); // boolean
client.getStreamingMessage(); // ConversationMessage | null (copy)Listening
ts
client.on('completionStarted', () => console.log('thinking…'));
client.on('completionStream', ({ chunk }) => {
process.stdout.write(chunk);
});
client.on('functionCallBegin', ({ name, arguments: args }) => {
console.log('calling', name, args);
});
client.on('functionCallEnd', ({ id, result }) => {
console.log('done', id, result);
});
client.on('completionEnded', () => console.log('committed'));
client.on('completionFinished', () => console.log('idle'));Stopping mid-stream
ts
client.interruptStreaming();Asks the server to stop. The current streamingMessage is committed as-is and completionEnded fires.
With the Pinia store
If you are using useAutopilotStore(), streamingMessage, isStreaming, and hasStreamingMessage are reactive — bind them directly in a template, no event listeners needed:
vue
<template>
<article v-if="store.streamingMessage">
{{ store.streamingMessage.message }}
</article>
</template>