Building AI Agents with TypeScript AI SDK


Imagine asking your AI assistant: “What’s the latest news on quantum computing breakthroughs, and can you summarise the most significant paper you find?” With basic tool calling, the AI might fetch a URL, read it once, and call it a day. But what if the first result isn’t quite right? What if it needs to search again, check multiple sources, or dig deeper into specific claims?

This is where AI agents shine. Unlike simple tool calls that execute once and stop, agents can think, act, evaluate, and iterate—all on their own. They’re not just calling functions, they’re pursuing goals.

In our previous guide on tool calling, we built a chatbot that could check the time and fetch URLs. That foundation was crucial, but we’re about to level up significantly. In this tutorial, we’ll build a production-ready AI Search and Article Summariser. Think Perplexity, but in your terminal. It autonomously searches the web, analyses multiple sources, and delivers comprehensive summaries. By the end, you’ll understand not just how to build agents, but when they’re the right tool for the job and when simpler patterns make more sense.

What Is an AI Agent?

An AI agent is a program that leverages an LLM and can use tools repeatedly (in a loop) to accomplish complex tasks autonomously. The key word here is “autonomously.” Whilst basic tool calling requires you to explicitly orchestrate each step (call tool, get result, call model again), an agent handles this orchestration itself.

But you’ll likely see the term “agent” used in various ways. I like how Simon Willison has settled on this simple description: An LLM agent runs tools in a loop to achieve a goal..

Basic tool calling is like giving someone a phone and asking them to look up a restaurant’s number—they make one call and report back. An agent is like asking someone to book you a table at a nice Italian restaurant—they’ll search for options, check reviews, call to confirm availability, compare prices, and keep iterating until they’ve accomplished the goal.

The agent’s loop continues until it either completes the task successfully, exhausts its allowed steps, or determines it cannot proceed further. This persistence and autonomy is what makes agents powerful for complex, multi-step workflows.

Agent vs. Tool Calling: The Evolution

If you’ve worked through my previous article, you’ve already used tools with generateText(). You defined tools, the model decided when to call them, and the AI SDK executed them. That’s tool calling in its simplest form.

But here’s what you had to manage manually:

  • The conversation loop: After each tool execution, you needed to append results to the messages array and call generateText() again
  • Stopping conditions: You had to decide when the back-and-forth should end
  • Context management: You controlled what history the model saw at each step
  • Error handling: Tool failures required your explicit intervention

Agents encapsulate all of this. The Experimental_Agent class handles the entire loop, manages conversation history automatically, applies stopping conditions, and gives you clean hooks for observing what’s happening. You focus on defining the agent’s capabilities and goals; the SDK handles the orchestration.

When Should You Build an Agent?

Agents aren’t always the answer. Sometimes you want explicit control over each step. But agents excel when you have:

  • Research and analysis tasks where the path isn’t predetermined. Your user asks a question, and the agent needs to search, read multiple sources, cross-reference information, and synthesise findings. You can’t hard-code this workflow because you don’t know how many searches or which sources will be needed.

  • Multi-step problem solving where decisions depend on previous results. Think of debugging assistance: the agent runs code, analyses the error, searches for solutions, tries fixes, and keeps iterating until the issue is resolved. Each step informs the next, and you can’t know upfront how many iterations will be required. You’re probably familiar with this if you’ve used tools like GitHub Copilot or Claude Code.

  • Dynamic task execution where the agent needs to adapt based on what it discovers. Booking a holiday might involve checking flight availability, comparing hotels, reading reviews, and checking visa requirements. The agent needs to pivot based on what’s available and what constraints it encounters.

  • Exploratory workflows where you want the AI to “think” through a problem creatively. Writing assistance, brainstorming, or research synthesis all benefit from an agent that can iterate on its own thinking.

If your process is predictable and linear, you probably want structured workflow patterns with explicit control flow instead. Agents are for when you need flexibility, persistence, and autonomous decision-making.

Project Setup

Before we dive into code, let’s get your development environment ready. You’ll need Node.js version 22 or later and a Google Gemini API key. If you haven’t got a Gemini API key, you can get one free from Google AI Studio. Our tech stack includes the Vercel AI SDK v5 for agent orchestration, @clack/prompts for beautiful terminal interactions, and @ai-sdk/google for Gemini API integration.

