I just created an new agent in broswer and deploye...
# ask-ai
b
I just created an new agent in broswer and deployed it. How can I make calls from the API? no sip provider, I want to call it from my application
t
Thanks for reaching out, Wesley. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’m responding to your question about calling the agent you just created and deployed from your application (no SIP provider). You can do this entirely via the LiveKit API by dispatching the agent into a room and having your app join that same room as a participant. Below are the clear steps and minimal code examples to get you talking to the agent. 1) Dispatch the agent into a room • Call the AgentDispatch API to start the agent in a room. Provide the agent’s
agent_name
and a room name (the room will be created if it doesn’t exist). • Example (Python async):
Copy code
from livekit import api

async def dispatch_agent():
    lk_api = api.LiveKitAPI(
        url="your_livekit_url",
        api_key="your_api_key",
        api_secret="your_api_secret"
    )

    await lk_api.agent_dispatch.create_dispatch(
        api.CreateAgentDispatchRequest(
            agent_name="your-agent-name",
            room="my-call-room",
            metadata='{"user": "Alice"}'  # optional
        )
    )

    await lk_api.aclose()
2) Generate a participant token for your application • Your app needs an access token that grants
room_join
for the same room. Issue this token server-side using your API key/secret. • Example token creation (Python):
Copy code
from livekit import api as server_api

token = (
    server_api.AccessToken("your_api_key", "your_api_secret")
    .with_identity("user-123")
    .with_grants(server_api.VideoGrants(room_join=True, room="my-call-room"))
    .to_jwt()
)
• Return this token to your client app (web/mobile) and use it to connect to LiveKit. 3) Connect your app to the room • In your client (WebRTC) code, connect to your LiveKit server with the generated token. Once connected, publish your microphone/audio track and subscribe to the agent’s audio. • The agent and your app will exchange audio and data in real time inside the room. 4) Interaction notes / improvements • The agent handles STT → LLM → TTS automatically once dispatched; your app only needs to provide audio and receive audio. • Use
preemptive_generation=True
in agent session configuration if you want smoother, faster responses from the agent. Summary • Dispatch the agent to a room via AgentDispatch. • Generate a participant token that allows your app to join that same room. • Connect your app with that token and exchange audio/data with the agent in real time. • No SIP provider is required — everything runs over LiveKit rooms (WebRTC). If you’d like, tell me: • The language/platform of your application (Web, iOS, Android, Node, Python, etc.) so I can give a short client example for joining the room, or • Your agent’s
agent_name
and the room name you want to use and I’ll draft the exact API calls (with placeholders) you can run. Sources: Agents telephony integration | LiveKit Documentation | Agent dispatch | LiveKit Documentation | Outbound Calling Script | LiveKit Documentation | server-sdk-kotlin | livekit/livekit_composite | DeepWiki | Survey Calling Agent | LiveKit Documentation
b
can you create the full code?
t
Thanks — I’m responding to your request for a full code example to call your deployed agent from your application. I can produce a complete, runnable example, but first I need to know two things so I deliver the right code for your stack: 1. Which language/platform do you want for the examples? ◦ Options: Python (server + client), Node.js (server + browser client), plain Web (browser JS), iOS (Swift), Android (Kotlin), or Go. 1. Do you want: ◦ Server-side only (dispatch agent + generate token), or ◦ Both server-side (dispatch + token) and client-side code that connects to the room and exchanges audio with the agent? If you pick Node.js or Python, I can provide: • A server file that dispatches the agent and issues participant tokens. • A simple client (browser) that uses the token to join the room, publish microphone audio, and play the agent’s audio. Tell me your preferred language/platform and whether you want both server and client code, and I’ll generate the full code with clear instructions and placeholders for your LiveKit URL, API key/secret, agent_name, and room name.
b
it is python, the part up to the getting the token is working, just need what comes after
t
Thanks — I’m responding to your request for the Python client code that runs after you already have a participant JWT token. Below is a self-contained, minimal Python example that: • Connects to LiveKit using your token • Publishes your microphone audio (if available) • Subscribes to and plays back remote audio (the agent’s audio) • Prints participant/track events so you can see when the agent joins and speaks Before running, replace the placeholders for
TOKEN
and
ROOM_URL
with your values. 1) Install dependencies
Copy code
pip install livekit pyaudio sounddevice numpy
•
livekit
is the LiveKit Python client SDK. •
sounddevice
and
numpy
are used to play incoming audio. • (On some systems you may prefer
pyaudio
for capture; example below uses
sounddevice
for playback and the SDK’s built-in capture helper if available.) 2) Full example (save as client.py)
Copy code
import asyncio
import os
import sys
import numpy as np
import sounddevice as sd

