index
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Mar 17, 2026

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#

<InstallCommand npm={["@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:

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:

"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#

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.