I’ve prepared a GitHub repository with everything you need to follow along. Clone the repository and run pnpm install to install dependencies. You have to create a .env file with your GOOGLE_API_KEY, and you’re ready to go. The starter template is in the project/03-ai-sdk-agent directory and includes basic TypeScript configuration and all required dependencies—no faffing about with configuration.

Building the AI Search and Article Summariser

We’re building an agent that behaves like a mini Perplexity: you ask a question, and it autonomously searches the web, reads relevant articles, and delivers a comprehensive, sourced summary. The brilliance of this example is that it uses two provider-native tools built directly into Gemini. The google_search and url_context tools allow us to access web content and context without needing to write code to find links and crawl pages.

Here’s the user experience we’re creating: you type a question like “What are the latest developments in AI reasoning models?” The agent searches Google, evaluates which results look promising, fetches and analyses those URLs, synthesises the information, and presents you with a summary complete with source citations. All of this happens automatically without you orchestrating each step.

The agent decides how many searches to perform, which URLs to read, and when it has enough information to answer confidently. That’s the power of autonomous behaviour.

Introducing Experimental_Agent

The Experimental_Agent class is your high-level interface for building agents with the AI SDK. It handles everything we discussed earlier—the conversation loop, context management, and stopping conditions—whilst giving you a clean, declarative API.

In v6 this class will likely be called simply Agent or ToolLoopAgent, but for now, the Experimental_Agent name signals that this is experimental functionality subject to change.

Why use Experimental_Agent instead of manually managing the loop with generateText()? Consider what you’d need to implement yourself: an array to store conversation history, a while loop that continues until some condition is met, maybe logic to append tool results to the history, and stopping conditions to prevent infinite loops. That’s a lot of boilerplate code, and getting it wrong means buggy agents or runaway API costs.

Experimental_Agent does all of this for you whilst providing type safety and configurability. You define your agent once, and you can use it throughout your application with complete confidence that the loop mechanics are handled correctly.

Code Implementation

Let’s build this program step by step. We’ll start with the foundations and progressively add functionality until we have a complete, production-ready agent.

Initial Setup and Imports

First, we need our dependencies and some basic configuration. Create a new file called search-agent.ts:

import { Experimental_Agent as Agent, stepCountIs } from "ai";
import { google } from "@ai-sdk/google";
import * as prompts from "@clack/prompts";

// We'll use stepCountIs to limit the agent to a maximum number of steps
// This prevents runaway loops and controls costs
const MAX_AGENT_STEPS = 10;

These imports give us everything we need: Agent for building our agent, stepCountIs for controlling how long the agent can run, the Google provider for accessing Gemini’s models and tools, and Clack’s prompts for beautiful terminal interactions.

The MAX_AGENT_STEPS constant is important. Each “step” is one generation. Either the model generates text (completing the task) or it calls a tool (continuing the loop). By limiting steps, we ensure the agent can’t run indefinitely if something goes wrong.

Configuring the Agent

Now for the heart of our application—defining the agent itself:

const searchAgent = new Agent({
  model: google("gemini-2.5-flash"),

  system: `You are an expert research assistant. When users ask questions:
  
1. Search the web to find relevant, recent information
2. Read the most promising sources using the url_context tool
3. Synthesise information from multiple sources
4. Provide a comprehensive answer with proper citations

Always aim to verify claims across multiple sources before presenting them as fact.
Keep your responses clear and well-structured.`,

  tools: {
    google_search: google.tools.googleSearch({}),
    url_context: google.tools.urlContext({}),
  },

  stopWhen: stepCountIs(MAX_AGENT_STEPS),
});

Let’s unpack this configuration.

The model is Gemini 2.5 Flash, which offers an excellent balance of speed, cost, and capability for agent workflows. The system field is where we define the agent’s behaviour and strategy. Notice we’re not just saying “be helpful”; we’re giving it a specific method: search first, read sources, synthesise, and cite. This guidance shapes how the agent approaches tasks.

The google_search tool lets the agent search Google and receive structured results, while the url_context tool allows it to fetch and understand the full content of any URL. Together, these tools give the agent genuine research capabilities without us writing a single line of web scraping code.

Finally, stopWhen: stepCountIs(MAX_AGENT_STEPS) ensures the agent can’t exceed ten steps. This is our safety net against infinite loops whilst still giving the agent enough runway to search, read multiple sources, and synthesise findings.