from livekit import Room, LocalAudioTrack, Track
from livekit.utils import AudioFrame

# Replace these with your actual values
TOKEN = os.getenv("LIVEKIT_TOKEN", "YOUR_PARTICIPANT_JWT_HERE")
ROOM_URL = os.getenv("LIVEKIT_URL", "<wss://your-livekit-instance.com>")

# Playback parameters (LiveKit audio frames are float32 PCM)
SAMPLE_RATE = 48000
CHANNELS = 1
BUFFER_SECONDS = 0.2  # small buffer for playback

def play_audio_buffer(audio_frame: AudioFrame):
    """
    audio_frame.samples is a bytes-like PCM float32 interleaved buffer
    The LiveKit Python SDK delivers frames as numpy arrays in many builds,
    but this function handles a raw numpy float32 array.
    """
    try:
        data = audio_frame.data  # SDK may expose frame data as numpy array
    except AttributeError:
        # Fallback: try raw samples property
        data = np.frombuffer(audio_frame.samples, dtype=np.float32)

    # Ensure correct shape for sounddevice (frames, channels)
    if data.ndim == 1 and CHANNELS == 1:
        sd.play(data, samplerate=SAMPLE_RATE, blocking=False)
    else:
        # If interleaved, reshape: (n_frames, channels)
        frames = data.reshape(-1, CHANNELS)
        sd.play(frames, samplerate=SAMPLE_RATE, blocking=False)

async def main():
    if TOKEN == "YOUR_PARTICIPANT_JWT_HERE":
        print("Please set TOKEN to your participant JWT (or set LIVEKIT_TOKEN env var).")
        sys.exit(1)

    room = Room()

    # Optional: event handlers
    @room.on("participant_connected")
    def on_participant_connected(participant):
        print(f"Participant connected: {participant.identity}")

    @room.on("participant_disconnected")
    def on_participant_disconnected(participant):
        print(f"Participant disconnected: {participant.identity}")

    @room.on("track_subscribed")
    def on_track_subscribed(track: Track, publication, participant):
        print(f"Subscribed to track {publication.track_sid} from {participant.identity}, kind={track.kind}")
        # If this is audio, attach a callback to receive audio frames
        if track.kind == track.Kind.KIND_AUDIO:
            # The LiveKit Python SDK may expose a `subscribe` callback for audio frames.
            # Use `on_frame` if available, otherwise use generic `track` APIs from the SDK.
            try:
                @track.on("frame")
                def on_audio_frame(frame):
                    # frame is an AudioFrame-like object
                    play_audio_buffer(frame)
            except Exception:
                print("Audio frame callback could not be attached; check SDK version for frame API.")

    @room.on("track_unsubscribed")
    def on_track_unsubscribed(track, publication, participant):
        print(f"Unsubscribed from track {publication.track_sid} from {participant.identity}")

    # Connect to the room
    print("Connecting to room...")
    await room.connect(ROOM_URL, TOKEN)
    print("Connected as:", room.local_participant.identity)

    # Create and publish a local microphone audio track (if supported on platform)
    try:
        print("Creating local audio track (microphone)...")
        # The SDK offers helpers to create a LocalAudioTrack from system microphone.
        # This call may vary by SDK version. If not available, capture and publish frames manually.
        local_audio: LocalAudioTrack = await room.create_local_audio_track()  # helper method
        await room.local_participant.publish_track(local_audio)
        print("Published local audio track.")
    except Exception as e:
        print("Could not create/publish local mic track automatically:", e)
        print("If you want microphone capture, implement manual capture and publish frames per SDK docs.")

    try:
        # Keep the script running until disconnected
        await room.disconnected.wait()
        print("Disconnected from room.")
    finally:
        await room.disconnect()

