'ctx.agent?.sendText(remainingSeconds.toString(), ...
# ask-ai
b
'ctx.agent?.sendText(remainingSeconds.toString(), { topic: 'agent/countdown' })' this is how i try to send data, why i cant receive it on frontend
t
Thanks for reaching out, s. 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 why
ctx.agent?.sendText(remainingSeconds.toString(), { topic: 'agent/countdown' })
is not received on the frontend. Likely causes and what to do •
ctx.agent?.sendText()
is not a valid LiveKit method. Use the room’s local participant to send text from an agent:
Copy code
await ctx.connect(); // ensure agent is connected
  await ctx.room.localParticipant.sendText(remainingSeconds.toString(), {
    topic: 'agent/countdown',
  });
• The frontend must register a matching text handler for that exact topic (case‑sensitive):
Copy code
room.registerTextStreamHandler('agent/countdown', async (reader, info) => {
    const msg = await reader.readAll();
    console.log('Countdown:', msg);
  });
• Ensure both agent and frontend are connected to the same room before sending. Sending before
ctx.connect()
or before the frontend is joined will not deliver messages. If that doesn’t fix it, please share: • The backend snippet showing how you connect the agent and send the message. • The frontend code where you register the handler. • Any console errors or logs. Sources: Job lifecycle | LiveKit docs | Text and transcriptions | LiveKit docs | examples/other | livekit/livekit_composite | DeepWiki
b
useDataChannel(room, 'agent/countdown', (msg) => { console.log('Countdown message received:', msg); })
t
Thanks — I’m responding to your code snippet using
useDataChannel(...)
. Short answer:
useDataChannel
only receives messages sent via data channels (e.g.,
publishData
). It will NOT receive messages sent with
sendText()
/ text streams. Which to use: • If your agent/backend sends with
room.localParticipant.sendText(..., { topic: 'agent/countdown' })
→ use
room.registerTextStreamHandler('agent/countdown', ...)
on the frontend. • If your agent/backend sends with
participant.publishData(data, { topic: 'agent/countdown' })
→
useDataChannel(room, 'agent/countdown', msg => { ... })
is correct. Please tell me which method your backend is using (sendText or publishData) and share the backend send snippet if possible — I’ll give the exact frontend handler to match. Sources: Client SDKs | livekit/livekit_composite | DeepWiki | examples/other | livekit/livekit_composite | DeepWiki | client-sdk-swift | livekit/livekit_composite | DeepWiki | Text and transcriptions | LiveKit docs
b
how do i receive if its being sent with sendtext?
t
You’ll receive
sendText
messages on the frontend by registering a text stream handler for the same topic. Use
room.registerTextStreamHandler
and clean it up on unmount. Example (React + useEffect):
Copy code
useEffect(() => {
  const cleanup = room.registerTextStreamHandler('agent/countdown', async (reader, participantInfo) => {
    const msg = await reader.readAll();
    console.log('Countdown message received:', msg);
  });

  return cleanup; // unregister on unmount
}, [room]);
Notes: • Topic must match exactly (case‑sensitive). • Ensure the frontend is connected to the same room before registering the handler. • The backend should send via:
Copy code
await ctx.connect();
await ctx.room.localParticipant.sendText(remainingSeconds.toString(), { topic: 'agent/countdown' });
If you want, paste your frontend and backend snippets and I’ll check them for mismatches.
b
`import { ROOM_KEY } from '@/features/interview/keys'; import { useObservableState } from '@/features/interview/use-observable-state'; import type { ReceivedDataMessage } from '@alert-easter-19258/components-core'; import { setupDataMessageHandler } from '@alert-easter-19258/components-core'; import type { DataPublishOptions, Room } from 'livekit-client'; import { computed, inject, type MaybeRef } from 'vue'; type UseDataChannelReturnType<T extends string | undefined = undefined> = { isSending: MaybeRef<boolean>; send: (payload: Uint8Array, options: DataPublishOptions) => Promise<void>; message: MaybeRef<ReceivedDataMessage<T> | undefined>; }; /** * The
useDataChannel
hook returns the ability to send and receive messages. * Pass an optional
topic
to narrow down which messages are returned in the messages array. * * @remarks * There is only one data channel. Passing a
topic
does not open a new data channel. * It is only used to filter out messages with no or a different
topic
. * * @example *
Copy code
tsx
 * // Send messages to all participants via the 'chat' topic.
 * const { message: latestMessage, send } = useDataChannel('chat', (msg) => console.log("message received", msg));
 *
* * @example *
Copy code
tsx
 * // Receive all messages (no topic filtering)
 * const { message: latestMessage, send } = useDataChannel((msg) => console.log("message received", msg));
 *
* * @public */ export function useDataChannel<T extends string>( room: Room, topic: T, onMessage?: (msg: ReceivedDataMessage<T>) => void, ): UseDataChannelReturnType<T>; /** * Overload for
useDataChannel
without a topic. See {@link (useDataChannel:1)} for information and usage examples. * * @public */ export function useDataChannel( room: Room, onMessage?: (msg: ReceivedDataMessage) => void, ): UseDataChannelReturnType; /** * @internal */ export function useDataChannel<T extends string>( room: Room, topicOrCallback?: T | ((msg: ReceivedDataMessage) => void), callback?: (msg: ReceivedDataMessage<T>) => void, ) { const onMessage = typeof topicOrCallback === 'function' ? topicOrCallback : callback; const topic = typeof topicOrCallback === 'string' ? topicOrCallback : undefined; const handler = computed(() => setupDataMessageHandler(room, topic, onMessage)); const message = useObservableState(handler.value.messageObservable, undefined); const isSending = useObservableState(handler.value.isSendingObservable, false); return { message, send: handler.value.send, isSending, }; }`
t
You're on the right track — in Vue you must use the text stream API, not
useDataChannel
, to receive messages sent with
sendText()
. What to do (Vue example) • Register a text stream handler after the room is connected and clean it up on unmount:
Copy code
import { onMounted, onBeforeUnmount } from 'vue';

onMounted(() => {
  const cleanup = room.registerTextStreamHandler(
    'agent/countdown',
    async (reader, participantInfo) => {
      const msg = await reader.readAll();
      console.log('Countdown message received:', msg);
      // handle msg (string)
    }
  );

  onBeforeUnmount(() => {
    cleanup?.();
  });
});
Key points • Topic must match exactly (case-sensitive): 'agent/countdown'. • Ensure the frontend has joined the same room before registering the handler. • Backend must send via:
Copy code
await ctx.connect();
  await ctx.room.localParticipant.sendText(remainingSeconds.toString(), { topic: 'agent/countdown' });
If you paste your backend send snippet and the place where you register the handler, I’ll check for mismatches.
b
does livekit react components provide hook for reading text
t
Yes — LiveKit React components provide a hook for reading text streams: useTextStream. What to know • Use
useTextStream('your-topic')
to subscribe to text sent with
sendText()
for that topic (topics are case‑sensitive). • There are helper hooks: ◦
useTranscriptions()
— subscribes to
lk.transcription
. ◦
useChat()
— chat-specific handling. • For custom topics (like
agent/countdown
), use
useTextStream
. Example (React):
Copy code
const { textStreams } = useTextStream('agent/countdown');

// textStreams contains incoming TextStreamData entries; map or inspect them:
useEffect(() => {
  textStreams.forEach(s => console.log('msg', s.text, s.participant));
}, [textStreams]);
If you want, paste your React code and I’ll show how to integrate
useTextStream
with your UI. Sources: packages/react/etc/components-react.api.md | Text and transcriptions | LiveKit docs