Talk to your agentsVercel ai sdk

Create custom UIs using the Vercel AI SDK

Copy page

Use the Vercel AI SDK's useChat hook to build React chat interfaces that stream responses from your agent.

Overview

Agents built with Inkeep have an /api/chat API endpoint that is compatible with Vercel AI SDK's streaming protocol, so it works out of the box with Vercel's AI SDK ecosystem.

To build totally custom UIs, you have two options in the AI SDK ecosystem:

  • Use useChat hook to build custom UIs in React, Svelte, Vue.js, and Angular. (← this guide)
  • Use the AI Elements library for a shadcn-like chat primitives.

Using useChat

Install the Vercel AI SDK in your React application:

npm install ai @ai-sdk/react

Basic Configuration

Here's a minimal example of using useChat with your agent:

"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState } from "react";

export default function Page() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: "${process.env.NEXT_PUBLIC_INKEEP_AGENTS_RUN_API_URL}/api/chat", // 
      headers: {
        "x-inkeep-tenant-id": "default",
        "x-inkeep-project-id": "event-planner",
        "x-inkeep-agent-id": "event-planner",
      },
    }),
  });
  const [input, setInput] = useState("");

  return (
    <div className="max-w-2xl mx-auto p-4">
      <div className="space-y-4 mb-4">
        {messages.map((message) => (
          <div key={message.id} className="border rounded p-3">
            <div className="font-semibold mb-2">
              {message.role === "user" ? "👤 User" : "🤖 Assistant"}
            </div>

            <div className="space-y-2">
              {message.parts.map((part, partIndex) => {
                const partKey = `${message.id}-${part.type}-${partIndex}`;

                if (part.type === "text") {
                  return (
                    <div key={partKey} className="whitespace-pre-wrap">
                      {part.text}
                    </div>
                  );
                }
                
                return null;
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput("");
          }
        }}
        className="flex gap-2"
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask about weather, request confirmation, or ask for location..."
          className="flex-1 p-2 border rounded"
        />
        <button
          type="submit"
          className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          Send
        </button>
      </form>

      <div className="mt-4 text-sm text-gray-600">
        <p>Try asking:</p>
        <ul className="list-disc list-inside space-y-1">
          <li>"What's the weather in New York?"</li>
          <li>"Can you get my location?"</li>
          <li>"Ask me to confirm something"</li>
        </ul>
      </div>
    </div>
  );
}

Configuration Options

API Endpoint

The api parameter should point to your Run API's chat endpoint:

  • Local development: http://localhost:3003/api/chat
  • Production: https://your-run-api-domain.com/api/chat

Authentication

Choose the authentication method:

See Authentication → Run API for more details.

Optional Configuration

const { messages, sendMessage, status } = useChat({
  api: 'http://localhost:3003/api/chat',
  headers: { /* ... */ },

  // Handle completion
  onFinish: (message) => {
    console.log('Agent finished responding:', message);
  },

  // Handle errors
  onError: (error) => {
    console.error('Chat error:', error);
  },
});

Headers

To pass required headers (validated against your Context Config), include them as custom headers:

const { messages, sendMessage } = useChat({
  api: 'http://localhost:3003/api/chat',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    // Custom context headers
    'x-user-id': 'user-123',
    'x-organization-id': 'org-456',
  },
});
Note
Note

See Headers for details.

Complete Example

Here's a more complete example with conversation management and custom styling:

"use client";

import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport } from "ai";
import { useState } from "react";

export default function Page() {
  const { messages, sendMessage } = useChat({
    transport: new DefaultChatTransport({
      api: "{process.env.NEXT_PUBLIC_INKEEP_AGENTS_RUN_API_URL}/api/chat",
      headers: {
        "x-inkeep-tenant-id": "default",
        "x-inkeep-project-id": "event-planner",
        "x-inkeep-agent-id": "event-planner",
        tz: "US/Pacific",
      },
    }),
  });
  const [input, setInput] = useState("");

  return (
    <div className="max-w-2xl mx-auto p-4">
      <div className="space-y-4 mb-4">
        {messages.map((message) => (
          <div key={message.id} className="border rounded p-3">
            <div className="font-semibold mb-2">
              {message.role === "user" ? "👤 User" : "🤖 Assistant"}
            </div>

            <div className="space-y-2">
              {message.parts.map((part, partIndex) => {
                const partKey = `${message.id}-${part.type}-${partIndex}`;

                if (part.type === "text") {
                  return (
                    <div key={partKey} className="whitespace-pre-wrap">
                      {part.text}
                    </div>
                  );
                }

                if (part.type?.startsWith("data-")) {
                  return (
                    <div
                      key={partKey}
                      className="bg-blue-50 p-3 rounded border-l-4 border-blue-400"
                    >
                      <div className="font-semibold text-blue-700">
                        Data: {JSON.stringify(part, null, 2)}
                      </div>
                    </div>
                  );
                }
                return null;
              })}
            </div>
          </div>
        ))}
      </div>

      <form
        onSubmit={(e) => {
          e.preventDefault();
          if (input.trim()) {
            sendMessage({ text: input });
            setInput("");
          }
        }}
        className="flex gap-2"
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask about weather, request confirmation, or ask for location..."
          className="flex-1 p-2 border rounded"
        />
        <button
          type="submit"
          className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
        >
          Send
        </button>
      </form>

      <div className="mt-4 text-sm text-gray-600">
        <p>Try asking:</p>
        <ul className="list-disc list-inside space-y-1">
          <li>"What's the weather in New York?"</li>
          <li>"Can you get my location?"</li>
          <li>"Ask me to confirm something"</li>
        </ul>
      </div>
    </div>
  );
}

Environment Variables

For production deployments, store configuration in environment variables:

NEXT_PUBLIC_AGENTS_RUN_API_URL=your_domain_for_run_api
NEXT_PUBLIC_INKEEP_API_KEY=your-api-key
NEXT_PUBLIC_TENANT_ID=your-tenant-id
NEXT_PUBLIC_PROJECT_ID=your-project-id
NEXT_PUBLIC_AGENT_ID=your-agent-id
Note
Note

In local development, the Run API runs at http://localhost:3003. For production, update this to your deployed Run API URL.

Next Steps