Skip to main content
Once your backend has called startConversation, the response contains everything your web client needs to join the live room:
  • conversation.serverUrl — the LiveKit WebSocket URL
  • consumerAccessToken — the end-user’s JWT
  • conversation.character.livekitIdentity — the character’s identity for event wiring
Pick the SDK that matches your stack.

@equos/browser-sdk

Vanilla TypeScript/JavaScript. Great for custom UIs or non-React frameworks.

@equos/react

Provider + renderer components for React, Next.js, Remix, etc.

Browser SDK

Install:
npm i @equos/browser-sdk
Fetch the conversation credentials from your backend, then hand them to EquosConversation:
import { EquosConversation, EquosEvent } from "@equos/browser-sdk";

const video = document.getElementById("character-video") as HTMLVideoElement;

// 1. Get credentials from your backend
const res = await fetch("/api/start-conversation", { method: "POST" });
const { conversation: conv, consumerAccessToken } = await res.json();

// 2. Build the client with the three fields
const conversation = new EquosConversation({
  config: {
    wsUrl: conv.serverUrl,
    token: consumerAccessToken,
    agentIdentity: conv.character.livekitIdentity,
  },
});

// 3. Handle events and connect
conversation.on(EquosEvent.AgentConnected, () => conversation.attach(video));
conversation.on(EquosEvent.Utterance, ({ utterance }) =>
  console.log(utterance.author, utterance.content)
);

await conversation.connect();
Common controls:
await conversation.setMicrophoneEnabled(true);   // mic on
conversation.sendText("Hello!");                  // send a text message
await conversation.disconnect();                  // hang up
conversation.detach(video);                       // unbind the <video>

Full vanilla example

Plain HTML + TypeScript project using Vite.

React SDK

Install:
npm i @equos/react
The React SDK wraps the browser SDK in a provider + renderer so you don’t have to wire events manually:
import {
  EquosConversationProvider,
  EquosConversationRenderer,
} from "@equos/react";
import { useState } from "react";

export default function App() {
  const [response, setResponse] = useState(null);

  const start = async () => {
    const res = await fetch("/api/start-conversation", { method: "POST" });
    setResponse(await res.json()); // { conversation, consumerAccessToken }
  };

  if (!response) return <button onClick={start}>Start conversation</button>;

  return (
    <EquosConversationProvider
      conversation={response.conversation}
      accessToken={response.consumerAccessToken}
      autoPublishMic
    >
      <EquosConversationRenderer
        allowMic
        allowCamera
        allowScreenshare
        allowHangUp
        onHangUp={() => setResponse(null)}
      />
    </EquosConversationProvider>
  );
}
Inside the provider, a useEquosConversation() hook exposes methods like sendText() and sendContext() for building custom controls.

Full React example

Vite + React app using <EquosConversationProvider> and <EquosConversationRenderer>.

Next.js (full-stack pattern)

Next.js is the canonical full-stack pattern: your API key stays on the server (in a server action or route handler), and only the conversation payload crosses to the client. Server actionapp/actions.ts:
"use server";
import { equos } from "@/lib/equos"; // server-only EquosClient

export async function startConversationAction() {
  return equos.conversations.startConversation({
    createEquosConversationRequest: {
      name: `nextjs-demo-${Date.now()}`,
      characterId: process.env.EQUOS_CHARACTER_ID!,
      consumer: { name: "Demo User", identity: "demo-user" },
    },
  });
}

export async function stopConversationAction(id: string) {
  await equos.conversations.stopConversation({ id });
}
Client componentapp/conversation-client.tsx:
"use client";
import {
  EquosConversationProvider,
  EquosConversationRenderer,
} from "@equos/react";
import { useState, useTransition } from "react";
import {
  startConversationAction,
  stopConversationAction,
} from "./actions";

export function ConversationClient() {
  const [response, setResponse] = useState(null);
  const [, startTransition] = useTransition();

  const start = () =>
    startTransition(async () =>
      setResponse(await startConversationAction())
    );

  const stop = async () => {
    const id = response.conversation.id;
    setResponse(null);
    await stopConversationAction(id);
  };

  if (!response) return <button onClick={start}>Start conversation</button>;

  return (
    <EquosConversationProvider
      conversation={response.conversation}
      accessToken={response.consumerAccessToken}
      autoPublishMic
    >
      <EquosConversationRenderer
        allowMic
        allowCamera
        allowHangUp
        onHangUp={stop}
      />
    </EquosConversationProvider>
  );
}
Mark your server-side Equos client with import "server-only" so Next.js fails the build if anything tries to import the API key into a client bundle.

Full Next.js example

Next.js 15 app with server actions + @equos/react.

Features

The following features are available in both the Browser SDK and the React SDK.

Mode switch (text · audio · video)

A conversation can run in one of three modes:
  • EquosMode.Text — typed messages only, no audio or video
  • EquosMode.Audio — voice conversation, no video
  • EquosMode.Video — full video + voice (default)
import { EquosMode, EquosEvent, type EquosModeType } from "@equos/browser-sdk";

// Change mode at runtime
conversation.setMode(EquosMode.Audio);

// React to remote changes
conversation.on(EquosEvent.ModeChanged, (mode: EquosModeType) => {
  console.log("Mode is now", mode); // "text" | "audio" | "video"
});
In the React SDK, mode is a controlled prop on EquosConversationProvider—update your state and re-render to change it.

Send user text

Send a text message from the user to the character—useful for typed input, accessibility, or in EquosMode.Text conversations. Behaves like the user spoke the message.
conversation.sendText("What's the refund policy?");

Send context

Inject silent context into the conversation—information the character should know about, but that isn’t spoken by the user and doesn’t appear as a message. Great for things like:
  • Telling the character who the logged-in user is ("The user's name is Alex, Pro tier, signed up in 2024.")
  • Passing in the current page or screen the user is on
  • Feeding live data (cart contents, current order, sensor readings) as the conversation evolves
conversation.sendContext("The user is currently viewing the pricing page.");
Unlike sendText, sendContext does not surface as a user utterance. The character picks it up as background knowledge for its next response.