Google ADK

Google ADK

Connect to Google ADK (Agent Development Kit) agents with streaming, tool calls, and multi-agent support.

The @assistant-ui/react-google-adk package provides integration with Google ADK JS, Google's official agent framework for TypeScript. It supports streaming text, tool calls, multi-agent orchestration, code execution, session state, tool confirmations, auth flows, and more.

Requirements

You need a Google ADK agent running on a server. ADK supports LlmAgent with Gemini models, tool use, multi-agent orchestration (sequential, parallel, loop agents), and session management.

Installation

npm install @assistant-ui/react @assistant-ui/react-google-adk @google/adk

@google/adk is only needed on the server side. The client-side runtime has no dependency on it.

Getting Started

Create a backend API endpoint

Use createAdkApiRoute to create an API route in one line:

app/api/chat/route.ts
import { createAdkApiRoute } from "@assistant-ui/react-google-adk/server";
import { InMemoryRunner, LlmAgent } from "@google/adk";

const agent = new LlmAgent({
  name: "my_agent",
  model: "gemini-2.5-flash",
  instruction: "You are a helpful assistant.",
});

const runner = new InMemoryRunner({ agent, appName: "my-app" });

export const POST = createAdkApiRoute({
  runner,
  userId: "user_1",
  sessionId: (req) =>
    new URL(req.url).searchParams.get("sessionId") ?? "default",
});

Set up the client runtime

Use createAdkStream to connect to your API route — no manual SSE parsing needed:

components/MyAssistant.tsx
"use client";

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import {
  useAdkRuntime,
  createAdkStream,
} from "@assistant-ui/react-google-adk";
import { Thread } from "@/components/assistant-ui/thread";