if __name__ == "__main__":
    asyncio.run(main())
Notes and troubleshooting • The exact names/APIs for audio frame callbacks and
create_local_audio_track()
can vary slightly between LiveKit Python SDK versions. If you get attribute errors: ◦ Check your installed
livekit
package version. ◦ If the SDK lacks
create_local_audio_track()
, capture microphone audio with
sounddevice
and publish frames via the SDK’s Push-based track APIs (I can provide an example if needed). ◦ If audio frames come in a different object shape, print the frame object to inspect available fields (e.g.,
print(dir(frame))
). Next steps I can help with • If you want a robust mic-capture + publish example (manual capture loop with
sounddevice
and publishing PCM frames to LiveKit), tell me and I’ll produce the code. • If you hit a specific AttributeError or event name mismatch, paste the error and I’ll adapt the example to your installed SDK version. Would you like the manual microphone capture + publish example or help matching this code to your SDK version? Sources: SDKs | livekit/livekit_composite | DeepWiki | server-sdk-js | livekit/livekit_composite | DeepWiki
b
none of those imports exists
Copy code
from livekit import Room, LocalAudioTrack, Track
from livekit.utils import AudioFrame
t
Thanks — I’m responding to the error you showed about missing imports. You’re right to flag that: those imports don’t exist because there is no official LiveKit client SDK for Python that provides
Room
,
LocalAudioTrack
, etc. The Python packages on PyPI are server-side (token/room management), not WebRTC clients. What this means for your flow • Keep your server-side Python code that generates the participant token — that part is correct and stays in Python. • For the client that connects to the room, captures microphone audio, and plays the agent’s audio, use a LiveKit client SDK that supports WebRTC (the recommended option is the JavaScript/Browser SDK or one of the native SDKs such as iOS/Android/React Native). • You cannot run a native LiveKit WebRTC client purely in Python (except via complex workarounds like driving a headless browser). I can help in two practical ways — pick one: 1) Provide a minimal, complete example with: • A small Python server (Flask/FastAPI) that exposes your existing token endpoint, and • A browser client (HTML + JavaScript) using the official
livekit-client
SDK that: ◦ Requests the token, ◦ Connects to the room, ◦ Publishes the microphone, ◦ Plays back the agent’s audio. 2) Provide a headless/browser-driven approach if you must keep everything under Python (using Playwright or Selenium to run the JS client from Python). Note: this is more brittle and not recommended for production. If you want option 1 (recommended), tell me: • Do you prefer Flask or FastAPI for the Python server? • Your LiveKit server URL and the token endpoint path you already have (or tell me to use placeholders). I’ll generate the full server + browser client code with clear instructions and exact placeholders to drop in your token-creation code. If you prefer option 2, I’ll outline the Playwright approach and show the full Python script that launches a headless browser running the official JS client. Sources: client-sdk-js | livekit/livekit_composite | DeepWiki | Agents (Python) | livekit/livekit_composite | DeepWiki | Python SDKs | livekit/livekit_composite | DeepWiki
b
okay so how can I do it?
t
Thanks — I’m responding to your question about how to actually connect to the agent after you’ve already generated the participant token in Python. Because there’s no official LiveKit WebRTC client SDK for Python, the recommended approach is: • Keep your Python server to generate participant tokens (you already have this). • Build the client that connects to the room using a LiveKit client SDK that supports WebRTC — the simplest choice is the JavaScript browser SDK. • The browser client will use the token from your Python server, connect to the room, publish the microphone, and receive/play the agent’s audio. Below I’ll give a complete, minimal example you can run now: • A Python Flask server route that returns a participant token (uses your existing token-generation code). • A static HTML + JavaScript client that uses the official
livekit-client
JS SDK to join the room, publish microphone audio, and play incoming audio (including the agent). Replace placeholders (LIVEKIT_URL, API_KEY/SECRET, AGENT_NAME, ROOM_NAME) with your values. 1) Python server (Flask) — token endpoint • Install: pip install flask livekit • Save as server.py:
Copy code
# server.py
import os
from flask import Flask, jsonify
from livekit import AccessToken, VideoGrants  # server-side token generation

