Streaming One-Shot Queries

Introduction: Your First Agent Call

In the previous lesson, you learned the conceptual foundation of the Claude Agent SDK — understanding that Claude Code is the local agent runtime and the SDK is your programmatic interface to control it. Now it's time to write your first actual code that launches an agent and handles its response. You'll learn how to use the query() function to send a prompt, process streaming messages, extract text content, and read valuable metrics about the interaction.

Understanding query(): One-Shot Agent Tasks

The query() function is your simplest entry point for interacting with the Claude Code agent. It allows you to send a single prompt to the agent runtime and stream back everything the agent does in response — all without needing to manage a session or conversation history.

Here's what that looks like in code:

import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "Hi Claude! Please introduce yourself.",
  }) as AsyncIterable<SDKMessage>) {
    // Each message represents one step in the agent's process
    console.log(message);
  }
}

main();

When you use query(), the SDK launches a temporary agent instance, sends your prompt as the only message, and then streams back each step of the agent's reasoning and actions. Here's what happens behind the scenes:

  • The SDK starts a short-lived Claude Code agent session using the local runtime.
  • Your prompt is delivered as the sole message in this session.
  • The agent reasons about the task and plans its approach.
  • The agent generates responses based on what it's allowed to do.
    • By default, the agent can see available tools (like Read, Write, or Bash) but cannot execute them without explicit permission configuration. We'll explore tool configuration and permission modes in later lessons.
  • Each step is streamed back to you in real time as a sequence of typed messages.
  • When the agent finishes, the session closes — no memory or context is kept.

This stateless, one-shot approach makes query() ideal for single, self-contained actions such as "summarize this file," "create a script," or "refactor this function." You simply pass your prompt in an options object, and if you need to provide more context, you add it to that same string. Each call is independent and self-contained, with no conversation state carried between calls.

Notice that we're using TypeScript's native async/await patterns with for await...of to iterate over the streaming responses. TypeScript's built-in async support handles all the complexity of managing the asynchronous stream, allowing you to focus on processing each message as it arrives. We cast the result to AsyncIterable<SDKMessage> to help TypeScript understand the type of messages you'll receive.

The Streaming Pattern: How Responses Arrive

The responses stream back as an async iterable that yields message objects as the agent works. Rather than waiting for the entire process to complete, you receive each step as it happens, allowing you to display progress, log actions, or react to specific tool uses in real time. The for await...of loop waits for each message to arrive, processes it, and then waits for the next one until the agent completes its work.

When you run the code, you'll see a sequence of messages arrive:

{
  type: 'system',
  subtype: 'init',
  cwd: '/usercode/FILESYSTEM',
  session_id: 'f901e397-dafc-4c0e-a5df-3884924a8692',
  tools: [
    'Task',          'Bash',
    'Glob',          'Grep',
    'ExitPlanMode',  'Read',
    'Edit',          'Write',
    'NotebookEdit',  'WebFetch',
    'TodoWrite',     'WebSearch',
    'BashOutput',    'KillShell',
    'Skill',         'SlashCommand',
    'EnterPlanMode'
  ],
  mcp_servers: [],
  model: 'claude-sonnet-4-5-20250929',
  permissionMode: 'default',
  slash_commands: [
    'compact',       'context',
    'cost',          'init',
    'pr-comments',   'release-notes',
    'todos',         'review',
    'security-review', 'plan'
  ],
  apiKeySource: 'ANTHROPIC_API_KEY',
  claude_code_version: '2.0.57',
  ...
}

{
  type: 'assistant',
  message: {
    model: 'claude-sonnet-4-5-20250929',
    id: 'msg_015buWzSjpMmhPU7q81jJsTt',
    type: 'message',
    role: 'assistant',
    content: [ [Object] ],
    stop_reason: null,
    stop_sequence: null,
    usage: {
      input_tokens: 3,
      cache_creation_input_tokens: 0,
      cache_read_input_tokens: 13869,
      output_tokens: 2,
      service_tier: 'standard'
    },
    ...
  },
  parent_tool_use_id: null,
  session_id: 'f901e397-dafc-4c0e-a5df-3884924a8692',
  ...
}

{
  type: 'result',
  subtype: 'success',
  is_error: false,
  duration_ms: 7929,
  duration_api_ms: 18656,
  num_turns: 1,
  total_cost_usd: 0.0156247,
  usage: {
    input_tokens: 3,
    cache_creation_input_tokens: 0,
    cache_read_input_tokens: 13869,
    output_tokens: 236,
    server_tool_use: { web_search_requests: 0, web_fetch_requests: 0 },
    service_tier: 'standard',
    ...
  },
  result: "Hi! I'm Claude, an AI assistant created by Anthropic...",
  ...
}

This simple example shows three message types streaming back:

  • System message (type: "system"): Initializes the agent session with configuration details, including the working directory, available tools, model version, slash commands, Claude Code version, and session settings.
  • Assistant message (type: "assistant"): Contains the agent's text response wrapped in content blocks — in this case, a single text block with the introduction.
  • Result message (type: "result"): Provides final metrics about the interaction, including cost, token usage, duration, and the number of turns.

This is a particularly simple interaction where the agent completes the task in a single turn with just a text response. In more complex scenarios, you'd see additional messages streaming through — tool use blocks when the agent calls tools like Read or Bash, tool result blocks showing the output of those tools, and multiple assistant messages as the agent reasons through multiple turns. Each of these messages would arrive in real time as the agent works, giving you complete visibility into its process. Now that you understand how messages stream back, let's learn how to extract the actual text content from them.

Extracting Text from Assistant Messages

To get the actual text the agent is saying, you need to filter the stream for assistant messages and extract text from their content blocks. TypeScript's discriminated unions make this elegant — the message.type property determines which other properties are available. Here's the complete pattern with comments explaining each step:

