Skip to main content

Overview

The Vercel AI SDK provides a TypeScript toolkit for building AI applications. ACN integrates as custom tools that your AI can call during conversations.

Define ACN Tools

import { tool } from "ai";
import { z } from "zod";

const ACN_BASE_URL = "https://api.acn.exchange";

const acnDiscover = tool({
  description: "Search ACN marketplace for services matching a natural language query",
  parameters: z.object({
    query: z.string().describe("What capability do you need?"),
  }),
  execute: async ({ query }) => {
    const res = await fetch(`${ACN_BASE_URL}/v1/discover`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ query }),
    });
    return res.json();
  },
});

const acnExecute = tool({
  description: "Execute a service discovered on ACN. Use provider_id and endpoint_slug from discovery results.",
  parameters: z.object({
    provider_id: z.string().describe("Provider ID"),
    endpoint_slug: z.string().describe("Endpoint slug"),
    payload: z.record(z.any()).describe("Request payload matching the endpoint schema"),
  }),
  execute: async ({ provider_id, endpoint_slug, payload }) => {
    const res = await fetch(
      `${ACN_BASE_URL}/v1/execute/${provider_id}/${endpoint_slug}`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.ACN_API_KEY}`,
        },
        body: JSON.stringify(payload),
      }
    );
    return res.json();
  },
});

const acnBalance = tool({
  description: "Check your current ACN balance in USDC",
  parameters: z.object({}),
  execute: async () => {
    const res = await fetch(`${ACN_BASE_URL}/v1/wallet/balance`, {
      headers: { Authorization: `Bearer ${process.env.ACN_API_KEY}` },
    });
    return res.json();
  },
});

Use in a Route Handler

import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-20250514"),
    messages,
    tools: {
      acn_discover: acnDiscover,
      acn_execute: acnExecute,
      acn_balance: acnBalance,
    },
    system: "You are an assistant with access to ACN services. "
          + "Use acn_discover to find services, then acn_execute to call them. "
          + "Always show the user the cost before executing.",
  });

  return result.toDataStreamResponse();
}

Use with generateText

For non-streaming, server-side use:
import { generateText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

const { text } = await generateText({
  model: anthropic("claude-sonnet-4-20250514"),
  tools: {
    acn_discover: acnDiscover,
    acn_execute: acnExecute,
  },
  maxSteps: 5,
  prompt: "Find the current Bitcoin price using ACN",
});

console.log(text);

With Next.js

// app/api/chat/route.ts
import { streamText } from "ai";
import { anthropic } from "@ai-sdk/anthropic";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();

  const result = streamText({
    model: anthropic("claude-sonnet-4-20250514"),
    messages,
    tools: {
      acn_discover: acnDiscover,
      acn_execute: acnExecute,
      acn_balance: acnBalance,
    },
  });

  return result.toDataStreamResponse();
}
The useChat hook on the frontend handles the streaming response automatically:
"use client";
import { useChat } from "@ai-sdk/react";

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div>
      {messages.map((m) => (
        <div key={m.id}>{m.role}: {m.content}</div>
      ))}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} />
      </form>
    </div>
  );
}