app = Flask(__name__)

# Replace with your LiveKit API key/secret and URL
API_KEY = os.getenv("LIVEKIT_API_KEY", "YOUR_API_KEY")
API_SECRET = os.getenv("LIVEKIT_API_SECRET", "YOUR_API_SECRET")
LIVEKIT_URL = os.getenv("LIVEKIT_URL", "<https://your-livekit-instance.com>")  # for info only

@app.route("/token/<identity>/<room>")
def token(identity, room):
    # Generate a participant token that can join `room`
    at = AccessToken(API_KEY, API_SECRET)
    at.with_identity(identity)
    at.with_grants(VideoGrants(room_join=True, room=room))
    jwt = at.to_jwt()
    return jsonify({"token": jwt, "livekit_url": LIVEKIT_URL})

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
How to use: • Start the server: python server.py • Request a token (example): GET http://localhost:5000/token/user123/my-call-room • That endpoint returns JSON with
token
. Your browser client will fetch that. 2) Browser client (HTML + JS) — connect + mic publish + play audio • No build step required. Uses CDN for livekit-client. • Save as client.html in same folder (or serve statically):
Copy code
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>LiveKit Agent Client</title>
    <script src="https://unpkg.com/livekit-client@latest/dist/livekit-client.umd.js"></script>
    <style>
      body { font-family: Arial, sans-serif; padding: 20px; }
      button { margin: 5px; }
    </style>
  </head>
  <body>
    <h3>LiveKit Agent Client</h3>
    <label>Identity: <input id="identity" value="user123" /></label><br/>
    <label>Room: <input id="room" value="my-call-room" /></label><br/>
    <button id="join">Join Room</button>
    <button id="leave" disabled>Leave Room</button>
    <p id="status"></p>

    <script>
      const { connect, Room } = livekitClient;

      const serverBase = window.location.origin; // assumes server is same origin; adjust if needed

      const joinBtn = document.getElementById('join');
      const leaveBtn = document.getElementById('leave');
      const status = document.getElementById('status');

      let room;

      joinBtn.onclick = async () => {
        const identity = document.getElementById('identity').value;
        const roomName = document.getElementById('room').value;

        status.textContent = 'Requesting token...';
        // Call your Python server to get token
        const res = await fetch(`${serverBase}/token/${encodeURIComponent(identity)}/${encodeURIComponent(roomName)}`);
        if (!res.ok) {
          status.textContent = 'Token request failed';
          return;
        }
        const data = await res.json();
        const token = data.token;
        const livekitUrl = data.livekit_url || (window.location.origin.replace(/^http/, 'ws') ); // not required for connect()

        status.textContent = 'Connecting to room...';

        try {
          // Connect to LiveKit
          room = await connect(token, { 
            // server may be defaulted; to force URL: connect(livekitUrl, token)
            // If you need to supply the ws URL explicitly, use connect("<wss://your-livekit-instance>", token, options)
            // For most hosted LiveKit, provide the ws/wss URL and the token
            // Example: await connect("<wss://your-livekit-instance.com>", token);
            // Here we call connect with token only when the browser can infer the URL from the token
            // For safety, you can call: await connect("<wss://your-livekit-instance.com>", token);
          }, token);

          status.textContent = 'Connected as: ' + room.localParticipant.identity;
          joinBtn.disabled = true;
          leaveBtn.disabled = false;

          // Publish microphone
          const pc = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
          const audioTrack = pc.getAudioTracks()[0];
          const localTrack = await livekitClient.LocalAudioTrack.createFromMediaStreamTrack(audioTrack);
          await room.localParticipant.publishTrack(localTrack);
          status.textContent += ' — Microphone published';

          // Listen for remote participant (agent) audio
          room.on('participantConnected', participant => {
            status.textContent = `Participant connected: ${participant.identity}`;
          });

          room.on('trackPublished', (publication, participant) => {
            console.log('trackPublished', publication, participant);
          });

          room.on('trackSubscribed', (track, publication, participant) => {
            console.log('trackSubscribed', publication.trackSid, participant.identity, track.kind);
            if (track.kind === 'audio') {
              // attach audio element to play remote audio
              const el = document.createElement('audio');
              el.autoplay = true;
              el.controls = false;
              el.srcObject = track.mediaStream;
              document.body.appendChild(el);
            }
          });

        } catch (err) {
          console.error(err);
          status.textContent = 'Connection failed: ' + err.message;
        }
      };

      leaveBtn.onclick = async () => {
        if (room) {
          room.disconnect();
          status.textContent = 'Left room';
          joinBtn.disabled = false;
          leaveBtn.disabled = true;
        }
      };
    </script>
  </body>
