agent-webkit
Reference

@agent-webkit/react API

useAgentSession() and the state machine it exposes.

npm install @agent-webkit/react @agent-webkit/core

@agent-webkit/react is a thin wrapper over @agent-webkit/core. It owns the SSE iterator, reduces events into a typed message list, and surfaces permission / question state.

useAgentSession(opts)

import { useAgentSession } from "@agent-webkit/react";

const session = useAgentSession({
  baseUrl: "https://api.example.com",
  token: "...",
});

UseAgentSessionOptions

FieldTypeDefaultNotes
baseUrlstringRequired when client is not provided.
tokenstringBearer token forwarded to transport.
sessionIdstringAttach to an existing session instead of creating one.
resumeFromEventIdstringResume from a specific SSE seq.
createCreateSessionOptionsForwarded to POST /sessions when creating.
clientAgentClientInject a pre-built client (testing).
autoStartbooleantrueIf false, the hook does nothing on mount. Flip back to true when you're ready to open the session — useful for gating on auth, feature flags, or a user gesture.

Return value

interface UseAgentSessionReturn {
  // State
  messages: DisplayMessage[];
  status: Status;
  pendingPermission: PendingPermission | null;
  pendingQuestion: PendingQuestion | null;
  lastError: { code: string; message: string } | null;
  totalCostUsd: number;
  sessionId: string | null;

  // Actions
  send(input: UserInput): Promise<void>;
  interrupt(): Promise<void>;
  approve(correlationId: string, options?: ApproveOptions): Promise<void>;
  deny(correlationId: string, options?: DenyOptions): Promise<void>;
  answer(correlationId: string, answers: unknown): Promise<void>;
  setPermissionMode(mode: string): Promise<void>;
  setModel(model: string | null): Promise<void>;
  stopTask(taskId: string): Promise<void>;
  close(): Promise<void>;
}

State machine

Status

type Status =
  | "idle"
  | "streaming"
  | "awaiting_permission"
  | "awaiting_question"
  | "awaiting_hook"
  | "error";

Transitions:

  • idle → streaming on send().
  • streaming → awaiting_permission | awaiting_question when the server emits permission_request / ask_user_question.
  • awaiting_* → streaming after you call approve / deny / answer (or another subscriber wins the race).
  • Any → error on a fatal transport or server error.
  • Any → idle after a turn-final result event.

DisplayMessage

A discriminated union built by reducing message_delta, message_complete, tool_use, and tool_result events:

type DisplayMessage =
  | { kind: "user"; id: string; content: ContentBlock[] }
  | {
      kind: "assistant";
      id: string;
      message_id: string;
      content: ContentBlock[];
      streaming: boolean;
    }
  | {
      kind: "tool_result";
      id: string;
      tool_use_id: string;
      output: string;
      is_error: boolean;
    };

Use streaming: true to render a typing indicator on the assistant's current message.

PendingPermission

interface PendingPermission {
  correlation_id: string;
  tool_name: string;
  input: Record<string, unknown>;
  context?: Record<string, unknown>;
}

Render a modal, then call approve(pendingPermission.correlation_id) or deny(pendingPermission.correlation_id, { message: "..." }).

PendingQuestion

interface PendingQuestion {
  correlation_id: string;
  questions: { questions: AskUserQuestionItem[] };
}

The double-nested questions.questions shape mirrors the SDK's AskUserQuestion tool input. Each item has a question, header, and a list of multiSelect options.

Race conflicts

If another subscriber answers a permission or question before this client does, the approve / deny / answer promise rejects with a 409 Conflict error. The reducer also clears the pendingPermission / pendingQuestion slot when a winning response is observed via the SSE stream.

The recommended UI: when the slot disappears mid-render, fade out the modal and show "another tab responded".

Lower-level access

Need direct stream access? Build on @agent-webkit/core instead — see the core API. The React hook is intentionally a thin reducer; you should never need to monkey-patch around it.

On this page