@picoagent/embed-react wraps the core SDK in React hooks. Session lifecycle, plan state, agent status, glanceable text, and credits each have a dedicated hook that handles subscription and cleanup automatically.

Using the React SDK

Installation

npm install @picoagent/embed-react @picoagent/embed-voice

@picoagent/embed-voice is listed separately because it bundles the LiveKit audio transport, which has its own native dependencies. Install both for any session that requires live voice.

Available Hooks

HookPackageReturnsWhen to use
useSession@picoagent/embed-react{ session, status, error }Always — manages the session lifecycle.
usePlan@picoagent/embed-react{ currentStep, currentStepIndex, totalSteps }When your UI shows a step panel or progress indicator.
useAgentStatus@picoagent/embed-reactstringWhen you want to show what the agent is currently doing.
useGlanceable@picoagent/embed-reactstring | nullWhen you need a persistent HUD banner or AR overlay text.
useCredits@picoagent/embed-react{ creditsUsed, sessionMinutes, ratePerMin } | nullWhen you want a usage meter visible to the worker.

useSession(credential)

Manages the full session lifecycle. Pass it the credential object fetched from your backend token endpoint.

const { session, status, error } = useSession(credential);

status values:

ValueMeaning
"idle"No credential yet, or the hook has not started connecting.
"connecting"createSession() has been called; waiting for the LiveKit room to open.
"connected"Session is live and ready to accept transport attachments and event subscriptions.
"error"A fatal error occurred. Inspect error.message and surface it to the user.

Pass session to all other hooks. They are all null-safe — they return empty/null state when session is null, so you do not need to guard each hook call yourself.

usePlan(session)

Returns the current plan state. Updates automatically as plan and step_update events arrive.

const plan = usePlan(session);
// plan.currentStep — active step object (null before first plan)
// plan.currentStepIndex — zero-based index
// plan.totalSteps — total step count

When session is null, usePlan returns { currentStep: null, currentStepIndex: 0, totalSteps: 0 }.

useAgentStatus(session)

Returns a string describing what the agent is currently doing — useful for a status label or animated indicator.

const agentStatus = useAgentStatus(session);
// e.g. "listening", "thinking", "speaking"

Common values: "listening", "thinking", "speaking". The exact set depends on the agent configuration.

useGlanceable(session)

Returns the latest glanceable text string emitted by the agent, or null if none has arrived yet. Updates in near-real time as the agent produces observations.

const glanceable = useGlanceable(session);
// e.g. "Watch your elbow angle" or null

Render it conditionally — null means there is nothing current to display:

{glanceable && <div className="glance-banner">{glanceable}</div>}

useCredits(session)

Returns the current credit usage snapshot, or null before the first update arrives.

const credits = useCredits(session);
// credits.creditsUsed — total credits consumed
// credits.sessionMinutes — elapsed time in minutes
// credits.ratePerMin — burn rate per minute

Full Widget Example

The example below combines all five hooks into a single GuidedTaskWidget component. It also shows how to attach the voice transport inside useEffect for proper cleanup.

import { useSession, usePlan, useAgentStatus, useGlanceable, useCredits } from "@picoagent/embed-react";
import { attachVoice } from "@picoagent/embed-voice";
import { useEffect, useRef, useState } from "react";

function GuidedTaskWidget({ credential }) {
  const { session, status, error } = useSession(credential);
  const plan = usePlan(session);
  const agentStatus = useAgentStatus(session);
  const glanceable = useGlanceable(session);
  const credits = useCredits(session);
  const voiceRef = useRef(null);
  const [agentSpeaking, setAgentSpeaking] = useState(false);

  useEffect(() => {
    if (!session) return;
    voiceRef.current = attachVoice(session, {
      onAgentSpeaking: (s) => setAgentSpeaking(s),
    });
    return () => voiceRef.current?.disconnect();
  }, [session]);

  if (status === "connecting") return <p>Connecting...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <p>Agent: {agentStatus}</p>

      {glanceable && <div className="glance-banner">{glanceable}</div>}

      {plan.currentStep && (
        <div>
          <h3>Step {plan.currentStepIndex + 1} of {plan.totalSteps}</h3>
          <p>{plan.currentStep.description}</p>
          <button onClick={() => session?.send({ type: "command", cmd: "next_step" })}>
            Next
          </button>
        </div>
      )}

      {credits && (
        <div className="credits-hud">
          {credits.creditsUsed} credits used ({credits.sessionMinutes.toFixed(1)} min)
        </div>
      )}
    </div>
  );
}

Key points in the example

  • attachVoice is called inside useEffect with session as the dependency. This ensures the voice transport is attached as soon as the session is live and disconnected when the component unmounts.
  • session?.send(...) uses optional chaining — safe to call even while session is null during initial render.
  • credits.sessionMinutes.toFixed(1) formats elapsed time to one decimal place.
  • The agentSpeaking state wired from onAgentSpeaking is available if you want to animate a speaking indicator (e.g. pulsing avatar).

Next Steps