</html>
Important notes and adjustments • If your Python token endpoint runs on a different origin, set
serverBase
to that server URL (e.g., http://localhost:5000). • The JS
connect
call typically takes
<wss://your-livekit-instance>
and the token. Some hosted setups accept the token alone; if you get errors, call
connect("<wss://your-livekit-instance.com>", token)
. • The code uses
LocalAudioTrack.createFromMediaStreamTrack
from the JS SDK. If the SDK version differs, use
Room.localParticipant.publishTrack(new LocalAudioTrack(track))
per SDK docs. • The agent will be dispatched by your server or via your earlier API call; ensure the agent is in the same room (
my-call-room
). Once the agent is in the room, your client subscribes to its audio and should hear it. Optional: Dispatching agent automatically • If you want the server to automatically dispatch your agent when a client requests a token, you can call AgentDispatch.create_dispatch from your server before returning the token. You already have that working; ensure the same
room
is used. If you want, I can now: • Provide a version that explicitly calls
connect("wss://...your-livekit-url...", token)
(tell me your LiveKit URL), or • Convert the server example to FastAPI, or • Show how to dispatch the agent from the same Flask endpoint before returning the token (so agent always joins before the client). Which of these would you like next?
b
Okay so looks like you need to build in html and javascript?
t
Yes — to connect from a user-facing app (publish mic audio and receive the agent’s audio) you’ll need a LiveKit client that runs in a WebRTC-capable environment such as a browser (HTML + JavaScript) or a native client (iOS/Android/React Native). Python is fine for your backend that generates tokens and dispatches the agent, but it cannot act as a LiveKit WebRTC client directly. What this means for your setup • Keep your Python server for: ◦ Generating participant JWTs (you already have this). ◦ Optionally dispatching the agent into the room before the client joins. • Build the client in one of these supported client SDKs: ◦ Browser: HTML + JavaScript (recommended for quick testing). ◦ Native: iOS (Swift), Android (Kotlin), or React Native (if you need mobile apps). • The client uses the token from your Python server to connect to the LiveKit room, publish microphone audio, and subscribe/play the agent’s audio. If you want to move forward now, I can: • Provide a complete, copy-pasteable example of the Python token endpoint + an HTML/JS client (I already prepared one earlier). That will let you test immediately in a browser. • Or provide a variant that: ◦ Explicitly dispatches the agent from the same token endpoint before returning the token, or ◦ Uses FastAPI instead of Flask for the server, or ◦ Shows a React/React Native or native mobile example. Which would you like? If you want the simple browser route, I’ll paste a compact, ready-to-run Flask + HTML/JS example again with any adjustments you need (CORS, explicit LiveKit URL in connect(), etc.).
b
okay so instead of using part of it in python, is it possible to create a webpage that I can run locally that will do this connection?
t
Yes — you can run a fully client-side webpage locally to connect to your LiveKit agent. You still need a valid participant JWT token, but for local testing you can either: • Paste a one-off token directly into the page (quick and simple for development), or • Fetch a token from your Python token endpoint if you want a short-lived dynamic token. Below is a complete, self-contained HTML + JavaScript file you can save and open locally (double-click or serve with a static server). It will: • Use the LiveKit web client SDK from a CDN • Let you input a participant token (paste a token you generated server-side) or fetch a token from a token endpoint • Connect to the room, publish your microphone, and play remote audio (the agent) Security note: embedding long-lived tokens in client-side pages is insecure. Only use pasted/test tokens for local development. For production, always generate tokens server-side and deliver them to the client on demand. 1) Generate a test token (one-off) • If you already have a small Python script that creates tokens, run it and copy the generated JWT. • Example minimal Python one-off (run once; prints a token):
Copy code
# generate_token.py
from livekit import AccessToken, VideoGrants
import os
API_KEY = os.getenv("LIVEKIT_API_KEY", "YOUR_API_KEY")
API_SECRET = os.getenv("LIVEKIT_API_SECRET", "YOUR_API_SECRET")