export function MyAssistant() {
  const runtime = useAdkRuntime({
    stream: createAdkStream({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Use the component

app/page.tsx
import { MyAssistant } from "@/components/MyAssistant";

export default function Home() {
  return (
    <main className="h-dvh">
      <MyAssistant />
    </main>
  );
}

Setup UI components

Follow the UI Components guide to setup the Thread and other UI components.

createAdkStream

Creates an AdkStreamCallback that connects to an ADK endpoint via SSE. Supports two modes:

Proxy mode — POST to your own API route:

import { createAdkStream } from "@assistant-ui/react-google-adk";

const stream = createAdkStream({ api: "/api/chat" });

Direct mode — connect directly to an ADK server:

const stream = createAdkStream({
  api: "http://localhost:8000",
  appName: "my-app",
  userId: "user-1",
});
OptionTypeDescription
apistringURL to POST to (proxy route or ADK server base URL)
appNamestring?ADK app name (enables direct mode when set)
userIdstring?ADK user ID (required with appName)
headersRecord<string, string> | (() => ...)Static or dynamic request headers

Direct ADK Server Connection

When connecting directly to an ADK server (without a proxy API route), use createAdkSessionAdapter to back your thread list with ADK sessions:

import {
  useAdkRuntime,
  createAdkStream,
  createAdkSessionAdapter,
} from "@assistant-ui/react-google-adk";

const ADK_URL = "http://localhost:8000";

const { adapter, load, artifacts } = createAdkSessionAdapter({
  apiUrl: ADK_URL,
  appName: "my-app",
  userId: "user-1",
});

const runtime = useAdkRuntime({
  stream: createAdkStream({
    api: ADK_URL,
    appName: "my-app",
    userId: "user-1",
  }),
  sessionAdapter: adapter,
  load,
});

The session adapter maps ADK sessions to assistant-ui threads:

  • adapter — a RemoteThreadListAdapter that uses ADK's session REST API for thread CRUD
  • load — reconstructs messages from session events via AdkEventAccumulator
  • artifacts — functions to fetch, list, and delete session artifacts (see Artifact Fetching)
OptionTypeDescription
apiUrlstringADK server base URL
appNamestringADK app name
userIdstringADK user ID
headersRecord<string, string> | (() => ...)Static or dynamic request headers

Server Helpers

createAdkApiRoute

One-liner API route handler that combines request parsing and SSE streaming:

import { createAdkApiRoute } from "@assistant-ui/react-google-adk/server";

export const POST = createAdkApiRoute({
  runner,
  userId: "default-user",
  sessionId: (req) =>
    new URL(req.url).searchParams.get("sessionId") ?? "default",
});

Both userId and sessionId accept a static string or a function (req: Request) => string for dynamic resolution (e.g. from cookies, headers, or query params).

adkEventStream

Converts an AsyncGenerator<Event> from ADK's Runner.runAsync() into an SSE Response. Sends an initial :ok comment to keep connections alive through proxies.

import { adkEventStream } from "@assistant-ui/react-google-adk/server";

const events = runner.runAsync({ userId, sessionId, newMessage });
return adkEventStream(events);

parseAdkRequest / toAdkContent

Lower-level helpers for custom API routes. Parse incoming requests and convert to ADK's Content format. Supports user messages, tool results, stateDelta, checkpointId, and multimodal content:

import { parseAdkRequest, toAdkContent } from "@assistant-ui/react-google-adk/server";

const parsed = await parseAdkRequest(req);
// parsed.type is "message" or "tool-result"
// parsed.config contains runConfig, checkpointId
// parsed.stateDelta contains session state changes

const newMessage = toAdkContent(parsed);
const events = runner.runAsync({
  userId,
  sessionId,
  newMessage,
  stateDelta: parsed.stateDelta,
});
return adkEventStream(events);

Hooks

Agent & Session State

import {
  useAdkAgentInfo,
  useAdkSessionState,
  useAdkSend,
} from "@assistant-ui/react-google-adk";

function MyComponent() {
  // Current active agent name and branch path (multi-agent)
  const agentInfo = useAdkAgentInfo();
  // agentInfo?.name = "search_agent"
  // agentInfo?.branch = "root.search_agent"

  // Accumulated session state delta
  const state = useAdkSessionState();

  // Send raw ADK messages programmatically
  const send = useAdkSend();
}

Tool Confirmations

When ADK's SecurityPlugin or tool callbacks request user confirmation before executing a tool, use useAdkToolConfirmations to read pending requests and useAdkConfirmTool to respond:

import {
  useAdkToolConfirmations,
  useAdkConfirmTool,
} from "@assistant-ui/react-google-adk";

function ToolConfirmationUI() {
  const confirmations = useAdkToolConfirmations();
  const confirmTool = useAdkConfirmTool();

  if (confirmations.length === 0) return null;

  return confirmations.map((conf) => (
    <div key={conf.toolCallId}>
      <p>Tool "{conf.toolName}" wants to run. {conf.hint}</p>
      <button onClick={() => confirmTool(conf.toolCallId, true)}>
        Approve
      </button>
      <button onClick={() => confirmTool(conf.toolCallId, false)}>
        Deny
      </button>
    </div>
  ));
}

Auth Requests

When a tool requires OAuth or other authentication, use useAdkAuthRequests to read pending requests and useAdkSubmitAuth to submit credentials:

import {
  useAdkAuthRequests,
  useAdkSubmitAuth,
  type AdkAuthCredential,
} from "@assistant-ui/react-google-adk";

function AuthUI() {
  const authRequests = useAdkAuthRequests();
  const submitAuth = useAdkSubmitAuth();

  if (authRequests.length === 0) return null;

  return authRequests.map((req) => (
    <div key={req.toolCallId}>
      <button onClick={() => {
        const credential: AdkAuthCredential = {
          authType: "oauth2",
          oauth2: { accessToken: "..." },
        };
        submitAuth(req.toolCallId, credential);
      }}>
        Authenticate
      </button>
    </div>
  ));
}

AdkAuthCredential supports all ADK auth types: apiKey, http, oauth2, openIdConnect, serviceAccount.

Artifacts

Track file artifacts created or modified by the agent:

import { useAdkArtifacts } from "@assistant-ui/react-google-adk";

function ArtifactList() {
  const artifacts = useAdkArtifacts();
  // Record<string, number> — filename to version number
}

Artifact Fetching

When using createAdkSessionAdapter, the returned artifacts object provides functions to fetch artifact content from the ADK server:

const { artifacts } = createAdkSessionAdapter({ apiUrl, appName, userId });

// List all artifact filenames in a session
const filenames = await artifacts.list(sessionId);

// Load artifact content (latest version)
const data = await artifacts.load(sessionId, "document.pdf");
// data.inlineData?.data — base64 content
// data.inlineData?.mimeType — MIME type
// data.text — text content (if text artifact)

// Load a specific version
const v1 = await artifacts.load(sessionId, "document.pdf", 1);

// List all versions
const versions = await artifacts.listVersions(sessionId, "document.pdf");

// Delete an artifact
await artifacts.delete(sessionId, "document.pdf");

Escalation

Detect when an agent requests escalation to a human operator:

import { useAdkEscalation } from "@assistant-ui/react-google-adk";

function EscalationBanner() {
  const escalated = useAdkEscalation();
  if (!escalated) return null;
  return <div>Agent has requested human assistance.</div>;
}

Long-Running Tools

Track tools that are executing asynchronously and awaiting external input:

import { useAdkLongRunningToolIds } from "@assistant-ui/react-google-adk";

function PendingToolsIndicator() {
  const pendingToolIds = useAdkLongRunningToolIds();
  if (pendingToolIds.length === 0) return null;
  return <div>{pendingToolIds.length} tool(s) awaiting input</div>;
}

Per-Message Metadata

Access grounding, citation, and token usage metadata per message:

import { useAdkMessageMetadata } from "@assistant-ui/react-google-adk";

function MessageMetadata({ messageId }: { messageId: string }) {
  const metadataMap = useAdkMessageMetadata();
  const meta = metadataMap.get(messageId);
  // meta?.groundingMetadata — Google Search grounding sources
  // meta?.citationMetadata — citation references
  // meta?.usageMetadata — token counts
}

Session State by Scope

ADK uses key prefixes to scope state. These helpers filter and strip the prefix:

import {
  useAdkAppState,
  useAdkUserState,
  useAdkTempState,
} from "@assistant-ui/react-google-adk";

function StateDebug() {
  const appState = useAdkAppState();   // app:* keys (app-level, shared)
  const userState = useAdkUserState(); // user:* keys (user-level)
  const tempState = useAdkTempState(); // temp:* keys (not persisted)
}

Use useAdkSessionState() for the full unfiltered state delta.

Structured Events

Convert raw ADK events into typed, structured events for custom renderers:

import {
  toAdkStructuredEvents,
  AdkEventType,
  type AdkStructuredEvent,
} from "@assistant-ui/react-google-adk";

const structured = toAdkStructuredEvents(event);
for (const e of structured) {
  switch (e.type) {
    case AdkEventType.CONTENT:
      console.log("Text:", e.content);
      break;
    case AdkEventType.THOUGHT:
      console.log("Reasoning:", e.content);
      break;
    case AdkEventType.TOOL_CALL:
      console.log("Tool:", e.call.name, e.call.args);
      break;
    case AdkEventType.ERROR:
      console.error(e.errorMessage);
      break;
  }
}

State Delta

Send session state mutations along with messages using stateDelta:

const send = useAdkSend();

// Pre-populate session state before the agent runs
send(
  [{ id: "1", type: "human", content: "Start task" }],
  { stateDelta: { taskId: "abc", mode: "verbose" } },
);

This maps to ADK's stateDelta parameter on /run_sse.

RunConfig

Pass AdkRunConfig to control agent behavior:

const send = useAdkSend();

send(messages, {
  runConfig: {
    streamingMode: "sse",
    maxLlmCalls: 10,
    pauseOnToolCalls: true, // pause for client-side tool execution
  },
});

Event Handlers

Listen to streaming events:

const runtime = useAdkRuntime({
  stream: createAdkStream({ api: "/api/chat" }),
  eventHandlers: {
    onError: (error) => {
      console.error("Stream error:", error);
    },
    onAgentTransfer: (toAgent) => {
      console.log("Agent transferred to:", toAgent);
    },
    onCustomEvent: (key, value) => {
      // Fired for each entry in event.customMetadata
      console.log("Custom metadata:", key, value);
    },
  },
});

Thread Management

ADK Session Adapter

Use createAdkSessionAdapter to persist threads via ADK's session API (see Direct ADK Server Connection above).

Custom Thread Management

const runtime = useAdkRuntime({
  stream: createAdkStream({ api: "/api/chat" }),
  create: async () => {
    const sessionId = await createSession();
    return { externalId: sessionId };
  },
  load: async (externalId) => {
    const history = await loadSession(externalId);
    return { messages: history };
  },
  delete: async (externalId) => {
    await deleteSession(externalId);
  },
});

Cloud Persistence

For persistent thread history via assistant-cloud:

import { AssistantCloud } from "assistant-cloud";

const runtime = useAdkRuntime({
  cloud: new AssistantCloud({
    baseUrl: process.env.NEXT_PUBLIC_ASSISTANT_BASE_URL,
    anonymous: true,
  }),
  stream: createAdkStream({ api: "/api/chat" }),
});

Message Editing & Regeneration

Provide a getCheckpointId callback to enable edit and regenerate buttons:

const runtime = useAdkRuntime({
  stream: createAdkStream({ api: "/api/chat" }),
  getCheckpointId: async (threadId, parentMessages) => {
    // Resolve checkpoint ID for server-side forking
    return checkpointId;
  },
});

When getCheckpointId is provided:

  • Edit buttons appear on user messages
  • Regenerate buttons appear on assistant messages

The resolved checkpointId is passed to your stream callback via config.checkpointId.

Without getCheckpointId, edit and regenerate buttons will not appear.

Hooks Reference

HookDescription
useAdkAgentInfo()Current agent name and branch path
useAdkSessionState()Full accumulated session state delta
useAdkAppState()App-level state (app:* prefix, stripped)
useAdkUserState()User-level state (user:* prefix, stripped)
useAdkTempState()Temp state (temp:* prefix, stripped, not persisted)
useAdkSend()Send raw ADK messages
useAdkConfirmTool()Confirm or deny a pending tool confirmation
useAdkSubmitAuth()Submit auth credentials for a pending auth request
useAdkToolConfirmations()Pending tool confirmation requests
useAdkAuthRequests()Pending auth credential requests
useAdkLongRunningToolIds()IDs of long-running tools awaiting input
useAdkArtifacts()Artifact delta (filename → version)
useAdkEscalation()Whether escalation was requested
useAdkMessageMetadata()Per-message grounding/citation/usage metadata

Features

FeatureStatus
Streaming text (SSE)Supported
Tool calls & resultsSupported
Tool confirmations (useAdkConfirmTool)Supported
Auth credential flow (useAdkSubmitAuth)Supported
Multi-agent (author/branch tracking)Supported
Agent transfer eventsSupported
Escalation detectionSupported
Chain-of-thought / reasoningSupported
Code execution (executableCode + result)Supported
Inline images & file dataSupported
Session state delta + scoped stateSupported
Artifact delta tracking + fetchingSupported
Long-running tools (HITL)Supported
Grounding / citation / usage metadataSupported
Structured events (toAdkStructuredEvents)Supported
Typed AdkRunConfigSupported
Client → server stateDeltaSupported
finishReason mapping (17 values)Supported
interrupted event handlingSupported
Snake_case events (Python ADK)Supported
Cloud thread persistenceSupported
ADK session-backed thread persistenceSupported
Direct ADK server connection (no proxy)Supported
One-liner API route (createAdkApiRoute)Supported
Message editing & regenerationSupported
Automatic tool invocationsSupported

ADK Python Backend

The package automatically normalizes snake_case event fields from ADK Python backends to camelCase. No configuration needed — connect to either ADK JS or ADK Python servers. This includes all nested fields: function_callfunctionCall, requested_tool_confirmationsrequestedToolConfirmations, etc.