diff --git a/.changeset/send-dtmf-active-room.md b/.changeset/send-dtmf-active-room.md new file mode 100644 index 000000000..7f41649e5 --- /dev/null +++ b/.changeset/send-dtmf-active-room.md @@ -0,0 +1,5 @@ +--- +'@livekit/agents': minor +--- + +Add a beta DTMF sending tool that publishes to the active agent session room. diff --git a/agents/src/beta/index.ts b/agents/src/beta/index.ts index 00697a5fc..d52eed5f8 100644 --- a/agents/src/beta/index.ts +++ b/agents/src/beta/index.ts @@ -33,6 +33,8 @@ export { Instructions } from '../llm/index.js'; export { END_CALL_DESCRIPTION, createEndCallTool, + sendDtmfEvents, + type DtmfEvent, type EndCallToolCalledEvent, type EndCallToolCompletedEvent, type EndCallToolOptions, diff --git a/agents/src/beta/tools/index.ts b/agents/src/beta/tools/index.ts index 8ef18b993..0ab5c7763 100644 --- a/agents/src/beta/tools/index.ts +++ b/agents/src/beta/tools/index.ts @@ -8,3 +8,4 @@ export { type EndCallToolCompletedEvent, type EndCallToolOptions, } from './end_call.js'; +export { sendDtmfEvents, type DtmfEvent } from './send_dtmf.js'; diff --git a/agents/src/beta/tools/send_dtmf.ts b/agents/src/beta/tools/send_dtmf.ts new file mode 100644 index 000000000..fa18ba658 --- /dev/null +++ b/agents/src/beta/tools/send_dtmf.ts @@ -0,0 +1,77 @@ +// SPDX-FileCopyrightText: 2026 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +import { setTimeout as waitFor } from 'node:timers/promises'; +import { z } from 'zod'; +import { getJobContext } from '../../job.js'; +import { tool } from '../../llm/index.js'; + +const DEFAULT_DTMF_PUBLISH_DELAY = 300; + +const DTMF_EVENTS = [ + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '0', + '*', + '#', + 'A', + 'B', + 'C', + 'D', +] as const; + +export type DtmfEvent = (typeof DTMF_EVENTS)[number]; + +function dtmfEventToCode(event: DtmfEvent): number { + if (/^\d$/.test(event)) { + return Number(event); + } + if (event === '*') { + return 10; + } + if (event === '#') { + return 11; + } + + return event.charCodeAt(0) - 'A'.charCodeAt(0) + 12; +} + +export const sendDtmfEvents = tool({ + name: 'send_dtmf_events', + description: ` +Send a list of DTMF events to the telephony provider. + +Call when: +- User wants to send DTMF events +`, + parameters: z.object({ + events: z.array(z.enum(DTMF_EVENTS)).describe('The DTMF events to send.'), + }), + execute: async ({ events }, { ctx }) => { + const room = ctx.session._roomIO?.rtcRoom ?? getJobContext().room; + + for (const event of events) { + try { + const code = dtmfEventToCode(event); + if (!room.localParticipant) { + throw new Error('room local participant is not available'); + } + + await room.localParticipant.publishDtmf(code, event); + await waitFor(DEFAULT_DTMF_PUBLISH_DELAY); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return `Failed to send DTMF event: ${event}. Error: ${message}`; + } + } + + return `Successfully sent DTMF events: ${events.join(', ')}`; + }, +});