Building the User Interface

Let’s create a clean terminal interface for interacting with our agent:

async function main() {
  prompts.intro("🔍 AI Search and Article Summariser");

  console.log("\nAsk me anything, and I'll search the web for answers!");
  console.log('Type "exit" to quit.\n');

  while (true) {
    const userQuery = await prompts.text({
      message: "Your question:",
      placeholder: "e.g., What are the latest AI breakthroughs?",
      validate: (value) => {
        if (!value) return "Please enter a question";
      },
    });

    if (prompts.isCancel(userQuery)) {
      prompts.cancel("Search cancelled.");
      process.exit(0);
    }

    if (userQuery === "exit") {
      prompts.outro("👋 Thanks for using AI Search!");
      break;
    }

    // We'll handle the agent interaction here next
  }
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

We use @clack/prompts for a polished user experience in the terminal. The intro() call creates a nice header, we show usage instructions, and then we enter an infinite loop prompting for questions. The validate function ensures users can’t submit empty queries, and we handle both Ctrl+C cancellation and explicit “exit” commands gracefully.

Step 4: Processing Agent Responses

Now let’s connect the user interface to our agent and handle its responses:

// Inside the while loop, after getting userQuery:

const spinner = prompts.spinner();
spinner.start("Researching your question...");

try {
  const result = await searchAgent.generate({
    prompt: userQuery,
  });

  spinner.stop("Research complete!");

  // Display the agent's response
  prompts.note(result.text, "Answer");

  // Show some transparency about what the agent did
  console.log(`\n💡 Research process: ${result.steps.length} steps taken\n`);
} catch (error) {
  spinner.stop("Error occurred");
  console.error("\n❌ Failed to process your question:", error);
  console.log("Let's try again...\n");
}

This is where the magic happens. We call searchAgent.generate() with the user’s question, and the agent takes over completely. It will search, read sources, iterate as needed, and return when it has an answer or hits the step limit.

The result object contains several useful properties. The text field is the agent’s final response, i.e. the answer to the user’s question. The steps array contains a record of everything the agent did: which tools it called, what parameters it used, and what results it received. This is invaluable for debugging and understanding agent behaviour.

Notice we’re showing the step count to users. This transparency helps them understand that genuine research is happening behind the scenes. When they see "7 steps taken," they know the agent didn’t just guess — it actually searched, read sources, and reasoned through the answer.

Complete Implementation

Here’s the full, production-ready code in one place. Save this as search-agent.ts if you haven’t already:

// Complete implementation - save as search-agent.ts
import { Experimental_Agent as Agent, stepCountIs } from "ai";
import { google } from "@ai-sdk/google";
import * as prompts from "@clack/prompts";

const MAX_AGENT_STEPS = 10;

const searchAgent = new Agent({
  model: google("gemini-2.5-flash"),

  system: `You are an expert research assistant. When users ask questions:

1. Search the web to find relevant, recent information
2. Read the most promising sources using the url_context tool
3. Synthesise information from multiple sources
4. Provide a comprehensive answer with proper citations

Always aim to verify claims across multiple sources before presenting them as fact.
Keep your responses clear and well-structured.`,

  tools: {
    google_search: google.tools.googleSearch({}),
    url_context: google.tools.urlContext({}),
  },

  stopWhen: stepCountIs(MAX_AGENT_STEPS),
});

async function main() {
  prompts.intro("🔍 AI Search and Article Summariser");

  console.log("\nAsk me anything, and I'll search the web for answers!");
  console.log('Type "exit" to quit.\n');

  while (true) {
    const userQuery = await prompts.text({
      message: "Your question:",
      placeholder: "e.g., What are the latest AI breakthroughs?",
      validate: (value) => {
        if (!value) return "Please enter a question";
      },
    });

    if (prompts.isCancel(userQuery)) {
      prompts.cancel("Search cancelled.");
      process.exit(0);
    }

    if (userQuery === "exit") {
      prompts.outro("👋 Thanks for using AI Search!");
      break;
    }

    const spinner = prompts.spinner();
    spinner.start("Researching your question...");

    try {
      const result = await searchAgent.generate({
        prompt: userQuery,
      });

      spinner.stop("Research complete!");
      prompts.note(result.text, "Answer");
      console.log(
        `\n💡 Research process: ${result.steps.length} steps taken\n`,
      );
    } catch (error) {
      spinner.stop("Error occurred");
      console.error("\n❌ Failed to process your question:", error);
      console.log("Let's try again...\n");
    }
  }
}

main().catch((error) => {
  console.error("Fatal error:", error);
  process.exit(1);
});

To run your agent, execute:

node --env-file=.env search-agent.ts

Let’s put your agent through its paces with some real-world queries that showcase its capabilities.

Query 1: Current Events Try asking: “What are the top stories on Hacker News right now?”

The agent will search for Hacker News, use url_context to fetch the homepage, parse the trending stories, and present you with a structured list.

Query 2: Technical Research Ask: “What is the Model Context Protocol, and why does it matter for AI development?”

Query 3: Comparative Analysis Try: “Compare the latest AI reasoning models from OpenAI and Google”

Here’s what a typical session looks like:

┌  🔍 AI Search and Article Summariser

Ask me anything, and I'll search the web for answers!
Type "exit" to quit.

◇  Your question:
│  What are the latest developments in AI reasoning models?

◆  Researching your question...

◇  Research complete!

◇  Answer ─────────────────────────────────────────────────────────

│  Recent developments in AI reasoning models show significant
│  progress across multiple fronts. OpenAI's o1 model series
│  introduces "chain of thought" reasoning that allows models to
│  spend more time thinking before responding, particularly
│  excelling in mathematics and coding challenges.

│  Google has announced Gemini 2.0, which features enhanced
│  reasoning capabilities and can now plan multi-step tasks
│  autonomously. The model shows particular strength in scientific
│  reasoning and complex problem decomposition.

│  A key trend is the shift from simply scaling model size to
│  improving inference-time compute—essentially teaching models
│  to "think longer" rather than just "know more." This approach
│  has yielded impressive results on benchmarks like GPQA and MATH.

│  Sources: OpenAI's technical blog (December 2024), Google AI
│  announcements, Nature journal coverage of reasoning model advances

├────────────────────────────────────────────────────────────────────

💡 Research process: 1 step taken

Key Takeaways and Next Steps

You’ve just built a useful AI search agent, and more importantly, you understand the fundamental shift from basic tool calling to autonomous agent behaviour. Let’s crystallise the core lessons:

What an AI agent is: An agent is an LLM that uses tools repeatedly in a loop to autonomously accomplish complex tasks, managing its own workflow from start to finish rather than requiring you to orchestrate each step manually.

When to build one: Reach for agents when your task requires multi-step reasoning, adaptive decision-making, or workflows where you can’t predict the exact sequence of actions upfront. Things like research, debugging, creative problem-solving, and exploratory analysis are perfect use cases.

How to build with AI SDK: The Experimental_Agent class encapsulates all the complexity of agent loops, giving you a declarative API where you focus on defining capabilities (tools) and behaviour (system instruction) whilst the SDK handles orchestration, context management, and stopping conditions.

Now that you’ve mastered the fundamentals, here’s how you can extend this foundation:

Add custom tools to give your agent domain-specific capabilities. Following the patterns from our previous tool calling guide, you could add tools for querying your company’s database, sending emails, or interfacing with any API. The agent would then weave these capabilities into its autonomous workflows.

Build specialised agents for different domains — a code review agent with tools for running tests and analysing security, a content agent that can research topics and draft articles, or a data analysis agent that can query databases and generate visualisations. The pattern you’ve learnt applies to any autonomous workflow.

The world of AI agents is evolving rapidly, and you’re now equipped to build genuinely capable systems. In future guides, we’ll explore multi-agent architectures where specialised agents collaborate, stop conditions for fine-grained control, and streaming responses so users see the agent’s thinking in real-time.

But for now, take what you’ve learnt and build something remarkable. Get the source code from the GitHub repository.


Found this guide helpful? Share it with your developer friends and colleagues. If you have questions or want to share what you’ve built, I’d love to hear from you on Twitter or GitHub. Happy building! 🚀

Subscribe to newsletter

Subscribe to receive expert insights on high-performance Web and Node.js optimization techniques, and distributed systems engineering. I share practical tips, tutorials, and fun projects that you will appreciate. No spam, unsubscribe anytime.