blue-ability-56213
12/24/2025, 10:14 PMtall-airline-16756
12/24/2025, 10:14 PMtall-airline-16756
12/24/2025, 10:15 PMctx.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:
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):
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 | DeepWikiblue-ability-56213
12/24/2025, 10:16 PMtall-airline-16756
12/24/2025, 10:16 PMuseDataChannel(...).
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 docsblue-ability-56213
12/24/2025, 10:17 PMtall-airline-16756
12/24/2025, 10:17 PMsendText 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):
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:
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.blue-ability-56213
12/24/2025, 10:18 PMuseDataChannel 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
* tsx
* // Send messages to all participants via the 'chat' topic.
* const { message: latestMessage, send } = useDataChannel('chat', (msg) => console.log("message received", msg));
*
*
* @example
* 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,
};
}`tall-airline-16756
12/24/2025, 10:18 PMuseDataChannel, 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:
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:
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.blue-ability-56213
12/24/2025, 10:20 PMtall-airline-16756
12/24/2025, 10:21 PMuseTextStream('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):
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