import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "Hi Claude! Please introduce yourself.",
  }) as AsyncIterable<SDKMessage>) {
    // Use a switch statement to handle different message types
    switch (message.type) {
      case "assistant": {
        // Iterate through the content blocks in the message
        for (const block of message.message.content ?? []) {
          // Check if this block contains text (vs tool uses or other content)
          if (block.type === "text") {
            // Extract and print the actual text string
            console.log(block.text);
          }
        }
        break;
      }
      
      default:
        // Ignore other message types for now
        break;
    }
  }
}

main();

The pattern uses a switch statement on message.type to identify assistant messages, then iterates through the message.message.content array to find text blocks. This type checking is necessary because the stream can contain other message types, and even within an assistant message, the content array might include tool use blocks or other types of content alongside text. The block.text property gives you the agent's natural language response as a string you can display, log, or process further.

Notice the use of the nullish coalescing operator (??) when accessing message.message.content — this provides a safe fallback to an empty array if content is undefined, preventing runtime errors. TypeScript's type system helps catch these potential issues at compile time.

When you run this code, you'll see the agent's introduction:

Hi! I'm Claude, an AI assistant created by Anthropic. 

I'm running as **Claude Code**, which means I'm specifically designed to help you with programming and development tasks. I have access to a variety of tools that let me:

- **Read, write, and edit files** in your codebase
- **Search through code** using pattern matching and grep
- **Execute commands** in the terminal (like git, npm, docker, etc.)
- **Browse the web** for documentation and current information
- **Launch specialized agents** for complex tasks like exploring codebases, planning implementations, or running tests
- **Manage tasks** with todo lists to keep track of multi-step work

I'm here to help with things like:
- Writing and refactoring code
- Debugging issues
- Exploring and understanding codebases
- Setting up projects and dependencies
- Working with git and GitHub
- Running tests and builds
- And much more!

Feel free to ask me to help with any coding task, big or small. What are you working on today?

Extracting Metrics from the Result Message

After the agent completes its work, the final result message provides valuable metrics about the interaction. Here's how to extract and display this information:

import { query, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";

async function main() {
  for await (const message of query({
    prompt: "Hi Claude! Please introduce yourself.",
  }) as AsyncIterable<SDKMessage>) {
    switch (message.type) {
      case "result": {
        console.log("\n--- Result ---");
        // The final result text (same as last assistant message)
        if ("result" in message) {
          console.log("Result: " + message.result);
        }
        // Number of reasoning cycles the agent went through
        console.log("Turns: " + message.num_turns);
        // Total cost in USD for this interaction
        console.log("Cost: $" + message.total_cost_usd.toFixed(4));
        // Token usage details with safe fallbacks
        console.log(
          "Input tokens: " + (message.usage?.input_tokens ?? 0) +
          "\nOutput tokens: " + (message.usage?.output_tokens ?? 0)
        );
        break;
      }
      
      default:
        // Ignore other message types for now
        break;
    }
  }
}

main();

The result message includes the final output in result, the number of reasoning cycles in num_turns, the total cost in total_cost_usd, and detailed token counts in the usage object. The usage object also includes information about cache usage (cache_read_input_tokens) and service tier, which help you understand how efficiently the agent is using cached context. These metrics help you understand the efficiency and cost of your agent interactions — essential information when building applications that make many agent calls.

Notice how we use TypeScript's optional chaining (message.usage?.input_tokens) combined with the nullish coalescing operator (?? 0) to safely access nested properties that might be undefined. We also check if the result property exists using the in operator before accessing it, since not all result messages include this field. These TypeScript patterns help you write robust code that handles edge cases gracefully.

When you run this code, you'll see the metrics output:

--- Result ---
Result: Hi! I'm Claude, an AI assistant created by Anthropic. 

I'm running as **Claude Code**, which means I'm specifically designed to help you with programming and development tasks. I have access to a variety of tools that let me:

- **Read, write, and edit files** in your codebase
- **Search through code** using pattern matching and grep
- **Execute commands** in the terminal (like git, npm, docker, etc.)
- **Browse the web** for documentation and current information
- **Launch specialized agents** for complex tasks like exploring codebases, planning implementations, or running tests
- **Manage tasks** with todo lists to keep track of multi-step work

I'm here to help with things like:
- Writing and refactoring code
- Debugging issues
- Exploring and understanding codebases
- Setting up projects and dependencies
- Working with git and GitHub
- Running tests and builds
- And much more!

Feel free to ask me to help with any coding task, big or small. What are you working on today?
Turns: 1
Cost: $0.0156
Input tokens: 3
Output tokens: 236

This simple introduction task was completed in just one turn, used only 3 input tokens (the minimal prompt), generated 236 output tokens (the introduction text), and cost about 1.6 cents. These metrics matter because they help you understand the efficiency and cost of your agent interactions. If you're building an application that makes many agent calls, monitoring costs becomes crucial. Token counts help you optimize prompts and understand context usage. Turn counts reveal how complex the agent found the task — a simple question might complete in one turn, while a complex coding task might require multiple reasoning cycles.

Summary: The Complete Query Pattern

You've now learned the fundamental pattern for working with the Claude Agent SDK: import the necessary functions and types using ES6 imports with import type for type-only imports, define an async function, call query() with your prompt in an options object, iterate over streaming messages with for await...of, use switch statements on message.type to handle different message types, extract text from blocks where block.type === "text" within message.message.content, leverage TypeScript's type system features like discriminated unions and optional chaining, and read metrics from the final result message. In the practice exercises ahead, you'll write this pattern yourself and build the hands-on experience that will serve you throughout the rest of this course.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal