Hooks

Tool confirmations, auth, input requests, artifacts, escalation, metadata, structured events.

Reference for the runtime's React hooks. These expose ADK-specific state and HITL primitives. Start with quickstart if you have not wired up a basic runtime yet.

Agent and 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 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, ADK pauses it and emits an adk_request_credential call carrying the auth config it issued. useAdkAuthRequests lists the calls still waiting, and useAdkSubmitAuth answers one with a credential. The reply is that request's authConfig with the credential as its exchangedAuthCredential, which is the shape ADK resumes the tool on, so the id has to be one the list currently holds; a stale one throws instead of sending a reply ADK would ignore.

For OAuth2 and OpenID Connect the app owns the browser round trip: open authConfig.exchangedAuthCredential.oauth2.authUri, then submit the URL ADK redirected the user back to as oauth2.authResponseUri (or the code alone as oauth2.authCode). ADK exchanges the code for tokens itself. adk-js reads nothing else off an OAuth2 reply; adk-python also accepts a credential that already carries oauth2.accessToken and stores it without an exchange. The examples call an app-owned completeOAuth(authUri) that resolves with that redirect URL.

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

const authUriOf = (authConfig: unknown) =>
  (authConfig as { exchangedAuthCredential?: AdkAuthCredential } | undefined)
    ?.exchangedAuthCredential?.oauth2?.authUri;

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

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

  return authRequests.map((req) => (
    <div key={req.toolCallId}>
      <button
        onClick={async () => {
          const authResponseUri = await completeOAuth(authUriOf(req.authConfig));
          submitAuth(req.toolCallId, {
            authType: "oauth2",
            oauth2: { authResponseUri },
          });
        }}
      >
        Sign in
      </button>
    </div>
  ));
}

AdkAuthCredential supports all ADK auth types: apiKey, http, oauth2, openIdConnect, serviceAccount. A scheme without a browser round trip takes the credential directly, such as submitAuth(req.toolCallId, { authType: "apiKey", apiKey }).

Input requests

When an ADK Python 2.0+ Workflow's RequestInput node pauses execution to ask the user a question, ADK emits an adk_request_input function call marked as long-running. Respond with useAdkSubmitInput inside a tool UI; the helper wraps the answer as { result } to match ADK's unwrap_response contract, so the Workflow node resumes with the unwrapped value:

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { useAdkSubmitInput } from "@assistant-ui/react-google-adk";

type RequestInputArgs = {
  interrupt_id?: string;
  message?: string;
  payload?: unknown;
  response_schema?: unknown;
};

export const RequestInputToolUI: ToolCallMessagePartComponent<
  RequestInputArgs,
  unknown
> = function RequestInputUI({ toolCallId, args, result }) {
    const submitInput = useAdkSubmitInput();

    if (result !== undefined) {
      return <p>Answered: {String(result)}</p>;
    }

    return (
      <form
        onSubmit={(e) => {
          e.preventDefault();
          const value = (
            e.currentTarget.elements.namedItem("answer") as HTMLInputElement
          ).value;
          submitInput(toolCallId, value);
        }}
      >
        <p>{args.message ?? "Please provide input:"}</p>
        <input name="answer" autoFocus />
        <button type="submit">Submit</button>
      </form>
    );
};

Register the renderer with a backend toolkit entry inside AssistantRuntimeProvider:

import {
  AssistantRuntimeProvider,
  AuiConfig,
  defineToolkit,
  Tools,
} from "@assistant-ui/react";
import {
  createAdkStream,
  useAdkRuntime,
} from "@assistant-ui/react-google-adk";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";

const toolkit = defineToolkit({
  adk_request_input: {
    type: "backend",
    render: RequestInputToolUI,
  },
});

function App() {
  const runtime = useAdkRuntime({
    stream: createAdkStream({ api: "/api/chat" }),
  });
  const config = AuiConfig({ tools: Tools({ toolkit }) });
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      <Thread />
    </AssistantRuntimeProvider>
  );
}

Info

adk_request_input is emitted only by ADK Python 2.0+ Workflow RequestInput nodes; ADK JS has no equivalent. Always respond via a tool UI with useAdkSubmitInput. HITL interrupts are automatically exempt from autoCancelPendingToolCalls, so typing a normal message in the composer will not overwrite the pending interrupt.

useAdkSubmitInput is sugar over the generic addResult. If you prefer, call addResult({ result: value }) from inside the render function directly. The { result } wrapper is required either way: the adapter JSON-stringifies the value before sending, and ADK's unwrap_response unwraps it on the backend before the Workflow node resumes.

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 (see api reference), 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.fileData?.fileUri — external artifact URI
// data.fileData?.mimeType — MIME type (if provided)
// 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>;
}

This hook reports every tool call ADK marked via long_running_tool_ids, including HITL interrupts, until the client sends its first response for that call. That first response ends the report even when it is only a progress update, because nothing in the event tells a progress update apart from a final response. A value the long-running tool itself returns reaches the thread as an interim result and leaves the call pending. To respond to a specific HITL type, see tool confirmations, auth requests, or input requests.

That same set decides which tool calls the client owns. A registered frontend tool's execute runs only for a call ADK marked long-running, because ADK resolves every other call itself and yields the call to the client before its own response. Register a tool the client executes as a LongRunningFunctionTool on the agent.

The client answers the long-running calls of one run together. Answers from addToolResult, approval replies, and the HITL helpers show in the thread at once but wait while any run is active. Once no run is active, held replies and replies given in the same tick go out one producing run per request. For calls loaded with the thread, one assistant message takes the place of a producing run. Client-owned long-running calls from the same run wait until all have answers, then go out in the order the calls appear in the thread. An interrupt answer does not wait for other calls. A held answer remains in useAdkLongRunningToolIds until it is sent, but a held confirmation or auth request disappears from its pending request hook as soon as it is buffered. A held reply's promise resolves when it is buffered, as with addToolResult. While a reply is held locally, its tool-call part has responseDeliveryStatus: "queued"; the field disappears when the reply is sent or dropped. When a run ends in an error, the release that follows it holds back that run's own answers; they go out once another run finishes, or with the next user message, ahead of it. A held answer stays with its thread when you switch away and back. Cancelling the run or reloading the thread drops held answers. A message edit or regenerate that fails before its first event restores held answers with the rest of the thread, even when it was made while an earlier edit or regenerate was pending. Stop interrupting a checkpoint lookup (with unstable_allowCancellation) also restores them. They go out once another run finishes or with the next user message.

A long-running call is not background work. @assistant-ui/core keeps a message running after its run ends only while a tool call carries a nested conversation whose trailing assistant message is still running; an ADK long-running call carries no nested conversation, and the run that yielded it has ended. It normally settles on requires-action with reason tool-calls until the function response is sent; an unresolved confirmation approval instead uses reason interrupt. Either state renders the tool UI and keeps useAdkSubmitInput, useAdkConfirmTool, and useAdkSubmitAuth answerable. When a frontend tool is registered for the call, its execute puts the thread back into isRunning for as long as it runs.

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;
  }
}

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 ADK messages. A single interrupt tool reply with a string call ID and empty config (including { runConfig: undefined }) is held like the HITL helpers while any run is active; other shapes send immediately.
useAdkConfirmTool()Confirm or deny a pending tool confirmation.
useAdkSubmitAuth()Submit auth credentials for a pending auth request.
useAdkSubmitInput()Submit the user's answer for a pending adk_request_input HITL interrupt.
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, and usage metadata.

Feature support

FeatureStatus
Streaming text (SSE)Supported
Tool calls and resultsSupported
Tool confirmations (useAdkConfirmTool)Supported
Auth credential flow (useAdkSubmitAuth)Supported
Workflow input requests (useAdkSubmitInput, ADK Python 2.0+)Supported
Multi-agent (author and branch tracking)Supported
Agent transfer eventsSupported
Escalation detectionSupported
Chain-of-thought / reasoningSupported
Code execution (executableCode + result)Supported
Inline images and 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 mappingSupported
interrupted event handlingSupported
Snake_case field normalizationSupported
Cloud thread persistenceSupported
ADK session-backed thread persistenceSupported
Direct ADK server connection (no proxy)Supported
One-liner API route (createAdkApiRoute)Supported
Message editing and regenerationSupported
Automatic tool invocationsSupported