Skip to main content
The @polpo-ai/react package wraps the TypeScript SDK with React hooks and a context Provider. It connects to the SSE event stream for live updates — no polling needed.

Install

npm install @polpo-ai/sdk @polpo-ai/react

Provider setup

Wrap your app with PolpoProvider:
import { PolpoProvider } from "@polpo-ai/react";

function App() {
  return (
    <PolpoProvider
      baseUrl="https://{project}.polpo.cloud"
      apiKey="sk_live_..."
      autoConnect={true}
    >
      <Dashboard />
    </PolpoProvider>
  );
}
PropTypeDefaultDescription
baseUrlstringrequiredAPI base URL
apiKeystringProject API key
autoConnectbooleantrueAuto-connect to SSE stream
eventFilterstring[]Filter SSE events (e.g. ["task:*"])

Hooks

useTasks(filter?)

const { tasks, isLoading, error, createTask, deleteTask, retryTask } = useTasks();

// With filter
const { tasks } = useTasks({ status: "in_progress", group: "sprint-1" });

useTask(taskId)

const { task, isLoading, error } = useTask("task-123");

useMissions()

const { missions, isLoading, createMission, deleteMission } = useMissions();

useMission(missionId)

const {
  mission, report, isLoading,
  executeMission, abortMission, resumeMission,
  addTask, removeTask, reorderTasks,
} = useMission("mission-456");

useAgents()

const { agents, isLoading, addAgent, updateAgent, removeAgent } = useAgents();

useProcesses()

const { processes, isLoading } = useProcesses();

useMemory() / useAgentMemory(name)

const { memory, isLoading, save } = useMemory();
const { memory: agentMem, save: saveAgent } = useAgentMemory("backend-dev");

useChat(options?)

Full-featured chat hook with automatic session management, streaming, tool call tracking, and abort support.
import { useChat } from "@polpo-ai/react";

function Chat() {
  const {
    messages,
    sendMessage,
    sessionId,
    setSessionId,
    newSession,
    status,
    error,
    isStreaming,
    pendingToolCall,
    abort,
  } = useChat({
    agent: "backend-dev",
    onChunk: (chunk) => console.log("chunk", chunk),
    onFinish: (result) => console.log("done", result),
    onError: (err) => console.error(err),
    onAskUser: (payload) => console.log("agent asks:", payload),
    onMissionPreview: (preview) => console.log("mission:", preview),
    onToolCall: (toolCall) => console.log("tool:", toolCall),
  });

  return (
    <div>
      {messages.map((m, i) => (
        <div key={i}>{m.role}: {m.content}</div>
      ))}
      <button onClick={() => sendMessage("Hello!")}>Send</button>
      <button onClick={abort} disabled={!isStreaming}>Stop</button>
      <button onClick={newSession}>New conversation</button>
    </div>
  );
}

Options

OptionTypeDescription
agentstringTarget agent name
sessionIdstringResume an existing session
onChunk(chunk) => voidCalled on each streaming chunk
onFinish(result) => voidCalled when streaming completes
onError(error) => voidCalled on error
onAskUser(payload) => voidCalled when finish_reason is "ask_user"
onMissionPreview(preview) => voidCalled when finish_reason is "mission_preview"
onToolCall(toolCall) => voidCalled when the agent invokes a tool

Return value

PropertyTypeDescription
messagesChatMessage[]Full message history
sendMessage(content: string | ContentPart[]) => Promise<void>Send a user message (plain text or content parts)
sessionIdstring | nullCurrent session ID
setSessionId(id: string) => voidSwitch to an existing session
newSession() => voidStart a new conversation
statusstring"idle" | "loading" | "streaming" | "error"
errorError | nullLast error, if any
isStreamingbooleanWhether a response is currently streaming
pendingToolCallPendingToolCall | nullClient-side tool call waiting for a response
abort() => voidAbort the current stream

useSessions()

const { sessions, isLoading, rename, remove } = useSessions();

File attachments

To attach files to a chat message, use useFiles to upload and reference the path in your message:
const { uploadFile } = useFiles("workspace");
const { sendMessage } = useChat();

// Upload and reference in one flow
const handleAttach = async (file: File) => {
  await uploadFile("workspace", file, file.name);
  await sendMessage({
    content: [
      { type: "text", text: "Analyze this file" },
      { type: "file", file_id: `workspace/${file.name}` },
    ],
  });
};

Client-side tools (pendingToolCall + sendToolResult)

Some agent tools are client-side — the agent calls them, but your app handles the interaction. The built-in client-side tool is ask_user_question: the agent asks clarifying questions and your UI collects the answers.
const { messages, sendMessage, sendToolResult, pendingToolCall, isStreaming } = useChat({
  agent: "coder",
});

