metatell Bot SDK
    Preparing search index...

    Examples

    This page shows common bot patterns. Runnable projects are available under examples/.

    Example Purpose
    basic-bot Connect to a room, read chat, reply to mentions, and move near users.
    voice-bot Play a WAV file into a room and record remote PCM audio.
    voice-ai-bot Connect voice input and output to Gemini Live.
    speech-to-speech-bot Speech recognition, LLM response generation, and text-to-speech playback.
    dify-bot Forward room chat to a Dify application.
    bt-bot Autonomous bot driven by a behavior tree and an LLM, with chat and voice (README in Japanese).
    const botInfo = await client.getInfo()

    client.chat.onMessage(async ({ from, text, mention, reply }) => {
    if (mention?.sessionId !== botInfo.sessionId) return

    await reply(`Hello ${from.name ?? 'there'}. I received: ${text}`)
    })
    client.chat.onMessage(async ({ text, reply }) => {
    const [command, ...args] = text.trim().split(/\s+/)

    switch (command) {
    case '/walk':
    await client.avatar.play({ id: 'walking', loop: false })
    await reply('Walking animation requested.')
    break

    case '/move': {
    const [x, y, z] = args.map(Number)
    if ([x, y, z].some(Number.isNaN)) {
    await reply('Usage: /move <x> <y> <z>')
    return
    }

    await client.avatar.moveTo({ x, y, z })
    await reply(`Moved to ${x}, ${y}, ${z}.`)
    break
    }
    }
    })
    const followNearestUser = async () => {
    const [target] = await client.room.getNearbyUsers(10)
    const current = client.avatar.getPosition()

    if (!target?.position || !current) return

    await client.avatar.lookAt(target.position)

    await client.avatar.moveTo({
    x: target.position.x + 1.5,
    y: target.position.y,
    z: target.position.z + 1.5,
    })
    }

    const followLoop = async () => {
    try {
    await followNearestUser()
    } catch (error) {
    console.error('Failed to follow nearest user:', error)
    } finally {
    setTimeout(followLoop, 1000)
    }
    }

    void followLoop()
    const users = await client.room.getUsers()

    for (const user of users) {
    console.log({
    id: user.id,
    name: user.name,
    position: user.position,
    })
    }
    import { enableVoice } from '@metatell/bot-sdk'

    // 48000 Hz, mono, signed 16-bit PCM. Use pcm.resample() for other rates.
    const voice = await enableVoice(client, {
    transport: { type: 'livekit' },
    handlers: {
    getLocalPcmStream: async function* () {
    while (true) {
    yield new Int16Array(960)
    await new Promise((resolve) => setTimeout(resolve, 20))
    }
    },
    },
    })

    await voice.detach()
    const frames: Int16Array[] = []

    await enableVoice(client, {
    transport: { type: 'livekit' },
    handlers: {
    onRemotePcm: async (pcm, meta) => {
    console.log('audio from', meta.fromIdentity)
    frames.push(new Int16Array(pcm))
    },
    },
    })
    client.on('voice:mute-changed', ({ muted }) => {
    console.log('muted:', muted)
    })

    await client.muteVoice?.(true)

    Before voice is enabled, muteVoice() updates the client state, emits voice:mute-changed, and makes sendVoiceFrame() ignore frames. While an enableVoice() attachment is active, muteVoice() is routed to the transport instead, and the LiveKit transport does not currently mute published audio. Stop your local PCM stream if the bot must go silent. AgentClient also exposes isVoiceMuted().

    Use the Dify example when a bot should forward metatell chat to a Dify application and post the application response back to the room.

    cd examples/dify-bot
    npm ci
    npm run build
    cp .env.example .env
    npm start -- https://metatell.app/YOUR_ROOM_ID