at = AccessToken(API_KEY, API_SECRET)
at.with_identity("local-user")
at.with_grants(VideoGrants(room_join=True, room="my-call-room"))
print(at.to_jwt())
Run:
python generate_token.py
and copy the printed token. 2) Save this file as client.html and open it in your browser
Copy code
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>LiveKit Local Client</title>
  <script src="https://unpkg.com/livekit-client@latest/dist/livekit-client.umd.js"></script>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; max-width:800px; }
    label, button { display:block; margin:8px 0; }
    audio { display:block; margin-top:10px; }
  </style>
</head>
<body>
  <h2>LiveKit Local Client</h2>

  <label>
    LiveKit WS URL (optional):
    <input id="wsUrl" placeholder="wss://your-livekit.example.com" style="width:100%"/>
    <small>Leave blank if your token encodes the server. If you get connection errors, set this to your LiveKit wss URL.</small>
  </label>

  <label>
    Paste participant token (JWT):
    <textarea id="token" rows="3" style="width:100%" placeholder="Paste a participant token here for local testing"></textarea>
  </label>

  <label>
    Or fetch token from endpoint (optional):
    <input id="tokenEndpoint" placeholder="http://localhost:5000/token/local-user/my-call-room" style="width:100%"/>
    <small>If filled, clicking "Get Token & Join" will call this endpoint and use the returned token JSON { "token": "..."}.</small>
  </label>

  <label>
    Room (for display):
    <input id="roomName" placeholder="my-call-room" style="width:100%"/>
  </label>

  <button id="joinBtn">Get Token & Join</button>
  <button id="leaveBtn" disabled>Leave</button>

  <p id="status">Status: idle</p>

  <div id="remoteTracks"></div>

  <script>
    const { connect, LocalAudioTrack } = livekitClient;
    const joinBtn = document.getElementById('joinBtn');
    const leaveBtn = document.getElementById('leaveBtn');
    const status = document.getElementById('status');
    const tokenInput = document.getElementById('token');
    const tokenEndpointInput = document.getElementById('tokenEndpoint');
    const wsUrlInput = document.getElementById('wsUrl');
    const remoteTracksDiv = document.getElementById('remoteTracks');
    const roomNameInput = document.getElementById('roomName');

    let room;

    function setStatus(s) {
      status.textContent = 'Status: ' + s;
      console.log(s);
    }

    async function getTokenFromEndpoint(url) {
      setStatus('Fetching token from endpoint...');
      const res = await fetch(url);
      if (!res.ok) throw new Error('Token endpoint returned ' + res.status);
      const data = await res.json();
      if (!data.token) throw new Error('Token JSON must include "token" field');
      return data.token;
    }

    joinBtn.onclick = async () => {
      joinBtn.disabled = true;
      leaveBtn.disabled = true;
      remoteTracksDiv.innerHTML = '';

      try {
        let token = tokenInput.value.trim();
        const endpoint = tokenEndpointInput.value.trim();
        const wsUrl = wsUrlInput.value.trim();
        const roomName = roomNameInput.value.trim();

        if (!token && endpoint) {
          token = await getTokenFromEndpoint(endpoint);
        }
        if (!token) {
          setStatus('No token provided. Paste a token or set a token endpoint.');
          joinBtn.disabled = false;
          return;
        }

        setStatus('Connecting to LiveKit...');

        // If you want to force using a specific wss URL, pass it as first arg:
        // room = await connect(wsUrl || undefined, token);
        // Some LiveKit deployments require explicit URL; others accept token-only.
        if (wsUrl) {
          room = await connect(wsUrl, token);
        } else {
          // Attempt connect with token only; works if token encodes the correct server info
          room = await connect(undefined, token);
        }

        setStatus('Connected as: ' + room.localParticipant.identity + '  Room: ' + (roomName || ''));
        leaveBtn.disabled = false;

        // Publish microphone
        try {
          const ms = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
          const t = ms.getAudioTracks()[0];
          // Create LocalAudioTrack from MediaStreamTrack via SDK helper
          const localTrack = await LocalAudioTrack.createFromMediaStreamTrack(t);
          await room.localParticipant.publishTrack(localTrack);
          setStatus('Published microphone');
        } catch (e) {
          console.warn('Microphone publish failed:', e);
          setStatus('Connected (mic publish failed or blocked).');
        }

        // Listen for remote audio tracks and play them
        room.on('trackSubscribed', (track, publication, participant) => {
          if (track.kind === 'audio') {
            const el = document.createElement('audio');
            el.autoplay = true;
            el.controls = true;
            // newer SDK exposes mediaStream
            if (track.mediaStream) {
              el.srcObject = track.mediaStream;
            } else {
              // fallback: attach track to new MediaStream
              const ms = new MediaStream();
              ms.addTrack(track.mediaStreamTrack || track.track); // best-effort
              el.srcObject = ms;
            }
            const label = document.createElement('div');
            label.textContent = `Audio from ${participant.identity}`;
            remoteTracksDiv.appendChild(label);
            remoteTracksDiv.appendChild(el);
          }
        });

        room.on('participantConnected', p => {
          setStatus('Participant connected: ' + p.identity);
        });

        room.on('participantDisconnected', p => {
          setStatus('Participant disconnected: ' + p.identity);
        });

        room.on('disconnected', () => {
          setStatus('Disconnected');
          joinBtn.disabled = false;
          leaveBtn.disabled = true;
        });

      } catch (err) {
        console.error(err);
        setStatus('Error: ' + (err.message || err));
        joinBtn.disabled = false;
      }
    };

    leaveBtn.onclick = async () => {
      if (room) {
        room.disconnect();
        room = null;
      }
      joinBtn.disabled = false;
      leaveBtn.disabled = true;
      setStatus('Left');
      remoteTracksDiv.innerHTML = '';
    };
  </script>