// When the agent asks a question, pendingToolCall is set
if (pendingToolCall?.toolName === "ask_user_question") {
  const questions = pendingToolCall.arguments.questions;

  return (
    <div>
      {questions.map((q) => (
        <div key={q.id}>
          <p>{q.question}</p>
          {q.options.map((opt) => (
            <button
              key={opt.label}
              onClick={() => {
                sendToolResult(
                  pendingToolCall.toolCallId,
                  "ask_user_question",
                  JSON.stringify({
                    answers: [{ questionId: q.id, selected: [opt.label] }],
                  })
                );
              }}
            >
              {opt.label}
            </button>
          ))}
        </div>
      ))}
    </div>
  );
}
The flow: agent calls tool → pendingToolCall is set → your UI renders → user responds → sendToolResult() sends the answer and resumes the agent stream.

useFiles(path?)

Browse and manage the agent workspace filesystem. Pass an optional path to auto-load entries for that directory.
const {
  roots,
  entries,
  isLoading,
  listFiles,
  previewFile,
  readFile,
  uploadFile,
  createDirectory,
  renameFile,
  deleteFile,
  searchFiles,
} = useFiles("/src");

// Roots are fetched automatically
console.log(roots); // [{ path: "/src" }, { path: "/docs" }, ...]

// List entries in a directory
const items = await listFiles("/src/utils");

// Preview a file
const content = await previewFile("/src/index.ts");

// Read raw file contents
const response = await readFile("/src/index.ts");

// Upload a file
await uploadFile("/src/config.json", myFile, "config.json");

// Create a directory
await createDirectory("/src/new-folder");

// Rename a file
await renameFile("/src/old.ts", "new.ts");

// Delete a file
await deleteFile("/src/obsolete.ts");

// Search by filename
const results = await searchFiles("config", "/src", 20);
All mutation methods (uploadFile, createDirectory, renameFile, deleteFile) include loading states and automatically refresh the file listing on success.

useSkills()

const {
  skills, isLoading,
  create, install, remove, assign, unassign,
} = useSkills();

// Create a skill
await create({ name: "code-review", description: "Reviews PRs" });

// Install from registry
await install({ skills: ["@polpo-ai/skill-testing"] });

// Assign / unassign
await assign("code-review", "backend-dev");
await unassign("code-review", "backend-dev");

// Delete a skill
await remove("code-review");

useSchedules()

const { schedules, isLoading, create, update, remove } = useSchedules();

// Create a schedule
await create({ name: "nightly-report", cron: "0 2 * * *", taskTemplate: { title: "Report", agent: "reporter" } });

// Update
await update("nightly-report", { cron: "0 3 * * *", enabled: false });

// Delete
await remove("nightly-report");

usePlaybooks()

const { playbooks, isLoading, create, remove } = usePlaybooks();

await create({ name: "onboard-repo", description: "Set up a new repo", steps: [{ title: "Scan", agent: "analyst" }] });
await remove("onboard-repo");

useVaultEntries(agent)

const { entries, isLoading, save, patch, remove } = useVaultEntries("emailer");

// Save a new entry
await save({ service: "smtp", type: "smtp", label: "SMTP", credentials: { host: "smtp.co", user: "bot", pass: "..." } });

// Patch specific fields (merge, not replace)
await patch("smtp", { credentials: { pass: "new-pass" } });

// Remove
await remove("smtp");

useActiveDelays()

const { delays, isLoading, add, update, remove } = useActiveDelays();

// Add a delay
await add({ taskId: "task-1", until: "2026-04-01T00:00:00Z", reason: "Waiting for approval" });

// Update a delay
await update("delay-id", { until: "2026-04-02T00:00:00Z" });

// Remove a delay
await remove("delay-id");

useEvents(filter?, maxEvents?)

const { events } = useEvents(["task:*", "agent:*"], 100);

useStats()

const { stats } = useStats();
// { pending, inProgress, done, failed, totalAgents, ... }

usePolpo()

Direct access to the client and store:
const { client, store } = usePolpo();

Real-time updates

All hooks automatically update when SSE events arrive. No manual refetching needed.
function TaskList() {
  const { tasks } = useTasks();

  // This component re-renders automatically when:
  // - A task is created, updated, or deleted
  // - A task transitions state
  // - The SSE connection reconnects (full refresh)

  return (
    <ul>
      {tasks.map(t => (
        <li key={t.id}>{t.title}{t.status}</li>
      ))}
    </ul>
  );
}

Connection status

const { connectionStatus } = usePolpo();
// "connecting" | "connected" | "reconnecting" | "disconnected" | "error"
On reconnect, the Provider automatically refetches all resources to fill any SSE gaps.