Skip to content

Handling Responses

session.ask() returns an AskStream — an AsyncIterable<StreamEvent> with a .result() method that reduces the stream into a TurnResult. There are two ways to consume it: iterate events as they arrive for live output, or await .result() for the reduced TurnResult. You can also mix the two — iterate first, then call .result() to get the cached summary.

Consuming Events

Iterate with for await...of to process events as they arrive:

const stream = session.ask("What does this repo do?");
for await (const event of stream) {
switch (event.type) {
case "text_delta":
process.stdout.write(event.delta);
break;
case "tool_use_start":
console.log(`\nUsing tool: ${event.name}`);
break;
case "tool_result":
console.log(`Tool ${event.name} completed in ${event.durationMs}ms`);
break;
case "error":
console.error(`Error: [${event.errorType}] ${event.message}`);
break;
}
}

Getting the TurnResult

Call .result() to get the reduced TurnResult. This works whether or not you iterated the stream:

// Without iterating — starts and drains the stream internally
const result = await session.ask("What does this repo do?").result();
// After iterating — returns the cached result immediately
const stream = session.ask("What does this repo do?");
for await (const event of stream) { /* ... */ }
const result = await stream.result();

Event Types

Turn lifecycle

EventDescription
turn_startEmitted once at the start. Carries turnId, prompt, timestamp.
turn_endEmitted once at the end. Carries metadata and usage.

Content generation

EventDescription
text_deltaStreaming chunk of the assistant’s visible response text.
textComplete text block (emitted after all text_deltas for a block).
thinking_deltaStreaming chunk of the model’s internal reasoning (when thinking is enabled).
thinkingComplete thinking text for the current iteration.
thinking_summary_deltaStreaming chunk of a thinking summary.
thinking_summaryComplete thinking summary for the current iteration.

Tool execution

EventDescription
tool_use_startTool call initiated. name is known, arguments not yet streamed.
tool_use_deltaStreaming chunk of tool argument JSON.
tool_use_endArguments fully parsed in params. Tool execution starts.
tool_resultTool execution complete. Carries output, isError, durationMs.

Other

EventDescription
iteration_startMarks the start of each LLM inference iteration (zero-based index).
compactionContext was compacted. Carries summary, tokensBefore, tokensAfter.
errorUnrecoverable error during the turn. See Handling Errors.

TurnResult Structure

The TurnResult is an immutable snapshot of everything that happened in a turn:

interface TurnResult {
id: string; // Unique turn ID
prompt: string; // The prompt that started this turn
steps: readonly Step[]; // Ordered record of everything the agent did
usage: TokenUsage; // Token counts across all iterations
metadata: TurnMetadata; // Timing, model, config snapshot
error: { ... } | null; // Non-null if the turn ended in error
startedAt: number; // Epoch ms
endedAt: number; // Epoch ms
}

Steps include text, thinking, thinking_summary, tool_call, iteration_start, compaction, and error records.

Per-Turn Overrides

Pass AskOptions as the second argument to ask() to override session-level settings for a single turn:

// Override the model for one question
const stream = session.ask("Analyze the security of this code", {
model: { provider: "anthropic", id: "claude-opus-4-6" },
maxIterations: 30,
thinking: { type: "adaptive", effort: "high" },
});
OptionTypeDescription
modelModelConfigOverride the model for this turn.
maxIterationsnumberOverride max iterations for this turn.
thinkingThinkingConfigOverride thinking config for this turn.
afterTurnstringBranch from after a specific turn ID. See Multi-turn Conversations.
signalAbortSignalCancel the turn mid-stream.

Cancellation

Use an AbortController to cancel a turn:

const controller = new AbortController();
const stream = session.ask("Search the entire codebase for vulnerabilities", {
signal: controller.signal,
});
// Cancel after 30 seconds
setTimeout(() => controller.abort(), 30_000);
for await (const event of stream) {
if (event.type === "error" && event.errorType === "aborted") {
console.log("Turn was cancelled");
}
}

Behavior Notes

  • Lazy start: The stream does not begin until you consume it (iterate or call .result()).
  • Single consumption: A stream can only be iterated once. Attempting to iterate again throws "AskStream is already being consumed".
  • Serialized asks: Concurrent ask() calls on the same session are serialized — each waits for the previous to complete. Because streams are also lazy, an AskStream you create but never consume will block every subsequent ask() on the session indefinitely. Always iterate or await .result() on every stream you create — even if you only want to discard it (await session.ask("...").result().catch(() => {})). If you need true parallelism, use separate sessions.
  • Cacheable result: .result() can be called multiple times — subsequent calls return the cached TurnResult.