</body>
</html>
How to run locally • Option A (double-click): Save file and open in Chrome/Firefox by double-clicking. Some browsers block getUserMedia or require HTTPS for certain features — if microphone access is blocked, use Option B. • Option B (simple local static server — recommended): a. Python 3:
python -m http.server 8000
in the folder containing client.html b. Open http://localhost:8000/client.html This avoids some browser restrictions when opening file:// directly. If you need to automatically dispatch the agent into the room at join time • Either pre-dispatch the agent from your existing Python code, or • Add an endpoint that dispatches the agent and returns a token; then set the "token endpoint" field to that endpoint so the client triggers agent dispatch before joining. Want me to: • Provide a one-click example where the token endpoint both dispatches the agent and returns a token (Flask example), or • Adjust this page to force a specific LiveKit wss URL for your deployment?
b
make it simpler, create a variable and I will paste it there
t
Absolutely — below is a minimal, self-contained HTML file you can save and run locally. At the top you’ll find two variables to paste your participant token and (optionally) your LiveKit wss URL. After pasting the token, open the file in a browser (or serve it with a simple static server) and click "Join". Save as client.html and edit the two variables near the top: TOKEN and WS_URL.
Copy code
<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <title>LiveKit Simple Client</title>
  <script src="https://unpkg.com/livekit-client@latest/dist/livekit-client.umd.js"></script>
  <style>
    body { font-family: Arial, sans-serif; padding: 20px; max-width:600px; }
    button { margin: 8px 0; }
    textarea { width:100%; }
    audio { display:block; margin-top:8px; }
  </style>
