Text generation · Handle event types in a completion stream8 / 8
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06
  7. 07

Handle event types in a completion stream

Example on GitHub(packages/sdk/examples/completion-events.ts)

Earlier chapters used result.tokenStream, a flat string iterable of the model's text. The stream gives you raw output, but content and thinking look identical without type tags. The events surface dispatches each by type, so they arrive separately.

On a completion run, this surface is the canonical one. Each item carries a payload: a content delta, a thinking block, a tool call, a stats frame, the terminal done, or the raw text.

The previous lesson covered contentDelta and thinkingDelta firing side by side. This one covers the full set, plus the result.final promise that joins them all into one object.

Here's how you'd dispatch the events in a streaming loop:

for await (const event of result.events) {
  switch (event.type) {
    case "contentDelta":
      process.stdout.write(event.text);
      break;
    case "thinkingDelta":
      process.stderr.write(`[think] ${event.text}`);
      break;
    case "toolCall":
      console.log(`▸ tool ${event.call.name}(${JSON.stringify(event.call.arguments)})`);
      break;
    case "completionStats":
      console.log(`▸ ${event.stats.tokensPerSecond?.toFixed(1)} tok/s`);
      break;
    case "completionDone":
      break;
  }
}

contentDelta is the model's text token-by-token, written to stdout. thinkingDelta is the chain-of-thought stream, written to stderr with a [think] prefix so the reasoning doesn't get mixed into the user-visible response. toolCall marks a function-call emission in the response stream. completionStats carries throughput numbers.

After the loop, await result.final joins them into one object: contentText, thinkingText, toolCalls, stats, stopReason, raw.fullText. Reading the aggregate would look like this:

console.log();
const final = await result.final;
console.log(`▸ Final contentText: ${final.contentText}`);
console.log(`▸ Stop reason: ${final.stopReason}`);

Note: tokenStream still works for simple cases, but new code should consume events for streaming and final.contentText for the aggregated result.

Put it to the test

  1. Iterate result.events with a for await ... { switch (event.type) { ... } }. Cases: contentDelta writes event.text to stdout, thinkingDelta writes [think] ${event.text} to stderr, completionDone breaks.
  2. After the loop, await result.final and log final.contentText and final.stopReason.
index.ts

$ Run your code to see results

$