</head>
<body>
  <h3>LiveKit Simple Client</h3>

  <!-- Paste your token and optional WS URL here -->
  <script>
    // Paste your participant JWT token (paste here for local testing)
    const TOKEN = "PASTE_YOUR_PARTICIPANT_JWT_HERE";

    // Optional: set your LiveKit WebSocket URL (wss://...). Leave as "" to try token-only connect.
    const WS_URL = ""; // e.g. "<wss://your-livekit-instance.com>"
  </script>

  <p>
    Token: <small>paste into the variable at top of file</small>
  </p>

  <button id="joinBtn">Join Room</button>
  <button id="leaveBtn" disabled>Leave</button>
  <p id="status">Status: idle</p>

  <div id="remote"></div>

  <script>
    const { connect, LocalAudioTrack } = livekitClient;
    const joinBtn = document.getElementById('joinBtn');
    const leaveBtn = document.getElementById('leaveBtn');
    const status = document.getElementById('status');
    const remote = document.getElementById('remote');

    let room;

    function setStatus(s) {
      status.textContent = 'Status: ' + s;
      console.log(s);
    }

    joinBtn.onclick = async () => {
      if (!TOKEN || TOKEN === "PASTE_YOUR_PARTICIPANT_JWT_HERE") {
        setStatus('Please paste your participant token into the TOKEN variable at the top of this file.');
        return;
      }

      joinBtn.disabled = true;
      remote.innerHTML = '';

      try {
        setStatus('Connecting...');

        // Connect: pass WS_URL if provided, otherwise attempt token-only connect
        if (WS_URL && WS_URL.trim() !== "") {
          room = await connect(WS_URL, TOKEN);
        } else {
          room = await connect(undefined, TOKEN);
        }

        setStatus('Connected as ' + room.localParticipant.identity);
        leaveBtn.disabled = false;

        // Publish microphone (ask permission)
        try {
          const ms = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
          const t = ms.getAudioTracks()[0];
          const localTrack = await LocalAudioTrack.createFromMediaStreamTrack(t);
          await room.localParticipant.publishTrack(localTrack);
          setStatus('Connected — microphone published');
        } catch (e) {
          console.warn('Mic publish failed:', e);
          setStatus('Connected — microphone not published (permission denied or error)');
        }

        // Play incoming audio tracks (agent or other participants)
        room.on('trackSubscribed', (track, publication, participant) => {
          if (track.kind === 'audio') {
            const el = document.createElement('audio');
            el.autoplay = true;
            el.controls = true;
            // SDK exposes mediaStream for audio tracks
            if (track.mediaStream) {
              el.srcObject = track.mediaStream;
            } else {
              // Fallback: create MediaStream and add available track reference
              const ms = new MediaStream();
              if (track.mediaStreamTrack) ms.addTrack(track.mediaStreamTrack);
              el.srcObject = ms;
            }
            const label = document.createElement('div');
            label.textContent = 'Audio from ' + participant.identity;
            remote.appendChild(label);
            remote.appendChild(el);
          }
        });

        room.on('disconnected', () => {
          setStatus('Disconnected');
          joinBtn.disabled = false;
          leaveBtn.disabled = true;
        });

      } catch (err) {
        console.error(err);
        setStatus('Connection error: ' + (err.message || err));
        joinBtn.disabled = false;
      }
    };

    leaveBtn.onclick = () => {
      if (room) {
        room.disconnect();
        room = null;
      }
      setStatus('Left room');
      joinBtn.disabled = false;
      leaveBtn.disabled = true;
      remote.innerHTML = '';
    };
  </script>
</body>
</html>
How to run • Save and open in Chrome/Firefox. If microphone access is blocked when opening via file://, serve it with a simple static server: ◦ Python 3: run
python -m http.server 8000
in the folder and open http://localhost:8000/client.html Notes • This is for local testing only. Do not embed long-lived tokens in production pages. • If connect fails, set WS_URL to your LiveKit wss URL (e.g., "wss://your-livekit-instance.com") and try again. • If you want, paste your token here (or tell me any error message you see) and I’ll help debug connection errors.