@mastra/livekit 0.5.2-alpha.0 → 0.5.3-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.ts.map +1 -1
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/plugin-entry.cjs +1 -1
- package/dist/plugin-entry.js +1 -1
- package/dist/{remote-D3IpNhh0.js → remote-DNmutTEU.js} +3 -3
- package/dist/remote-DNmutTEU.js.map +1 -0
- package/dist/{remote-DvBYZkd-.cjs → remote-DO33RaMO.cjs} +3 -3
- package/dist/remote-DO33RaMO.cjs.map +1 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/speech.d.ts.map +1 -1
- package/dist/worker-entry.cjs +1 -1
- package/dist/worker-entry.js +1 -1
- package/package.json +5 -5
- package/dist/remote-D3IpNhh0.js.map +0 -1
- package/dist/remote-DvBYZkd-.cjs.map +0 -1
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,KAAK,IAAI,WAAW,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC7G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,uBAAuB,EAAyC,MAAM,gBAAgB,CAAC;AAChG,OAAO,KAAK,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAK5E,+GAA+G;AAC/G,eAAO,MAAM,2BAA2B,2DAA2D,CAAC;AAEpG;;;;GAIG;AACH,qBAAa,kBAAkB;IAG3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHvB,OAAO,CAAC,MAAM,CAAS;IACvB,YACmB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAC7B,GAAG,GAAE,MAAmB,EAGzB;IACD;;2GAEuG;IACvG,GAAG,CAAC,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,SAAS,CAGhD;IACD,gGAAgG;IAChG,aAAa,CAAC,GAAG,GAAE,MAAmB,GAAG,IAAI,CAE5C;CACF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,CAoBlG;AAED,MAAM,MAAM,mBAAmB,GAAG,OAAO,CAAC,yBAAyB,CAAC,OAAO,CAAC,CAAC,CAAC;AAE9E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,gBAAgB,EAAE,MAAM,CAAC;IACzB,2DAA2D;IAC3D,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CA2BvE;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;IACrD;;;;;;;;OAQG;IACH,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,mGAAmG;IACnG,OAAO,EAAE,GAAG,CAAC,WAAW,CAAC;IACzB,gFAAgF;IAChF,MAAM,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACvC,+CAA+C;IAC/C,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,qFAAqF;IACrF,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;CAC3C;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,6FAA6F;IAC7F,IAAI,EAAE,MAAM,CAAC;IACb,2DAA2D;IAC3D,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;IACrB,iFAAiF;IACjF,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAED,oGAAoG;AACpG,MAAM,WAAW,wBAAyB,SAAQ,gBAAgB;IAChE,8CAA8C;IAC9C,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5F;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,GAAG,EAAE,gBAAgB,KAClB,cAAc,CAAC,eAAe,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC,CAAC;AAE9F,MAAM,WAAW,0BAA0B;IACzC,uFAAuF;IACvF,KAAK,EAAE,WAAW,CAAC;IACnB,qFAAqF;IACrF,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,qGAAqG;IACrG,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACtE,4GAA4G;IAC5G,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,4GAA4G;IAC5G,cAAc,CAAC,EAAE,qBAAqB,CAAC;CACxC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,mBAAmB,CAiHlG;AAED,MAAM,WAAW,uBAAuB;IACtC,+FAA+F;IAC/F,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAC/B;;;;OAIG;IACH,MAAM,CAAC,EAAE,sBAAsB,GAAG,KAAK,CAAC;IACxC,uDAAuD;IACvD,cAAc,CAAC,EAAE,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACtE;;;;;OAKG;IACH,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,qBAAqB,CAAC;IACvC;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,oFAAoF;IACpF,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,2GAA2G;IAC3G,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,EAAE,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,GAAG,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,GAAG,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,cAAc,CAAC,CAAC;CAC5D;AAiCD;;;;;;;GAOG;AACH,qBAAa,gBAAiB,SAAQ,KAAK,CAAC,KAAK;IAC/C,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,sBAAsB,GAAG,KAAK,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;IAC7C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAsB;IACrD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAuB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAqB;IAE/C,YAAY,OAAO,EAAE,uBAAuB,EAwC3C;IAEc,OAAO,CACpB,OAAO,EAAE,GAAG,CAAC,WAAW,EACxB,QAAQ,EAAE,GAAG,CAAC,WAAW,EACzB,cAAc,EAAE,KAAK,CAAC,aAAa,GAClC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,SAAS,GAAG,MAAM,GAAG,aAAa,CAAC,GAAG,IAAI,CAAC,CAmCxE;CACF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAEzF;AAED,gGAAgG;AAChG,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,mBAAmB,EAC9B,GAAG,EAAE,gBAAgB,EACrB,aAAa,CAAC,EAAE,oBAAoB,GACnC,OAAO,CAAC;IAAE,KAAK,EAAE,cAAc,CAAC,eAAe,CAAC,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CA8C7G"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_workflow_generator = require("./workflow-generator-RonPOBE2.cjs");
|
|
3
|
-
let _livekit_protocol = require("@livekit/protocol");
|
|
4
3
|
let livekit_server_sdk = require("livekit-server-sdk");
|
|
5
4
|
let zod_v4 = require("zod/v4");
|
|
6
5
|
let _mastra_core_tools = require("@mastra/core/tools");
|
|
@@ -115,7 +114,7 @@ function liveKitConnectionRoute(options = {}) {
|
|
|
115
114
|
canPublishData: true,
|
|
116
115
|
canUpdateOwnMetadata: true
|
|
117
116
|
});
|
|
118
|
-
if (recording === void 0) token.roomConfig = new
|
|
117
|
+
if (recording === void 0) token.roomConfig = new livekit_server_sdk.RoomConfiguration({ agents: [new livekit_server_sdk.RoomAgentDispatch({
|
|
119
118
|
agentName: options.agentName ?? "mastra-voice",
|
|
120
119
|
metadata: require_workflow_generator.serializeSessionMetadata(metadata)
|
|
121
120
|
})] });
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["RoomServiceClient","RoomEgress","serializeSessionMetadata","AgentDispatchClient","AccessToken","RoomConfiguration","RoomAgentDispatch","serializeSessionMetadata","z","DEFAULT_DESCRIPTION","z","createTool","createTool","z"],"sources":["../src/recording.ts","../src/dispatch.ts","../src/routes.ts","../src/recording-route.ts","../src/consent.ts","../src/end-call.ts"],"sourcesContent":["import { RoomEgress, RoomServiceClient } from 'livekit-server-sdk';\n\n/**\n * LiveKit auto-egress settings, accepted as a plain object or a RoomEgress instance.\n * Use `room.audioOnly` and `room.fileOutputs` for a single mixed audio recording.\n * Storage credentials belong here on the server, never in session metadata or tokens.\n */\nexport type LiveKitRecordingOptions = NonNullable<ConstructorParameters<typeof RoomEgress>[0]>;\n\n/** A recording-enabled session cannot reuse an existing LiveKit room. */\nexport class LiveKitRecordingRoomConflictError extends Error {\n readonly roomName: string;\n\n constructor(roomName: string) {\n super(\n `@mastra/livekit: recording requires a new room, but room \"${roomName}\" already exists. Use a fresh room name.`,\n );\n this.name = 'LiveKitRecordingRoomConflictError';\n this.roomName = roomName;\n }\n}\n\n/** Creates a fresh room with recording configured before any agent or caller joins. */\nexport async function createRecordingRoom(options: {\n roomName: string;\n serverUrl: string;\n apiKey: string;\n apiSecret: string;\n recording: LiveKitRecordingOptions;\n}) {\n const { recording, roomName } = options;\n if (!recording.room && !recording.participant && !recording.tracks) {\n throw new Error('@mastra/livekit: recording must configure at least one of room, participant, or tracks.');\n }\n\n const client = new RoomServiceClient(options.serverUrl.replace(/^ws/, 'http'), options.apiKey, options.apiSecret);\n // CreateRoom reuses existing rooms without applying room-composite auto-egress.\n // This preflight is not atomic: callers must use a fresh name and serialize room creation.\n const rooms = await client.listRooms([roomName]);\n if (rooms.length > 0) {\n throw new LiveKitRecordingRoomConflictError(roomName);\n }\n\n return client.createRoom({ name: roomName, egress: new RoomEgress(recording) });\n}\n","import { AgentDispatchClient } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { createRecordingRoom } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\nexport interface DispatchVoiceSessionOptions {\n /**\n * Room to dispatch the agent into (created on demand). With recording enabled, use a unique\n * name for each call. An existing room throws LiveKitRecordingRoomConflictError.\n */\n roomName: string;\n /** Must match the worker's `agentName`. Defaults to `'mastra-voice'`. */\n agentName?: string;\n metadata?: LiveKitSessionMetadata;\n /** Create a fresh room with automatic recording before dispatch. Omit to keep dispatch-only behavior. */\n recording?: LiveKitRecordingOptions;\n /** Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n}\n\nfunction toHttpUrl(url: string): string {\n return url.replace(/^ws/, 'http');\n}\n\n/**\n * Programmatically dispatches a Mastra voice agent into a LiveKit room — for\n * server-initiated sessions such as outbound calls or joining an existing room.\n */\nexport async function dispatchVoiceSession(options: DispatchVoiceSessionOptions) {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl) {\n throw new Error('@mastra/livekit: set LIVEKIT_URL or pass serverUrl to dispatchVoiceSession.');\n }\n if (!apiKey || !apiSecret) {\n throw new Error(\n '@mastra/livekit: set LIVEKIT_API_KEY and LIVEKIT_API_SECRET or pass apiKey/apiSecret to dispatchVoiceSession.',\n );\n }\n const metadata = serializeSessionMetadata(options.metadata ?? {});\n if (options.recording !== undefined) {\n await createRecordingRoom({\n roomName: options.roomName,\n serverUrl,\n apiKey,\n apiSecret,\n recording: options.recording,\n });\n }\n const client = new AgentDispatchClient(toHttpUrl(serverUrl), apiKey, apiSecret);\n return client.createDispatch(options.roomName, options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME, {\n metadata,\n });\n}\n","import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';\nimport type { ContextWithMastra, ApiRoute } from '@mastra/core/server';\nimport { AccessToken } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { dispatchVoiceSession } from './dispatch';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { LiveKitRecordingRoomConflictError } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\n/** Response body of the connection-details route. Matches LiveKit's frontend starter contract. */\nexport interface LiveKitConnectionDetails {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n}\n\nexport interface ConnectionRequestArgs {\n body: Record<string, unknown>;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitConnectionRouteOptions {\n /**\n * Route path. Defaults to `/voice/livekit/connection-details`. Mastra reserves the\n * `/api` prefix for built-in routes, so custom paths must not start with `/api`.\n */\n path?: string;\n /** LiveKit server URL (`wss://...`). Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n /** LiveKit agent name for explicit dispatch. Must match the worker's `agentName`. */\n agentName?: string;\n /** Token time-to-live. Defaults to `'15m'`. */\n ttl?: string | number;\n /** Defaults to `true` (Mastra custom routes require auth unless opted out). */\n requiresAuth?: boolean;\n /**\n * Room name or a function that derives one from the request. Defaults to a generated name.\n * With recording enabled, use a unique name for each call. An existing room returns HTTP 409\n * before agent dispatch or token issuance.\n */\n roomName?: string | ((args: ConnectionRequestArgs) => string);\n participantIdentity?: string | ((args: ConnectionRequestArgs) => string);\n /**\n * Session metadata delivered to the worker via agent dispatch. Defaults to passing\n * through `agentId`, `threadId`, and `resourceId` from the request body.\n */\n metadata?: (args: ConnectionRequestArgs) => LiveKitSessionMetadata | Promise<LiveKitSessionMetadata>;\n /**\n * Create a fresh room with automatic recording and dispatch the agent before returning a token.\n * The server-side callback receives the resolved room name; return undefined to skip recording.\n * Recording settings and storage credentials are never included in the participant token.\n */\n recording?:\n | LiveKitRecordingOptions\n | ((\n args: ConnectionRequestArgs & { roomName: string },\n ) => LiveKitRecordingOptions | undefined | Promise<LiveKitRecordingOptions | undefined>);\n}\n\nfunction stringField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * An ApiRoute for `server.apiRoutes` that mints a LiveKit access token with the Mastra\n * voice agent dispatched into the room. Frontends call it to join a voice session:\n *\n * ```ts\n * export const mastra = new Mastra({\n * agents: { support },\n * server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })] },\n * });\n * ```\n */\nexport function liveKitConnectionRoute(options: LiveKitConnectionRouteOptions = {}): ApiRoute {\n const handler = async (c: ContextWithMastra) => {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl || !apiKey || !apiSecret) {\n return c.json(\n {\n error:\n 'LiveKit is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET ' +\n '(or pass serverUrl/apiKey/apiSecret to liveKitConnectionRoute).',\n },\n 500,\n );\n }\n\n const body: Record<string, unknown> = await c.req\n .json()\n .then((parsed: unknown) =>\n parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {},\n )\n .catch(() => ({}));\n const args: ConnectionRequestArgs = { body, context: c };\n\n const metadata: LiveKitSessionMetadata = options.metadata\n ? await options.metadata(args)\n : {\n agentId: stringField(body, 'agentId'),\n threadId: stringField(body, 'threadId'),\n resourceId: stringField(body, 'resourceId'),\n };\n\n const roomName =\n typeof options.roomName === 'function'\n ? options.roomName(args)\n : (options.roomName ?? `mastra-voice-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n const identity =\n typeof options.participantIdentity === 'function'\n ? options.participantIdentity(args)\n : (options.participantIdentity ?? metadata.resourceId ?? `user-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n // One memory thread per room unless the caller pins a thread explicitly.\n metadata.threadId ??= roomName;\n\n const recording =\n typeof options.recording === 'function' ? await options.recording({ ...args, roomName }) : options.recording;\n if (recording !== undefined) {\n try {\n await dispatchVoiceSession({\n roomName,\n agentName: options.agentName,\n metadata,\n serverUrl,\n apiKey,\n apiSecret,\n recording,\n });\n } catch (error) {\n if (error instanceof LiveKitRecordingRoomConflictError) {\n return c.json({ error: 'Recording requires a new room. Use a unique roomName for each call.' }, 409);\n }\n throw error;\n }\n }\n\n const token = new AccessToken(apiKey, apiSecret, { identity, ttl: options.ttl ?? '15m' });\n token.addGrant({\n room: roomName,\n roomJoin: true,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n canUpdateOwnMetadata: true,\n });\n // A pre-created room needs explicit dispatch; token roomConfig only applies at room creation.\n if (recording === undefined) {\n token.roomConfig = new RoomConfiguration({\n agents: [\n new RoomAgentDispatch({\n agentName: options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME,\n metadata: serializeSessionMetadata(metadata),\n }),\n ],\n });\n }\n\n const details: LiveKitConnectionDetails = {\n serverUrl,\n roomName,\n participantName: identity,\n participantToken: await token.toJwt(),\n };\n return c.json(details);\n };\n\n return {\n // Studio calls this default path and /api/system/packages reports it — change all three together.\n path: options.path ?? '/voice/livekit/connection-details',\n method: 'POST',\n requiresAuth: options.requiresAuth,\n handler,\n } as ApiRoute;\n}\n","import type { ApiRoute, ContextWithMastra } from '@mastra/core/server';\nimport type { SpanRecord } from '@mastra/core/storage';\nimport { z } from 'zod/v4';\nimport type { LiveKitRecording, LiveKitRecordingResponse } from './recording-types';\n\nconst traceIdSchema = z.string().min(1).max(256);\nexport const recordingSchema = z.object({\n url: z.url({ protocol: /^https?$/ }),\n expiresAt: z.string().datetime().optional(),\n}) satisfies z.ZodType<LiveKitRecording>;\n\nexport interface LiveKitRecordingResolverArgs {\n traceId: string;\n /** Taken from the stored voice call span, never from the browser request. */\n roomName: string;\n span: SpanRecord;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitRecordingRouteOptions {\n /** Studio discovers this route at its default path: /voice/livekit/recordings/:traceId. */\n path?: string;\n /** Defaults to true. Only disable for a local demo. */\n requiresAuth?: boolean;\n /** Required trace-specific access policy. Only true permits trace lookup and URL resolution. */\n authorize: (args: { traceId: string; context: ContextWithMastra }) => boolean | Promise<boolean>;\n /** Return a playable URL, or undefined if the recording is not available yet. */\n resolveRecording: (args: LiveKitRecordingResolverArgs) => Promise<LiveKitRecording | undefined>;\n}\n\n/** Resolves a voice call trace to a recording without persisting signed URLs in traces. */\nexport function liveKitRecordingRoute(options: LiveKitRecordingRouteOptions): ApiRoute {\n if (typeof options.authorize !== 'function') {\n throw new Error('liveKitRecordingRoute requires an authorize callback.');\n }\n return {\n path: options.path ?? '/voice/livekit/recordings/:traceId',\n method: 'GET',\n requiresAuth: options.requiresAuth ?? true,\n handler: async (context: ContextWithMastra) => {\n context.header('Cache-Control', 'no-store');\n const input = traceIdSchema.safeParse(context.req.param('traceId'));\n if (!input.success) return context.json({ error: 'A valid trace ID is required.' }, 400);\n const traceId = input.data;\n try {\n if ((await options.authorize({ traceId, context })) !== true) {\n return context.json({ error: 'Access to this recording is denied.' }, 403);\n }\n const store = await context.get('mastra').getStorage()?.getStore('observability');\n if (!store) return context.json({ error: 'Trace storage is not configured.' }, 503);\n const trace = await store.getTrace({ traceId });\n const calls =\n trace?.spans.filter(\n span =>\n span.name === 'voice call' &&\n typeof span.metadata?.roomName === 'string' &&\n span.metadata.roomName.length > 0,\n ) ?? [];\n if (!calls.length) return context.json({ error: 'Voice call trace not found.' }, 404);\n if (calls.length !== 1) return context.json({ error: 'This trace contains multiple voice calls.' }, 409);\n const span = calls[0]!;\n const roomName = span.metadata?.roomName;\n if (typeof roomName !== 'string') return context.json({ error: 'Voice call trace not found.' }, 404);\n const recording = await options.resolveRecording({ traceId, roomName, span, context });\n if (!recording) return context.json({ status: 'unavailable' } satisfies LiveKitRecordingResponse);\n // Validate and strip extra fields so storage credentials cannot accidentally become response fields.\n const result = recordingSchema.safeParse(recording);\n if (!result.success) return context.json({ error: 'Unable to load the call recording.' }, 502);\n return context.json({ status: 'ready', ...result.data } satisfies LiveKitRecordingResponse);\n } catch {\n // Storage/provider errors can contain credentials or signed request details.\n return context.json({ error: 'Unable to load the call recording.' }, 502);\n }\n },\n };\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** A consent decision captured from the caller during a call. */\nexport interface ConsentGrant {\n /**\n * The consent item, matching a key in the worker's `configuration.consentPolicy` (e.g.\n * `'summaryStorage'`).\n */\n item: string;\n /** `true` if the caller agreed, `false` if they declined. */\n granted: boolean;\n /** The caller the grant belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread the grant was captured on, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface ConsentToolOptions {\n /**\n * Persist the caller's decision to YOUR system of record (database, CRM, consent ledger). Called\n * each time the caller answers a consent question — keep it quick, it runs inside the turn. This\n * tool owns extracting the caller's identity from the tool execution context; you own storage, so\n * consent lives in a durable, compliant place rather than an opaque plugin store. Read those\n * grants back at `onCallEnd` (or before any consent-gated step) to enforce the requirement.\n */\n onGrant: (grant: ConsentGrant) => void | Promise<void>;\n /** Tool id the agent calls. Defaults to `'recordConsent'`. */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /** Restrict the accepted consent items to this set (becomes an enum). Defaults to any string. */\n items?: readonly string[];\n}\n\nconst DEFAULT_DESCRIPTION =\n \"Record the caller's consent decision for a data-processing item (for example, storing a summary \" +\n 'of the call). Call this as soon as the caller answers a consent question: granted = true if they ' +\n 'agree, false if they decline. Do not proceed with an activity that requires consent until you ' +\n 'have recorded the caller granting it.';\n\n/**\n * Builds a Mastra tool that captures the caller's consent decisions at runtime — the companion to\n * the worker's `configuration.consentPolicy`, which only DECLARES which consents a call needs. Add\n * it to the agent that answers the call; on each call the tool reads the caller's `resourceId` /\n * `threadId` from its execution context and hands the decision to your `onGrant` store. The runtime\n * grants live only where `onGrant` put them: enforce consent by reading them back from your own\n * store — at `onCallEnd` or before any consent-gated action — cross-checking against the declared\n * requirements in `VoiceCallEndArgs.configuration.consentPolicy` (the configuration carries the\n * policy, never the grants).\n *\n * ```ts\n * // agent tools\n * recordConsent: createConsentTool({\n * items: ['summaryStorage'],\n * onGrant: async ({ item, granted, resourceId }) => {\n * if (resourceId) await db.saveConsent(resourceId, item, granted);\n * },\n * }),\n * ```\n */\nexport function createConsentTool(options: ConsentToolOptions) {\n const items = options.items;\n const itemSchema = items && items.length > 0 ? z.enum([...items] as [string, ...string[]]) : z.string();\n return createTool({\n id: options.id ?? 'recordConsent',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n item: itemSchema.describe(\n \"The consent item the caller is answering for (matches the deployment's required consents).\",\n ),\n granted: z.boolean().describe('True if the caller agreed, false if they declined.'),\n }),\n execute: async ({ item, granted }, { agent }) => {\n await options.onGrant({ item, granted, resourceId: agent?.resourceId, threadId: agent?.threadId });\n return { recorded: true as const, item, granted };\n },\n });\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** Details of the agent's decision to end the call, handed to {@link EndCallToolOptions.onEndCall}. */\nexport interface EndCallRequest {\n /** Short reason the agent gave for ending the call, if any (e.g. \"caller said goodbye\"). */\n reason?: string;\n /** The caller the call belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface EndCallToolOptions {\n /**\n * Tool id the agent calls to end the call. Must match the worker's\n * `configuration.endCall.tool` (both default to `'endCall'`), since that's the name the worker\n * watches for to hang up.\n */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /**\n * Optional bookkeeping hook, called when the agent invokes the tool (records the reason, marks\n * the call resolved in your CRM, …). Runs inside the turn — keep it quick. It does NOT hang up\n * the call; the worker does that once the agent's final words finish playing. The tool reads the\n * caller's identity from its execution context and passes it here.\n */\n onEndCall?: (request: EndCallRequest) => void | Promise<void>;\n}\n\nconst DEFAULT_DESCRIPTION =\n 'End the phone call. Call this only after you have said goodbye and there is nothing left to do — ' +\n 'for example the caller says goodbye, or the task is complete and you have wrapped up. Say your ' +\n 'closing line first, then call this as your final action; the call hangs up once your words finish ' +\n 'playing. Your closing line must be a final statement, never a question — if you are asking the ' +\n 'caller anything (for example whether they need anything else), do NOT call this in that reply; ' +\n 'wait for their answer. Do not call it if the caller still needs something.';\n\n/**\n * Builds a Mastra tool the agent calls to end the call itself (say goodbye → hang up). It is the\n * agent-visible half of agent-initiated hang-up: the tool only SIGNALS intent (and runs optional\n * bookkeeping) — it can't reach the LiveKit room from inside `agent.stream()`. The worker owns the\n * hang-up: with `configuration.endCall` set, it watches each turn for this tool, waits for the\n * agent's closing words to finish playing, then disconnects (running `onCallEnd` on the way out).\n * Keeping the tool inside the single agent means it stays visible/editable in Studio and works on\n * the agent and workflow reply paths alike.\n *\n * ```ts\n * // agent tools\n * endCall: createEndCallTool({\n * onEndCall: ({ reason, resourceId }) => log.info('agent ended call', { reason, resourceId }),\n * }),\n * // worker\n * createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } });\n * ```\n */\nexport function createEndCallTool(options: EndCallToolOptions = {}) {\n return createTool({\n id: options.id ?? 'endCall',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n reason: z\n .string()\n .nullish()\n .describe('Short reason the call is ending, e.g. \"caller said goodbye\" or \"task complete\".'),\n }),\n outputSchema: z.object({ ended: z.boolean() }),\n execute: async ({ reason }, { agent }) => {\n try {\n await options.onEndCall?.({\n reason: reason ?? undefined,\n resourceId: agent?.resourceId,\n threadId: agent?.threadId,\n });\n } catch (error) {\n // Bookkeeping is best-effort — the tool must still signal `{ ended: true }` so the worker's\n // end-call detector runs and the call actually hangs up.\n console.warn('@mastra/livekit: onEndCall hook threw', error);\n }\n return { ended: true as const };\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["RoomServiceClient","RoomEgress","serializeSessionMetadata","AgentDispatchClient","AccessToken","RoomConfiguration","RoomAgentDispatch","serializeSessionMetadata","z","DEFAULT_DESCRIPTION","z","createTool","createTool","z"],"sources":["../src/recording.ts","../src/dispatch.ts","../src/routes.ts","../src/recording-route.ts","../src/consent.ts","../src/end-call.ts"],"sourcesContent":["import { RoomEgress, RoomServiceClient } from 'livekit-server-sdk';\n\n/**\n * LiveKit auto-egress settings, accepted as a plain object or a RoomEgress instance.\n * Use `room.audioOnly` and `room.fileOutputs` for a single mixed audio recording.\n * Storage credentials belong here on the server, never in session metadata or tokens.\n */\nexport type LiveKitRecordingOptions = NonNullable<ConstructorParameters<typeof RoomEgress>[0]>;\n\n/** A recording-enabled session cannot reuse an existing LiveKit room. */\nexport class LiveKitRecordingRoomConflictError extends Error {\n readonly roomName: string;\n\n constructor(roomName: string) {\n super(\n `@mastra/livekit: recording requires a new room, but room \"${roomName}\" already exists. Use a fresh room name.`,\n );\n this.name = 'LiveKitRecordingRoomConflictError';\n this.roomName = roomName;\n }\n}\n\n/** Creates a fresh room with recording configured before any agent or caller joins. */\nexport async function createRecordingRoom(options: {\n roomName: string;\n serverUrl: string;\n apiKey: string;\n apiSecret: string;\n recording: LiveKitRecordingOptions;\n}) {\n const { recording, roomName } = options;\n if (!recording.room && !recording.participant && !recording.tracks) {\n throw new Error('@mastra/livekit: recording must configure at least one of room, participant, or tracks.');\n }\n\n const client = new RoomServiceClient(options.serverUrl.replace(/^ws/, 'http'), options.apiKey, options.apiSecret);\n // CreateRoom reuses existing rooms without applying room-composite auto-egress.\n // This preflight is not atomic: callers must use a fresh name and serialize room creation.\n const rooms = await client.listRooms([roomName]);\n if (rooms.length > 0) {\n throw new LiveKitRecordingRoomConflictError(roomName);\n }\n\n return client.createRoom({ name: roomName, egress: new RoomEgress(recording) });\n}\n","import { AgentDispatchClient } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { createRecordingRoom } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\nexport interface DispatchVoiceSessionOptions {\n /**\n * Room to dispatch the agent into (created on demand). With recording enabled, use a unique\n * name for each call. An existing room throws LiveKitRecordingRoomConflictError.\n */\n roomName: string;\n /** Must match the worker's `agentName`. Defaults to `'mastra-voice'`. */\n agentName?: string;\n metadata?: LiveKitSessionMetadata;\n /** Create a fresh room with automatic recording before dispatch. Omit to keep dispatch-only behavior. */\n recording?: LiveKitRecordingOptions;\n /** Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n}\n\nfunction toHttpUrl(url: string): string {\n return url.replace(/^ws/, 'http');\n}\n\n/**\n * Programmatically dispatches a Mastra voice agent into a LiveKit room — for\n * server-initiated sessions such as outbound calls or joining an existing room.\n */\nexport async function dispatchVoiceSession(options: DispatchVoiceSessionOptions) {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl) {\n throw new Error('@mastra/livekit: set LIVEKIT_URL or pass serverUrl to dispatchVoiceSession.');\n }\n if (!apiKey || !apiSecret) {\n throw new Error(\n '@mastra/livekit: set LIVEKIT_API_KEY and LIVEKIT_API_SECRET or pass apiKey/apiSecret to dispatchVoiceSession.',\n );\n }\n const metadata = serializeSessionMetadata(options.metadata ?? {});\n if (options.recording !== undefined) {\n await createRecordingRoom({\n roomName: options.roomName,\n serverUrl,\n apiKey,\n apiSecret,\n recording: options.recording,\n });\n }\n const client = new AgentDispatchClient(toHttpUrl(serverUrl), apiKey, apiSecret);\n return client.createDispatch(options.roomName, options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME, {\n metadata,\n });\n}\n","import type { ContextWithMastra, ApiRoute } from '@mastra/core/server';\nimport { AccessToken, RoomAgentDispatch, RoomConfiguration } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { dispatchVoiceSession } from './dispatch';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { LiveKitRecordingRoomConflictError } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\n/** Response body of the connection-details route. Matches LiveKit's frontend starter contract. */\nexport interface LiveKitConnectionDetails {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n}\n\nexport interface ConnectionRequestArgs {\n body: Record<string, unknown>;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitConnectionRouteOptions {\n /**\n * Route path. Defaults to `/voice/livekit/connection-details`. Mastra reserves the\n * `/api` prefix for built-in routes, so custom paths must not start with `/api`.\n */\n path?: string;\n /** LiveKit server URL (`wss://...`). Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n /** LiveKit agent name for explicit dispatch. Must match the worker's `agentName`. */\n agentName?: string;\n /** Token time-to-live. Defaults to `'15m'`. */\n ttl?: string | number;\n /** Defaults to `true` (Mastra custom routes require auth unless opted out). */\n requiresAuth?: boolean;\n /**\n * Room name or a function that derives one from the request. Defaults to a generated name.\n * With recording enabled, use a unique name for each call. An existing room returns HTTP 409\n * before agent dispatch or token issuance.\n */\n roomName?: string | ((args: ConnectionRequestArgs) => string);\n participantIdentity?: string | ((args: ConnectionRequestArgs) => string);\n /**\n * Session metadata delivered to the worker via agent dispatch. Defaults to passing\n * through `agentId`, `threadId`, and `resourceId` from the request body.\n */\n metadata?: (args: ConnectionRequestArgs) => LiveKitSessionMetadata | Promise<LiveKitSessionMetadata>;\n /**\n * Create a fresh room with automatic recording and dispatch the agent before returning a token.\n * The server-side callback receives the resolved room name; return undefined to skip recording.\n * Recording settings and storage credentials are never included in the participant token.\n */\n recording?:\n | LiveKitRecordingOptions\n | ((\n args: ConnectionRequestArgs & { roomName: string },\n ) => LiveKitRecordingOptions | undefined | Promise<LiveKitRecordingOptions | undefined>);\n}\n\nfunction stringField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * An ApiRoute for `server.apiRoutes` that mints a LiveKit access token with the Mastra\n * voice agent dispatched into the room. Frontends call it to join a voice session:\n *\n * ```ts\n * export const mastra = new Mastra({\n * agents: { support },\n * server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })] },\n * });\n * ```\n */\nexport function liveKitConnectionRoute(options: LiveKitConnectionRouteOptions = {}): ApiRoute {\n const handler = async (c: ContextWithMastra) => {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl || !apiKey || !apiSecret) {\n return c.json(\n {\n error:\n 'LiveKit is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET ' +\n '(or pass serverUrl/apiKey/apiSecret to liveKitConnectionRoute).',\n },\n 500,\n );\n }\n\n const body: Record<string, unknown> = await c.req\n .json()\n .then((parsed: unknown) =>\n parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {},\n )\n .catch(() => ({}));\n const args: ConnectionRequestArgs = { body, context: c };\n\n const metadata: LiveKitSessionMetadata = options.metadata\n ? await options.metadata(args)\n : {\n agentId: stringField(body, 'agentId'),\n threadId: stringField(body, 'threadId'),\n resourceId: stringField(body, 'resourceId'),\n };\n\n const roomName =\n typeof options.roomName === 'function'\n ? options.roomName(args)\n : (options.roomName ?? `mastra-voice-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n const identity =\n typeof options.participantIdentity === 'function'\n ? options.participantIdentity(args)\n : (options.participantIdentity ?? metadata.resourceId ?? `user-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n // One memory thread per room unless the caller pins a thread explicitly.\n metadata.threadId ??= roomName;\n\n const recording =\n typeof options.recording === 'function' ? await options.recording({ ...args, roomName }) : options.recording;\n if (recording !== undefined) {\n try {\n await dispatchVoiceSession({\n roomName,\n agentName: options.agentName,\n metadata,\n serverUrl,\n apiKey,\n apiSecret,\n recording,\n });\n } catch (error) {\n if (error instanceof LiveKitRecordingRoomConflictError) {\n return c.json({ error: 'Recording requires a new room. Use a unique roomName for each call.' }, 409);\n }\n throw error;\n }\n }\n\n const token = new AccessToken(apiKey, apiSecret, { identity, ttl: options.ttl ?? '15m' });\n token.addGrant({\n room: roomName,\n roomJoin: true,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n canUpdateOwnMetadata: true,\n });\n // A pre-created room needs explicit dispatch; token roomConfig only applies at room creation.\n if (recording === undefined) {\n token.roomConfig = new RoomConfiguration({\n agents: [\n new RoomAgentDispatch({\n agentName: options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME,\n metadata: serializeSessionMetadata(metadata),\n }),\n ],\n });\n }\n\n const details: LiveKitConnectionDetails = {\n serverUrl,\n roomName,\n participantName: identity,\n participantToken: await token.toJwt(),\n };\n return c.json(details);\n };\n\n return {\n // Studio calls this default path and /api/system/packages reports it — change all three together.\n path: options.path ?? '/voice/livekit/connection-details',\n method: 'POST',\n requiresAuth: options.requiresAuth,\n handler,\n } as ApiRoute;\n}\n","import type { ApiRoute, ContextWithMastra } from '@mastra/core/server';\nimport type { SpanRecord } from '@mastra/core/storage';\nimport { z } from 'zod/v4';\nimport type { LiveKitRecording, LiveKitRecordingResponse } from './recording-types';\n\nconst traceIdSchema = z.string().min(1).max(256);\nexport const recordingSchema = z.object({\n url: z.url({ protocol: /^https?$/ }),\n expiresAt: z.string().datetime().optional(),\n}) satisfies z.ZodType<LiveKitRecording>;\n\nexport interface LiveKitRecordingResolverArgs {\n traceId: string;\n /** Taken from the stored voice call span, never from the browser request. */\n roomName: string;\n span: SpanRecord;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitRecordingRouteOptions {\n /** Studio discovers this route at its default path: /voice/livekit/recordings/:traceId. */\n path?: string;\n /** Defaults to true. Only disable for a local demo. */\n requiresAuth?: boolean;\n /** Required trace-specific access policy. Only true permits trace lookup and URL resolution. */\n authorize: (args: { traceId: string; context: ContextWithMastra }) => boolean | Promise<boolean>;\n /** Return a playable URL, or undefined if the recording is not available yet. */\n resolveRecording: (args: LiveKitRecordingResolverArgs) => Promise<LiveKitRecording | undefined>;\n}\n\n/** Resolves a voice call trace to a recording without persisting signed URLs in traces. */\nexport function liveKitRecordingRoute(options: LiveKitRecordingRouteOptions): ApiRoute {\n if (typeof options.authorize !== 'function') {\n throw new Error('liveKitRecordingRoute requires an authorize callback.');\n }\n return {\n path: options.path ?? '/voice/livekit/recordings/:traceId',\n method: 'GET',\n requiresAuth: options.requiresAuth ?? true,\n handler: async (context: ContextWithMastra) => {\n context.header('Cache-Control', 'no-store');\n const input = traceIdSchema.safeParse(context.req.param('traceId'));\n if (!input.success) return context.json({ error: 'A valid trace ID is required.' }, 400);\n const traceId = input.data;\n try {\n if ((await options.authorize({ traceId, context })) !== true) {\n return context.json({ error: 'Access to this recording is denied.' }, 403);\n }\n const store = await context.get('mastra').getStorage()?.getStore('observability');\n if (!store) return context.json({ error: 'Trace storage is not configured.' }, 503);\n const trace = await store.getTrace({ traceId });\n const calls =\n trace?.spans.filter(\n span =>\n span.name === 'voice call' &&\n typeof span.metadata?.roomName === 'string' &&\n span.metadata.roomName.length > 0,\n ) ?? [];\n if (!calls.length) return context.json({ error: 'Voice call trace not found.' }, 404);\n if (calls.length !== 1) return context.json({ error: 'This trace contains multiple voice calls.' }, 409);\n const span = calls[0]!;\n const roomName = span.metadata?.roomName;\n if (typeof roomName !== 'string') return context.json({ error: 'Voice call trace not found.' }, 404);\n const recording = await options.resolveRecording({ traceId, roomName, span, context });\n if (!recording) return context.json({ status: 'unavailable' } satisfies LiveKitRecordingResponse);\n // Validate and strip extra fields so storage credentials cannot accidentally become response fields.\n const result = recordingSchema.safeParse(recording);\n if (!result.success) return context.json({ error: 'Unable to load the call recording.' }, 502);\n return context.json({ status: 'ready', ...result.data } satisfies LiveKitRecordingResponse);\n } catch {\n // Storage/provider errors can contain credentials or signed request details.\n return context.json({ error: 'Unable to load the call recording.' }, 502);\n }\n },\n };\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** A consent decision captured from the caller during a call. */\nexport interface ConsentGrant {\n /**\n * The consent item, matching a key in the worker's `configuration.consentPolicy` (e.g.\n * `'summaryStorage'`).\n */\n item: string;\n /** `true` if the caller agreed, `false` if they declined. */\n granted: boolean;\n /** The caller the grant belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread the grant was captured on, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface ConsentToolOptions {\n /**\n * Persist the caller's decision to YOUR system of record (database, CRM, consent ledger). Called\n * each time the caller answers a consent question — keep it quick, it runs inside the turn. This\n * tool owns extracting the caller's identity from the tool execution context; you own storage, so\n * consent lives in a durable, compliant place rather than an opaque plugin store. Read those\n * grants back at `onCallEnd` (or before any consent-gated step) to enforce the requirement.\n */\n onGrant: (grant: ConsentGrant) => void | Promise<void>;\n /** Tool id the agent calls. Defaults to `'recordConsent'`. */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /** Restrict the accepted consent items to this set (becomes an enum). Defaults to any string. */\n items?: readonly string[];\n}\n\nconst DEFAULT_DESCRIPTION =\n \"Record the caller's consent decision for a data-processing item (for example, storing a summary \" +\n 'of the call). Call this as soon as the caller answers a consent question: granted = true if they ' +\n 'agree, false if they decline. Do not proceed with an activity that requires consent until you ' +\n 'have recorded the caller granting it.';\n\n/**\n * Builds a Mastra tool that captures the caller's consent decisions at runtime — the companion to\n * the worker's `configuration.consentPolicy`, which only DECLARES which consents a call needs. Add\n * it to the agent that answers the call; on each call the tool reads the caller's `resourceId` /\n * `threadId` from its execution context and hands the decision to your `onGrant` store. The runtime\n * grants live only where `onGrant` put them: enforce consent by reading them back from your own\n * store — at `onCallEnd` or before any consent-gated action — cross-checking against the declared\n * requirements in `VoiceCallEndArgs.configuration.consentPolicy` (the configuration carries the\n * policy, never the grants).\n *\n * ```ts\n * // agent tools\n * recordConsent: createConsentTool({\n * items: ['summaryStorage'],\n * onGrant: async ({ item, granted, resourceId }) => {\n * if (resourceId) await db.saveConsent(resourceId, item, granted);\n * },\n * }),\n * ```\n */\nexport function createConsentTool(options: ConsentToolOptions) {\n const items = options.items;\n const itemSchema = items && items.length > 0 ? z.enum([...items] as [string, ...string[]]) : z.string();\n return createTool({\n id: options.id ?? 'recordConsent',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n item: itemSchema.describe(\n \"The consent item the caller is answering for (matches the deployment's required consents).\",\n ),\n granted: z.boolean().describe('True if the caller agreed, false if they declined.'),\n }),\n execute: async ({ item, granted }, { agent }) => {\n await options.onGrant({ item, granted, resourceId: agent?.resourceId, threadId: agent?.threadId });\n return { recorded: true as const, item, granted };\n },\n });\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** Details of the agent's decision to end the call, handed to {@link EndCallToolOptions.onEndCall}. */\nexport interface EndCallRequest {\n /** Short reason the agent gave for ending the call, if any (e.g. \"caller said goodbye\"). */\n reason?: string;\n /** The caller the call belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface EndCallToolOptions {\n /**\n * Tool id the agent calls to end the call. Must match the worker's\n * `configuration.endCall.tool` (both default to `'endCall'`), since that's the name the worker\n * watches for to hang up.\n */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /**\n * Optional bookkeeping hook, called when the agent invokes the tool (records the reason, marks\n * the call resolved in your CRM, …). Runs inside the turn — keep it quick. It does NOT hang up\n * the call; the worker does that once the agent's final words finish playing. The tool reads the\n * caller's identity from its execution context and passes it here.\n */\n onEndCall?: (request: EndCallRequest) => void | Promise<void>;\n}\n\nconst DEFAULT_DESCRIPTION =\n 'End the phone call. Call this only after you have said goodbye and there is nothing left to do — ' +\n 'for example the caller says goodbye, or the task is complete and you have wrapped up. Say your ' +\n 'closing line first, then call this as your final action; the call hangs up once your words finish ' +\n 'playing. Your closing line must be a final statement, never a question — if you are asking the ' +\n 'caller anything (for example whether they need anything else), do NOT call this in that reply; ' +\n 'wait for their answer. Do not call it if the caller still needs something.';\n\n/**\n * Builds a Mastra tool the agent calls to end the call itself (say goodbye → hang up). It is the\n * agent-visible half of agent-initiated hang-up: the tool only SIGNALS intent (and runs optional\n * bookkeeping) — it can't reach the LiveKit room from inside `agent.stream()`. The worker owns the\n * hang-up: with `configuration.endCall` set, it watches each turn for this tool, waits for the\n * agent's closing words to finish playing, then disconnects (running `onCallEnd` on the way out).\n * Keeping the tool inside the single agent means it stays visible/editable in Studio and works on\n * the agent and workflow reply paths alike.\n *\n * ```ts\n * // agent tools\n * endCall: createEndCallTool({\n * onEndCall: ({ reason, resourceId }) => log.info('agent ended call', { reason, resourceId }),\n * }),\n * // worker\n * createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } });\n * ```\n */\nexport function createEndCallTool(options: EndCallToolOptions = {}) {\n return createTool({\n id: options.id ?? 'endCall',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n reason: z\n .string()\n .nullish()\n .describe('Short reason the call is ending, e.g. \"caller said goodbye\" or \"task complete\".'),\n }),\n outputSchema: z.object({ ended: z.boolean() }),\n execute: async ({ reason }, { agent }) => {\n try {\n await options.onEndCall?.({\n reason: reason ?? undefined,\n resourceId: agent?.resourceId,\n threadId: agent?.threadId,\n });\n } catch (error) {\n // Bookkeeping is best-effort — the tool must still signal `{ ended: true }` so the worker's\n // end-call detector runs and the call actually hangs up.\n console.warn('@mastra/livekit: onEndCall hook threw', error);\n }\n return { ended: true as const };\n },\n });\n}\n"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { a as DEFAULT_LIVEKIT_AGENT_NAME, i as serializeSessionMetadata, n as pipeAgentReplyToWriter } from "./workflow-generator-j3_TfX1B.js";
|
|
2
|
-
import { RoomAgentDispatch, RoomConfiguration } from "
|
|
3
|
-
import { AccessToken, AgentDispatchClient, RoomEgress, RoomServiceClient } from "livekit-server-sdk";
|
|
2
|
+
import { AccessToken, AgentDispatchClient, RoomAgentDispatch, RoomConfiguration, RoomEgress, RoomServiceClient } from "livekit-server-sdk";
|
|
4
3
|
import { z } from "zod/v4";
|
|
5
4
|
import { createTool } from "@mastra/core/tools";
|
|
6
5
|
import { z as z$1 } from "zod";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["DEFAULT_DESCRIPTION","z","z"],"sources":["../src/recording.ts","../src/dispatch.ts","../src/routes.ts","../src/recording-route.ts","../src/consent.ts","../src/end-call.ts"],"sourcesContent":["import { RoomEgress, RoomServiceClient } from 'livekit-server-sdk';\n\n/**\n * LiveKit auto-egress settings, accepted as a plain object or a RoomEgress instance.\n * Use `room.audioOnly` and `room.fileOutputs` for a single mixed audio recording.\n * Storage credentials belong here on the server, never in session metadata or tokens.\n */\nexport type LiveKitRecordingOptions = NonNullable<ConstructorParameters<typeof RoomEgress>[0]>;\n\n/** A recording-enabled session cannot reuse an existing LiveKit room. */\nexport class LiveKitRecordingRoomConflictError extends Error {\n readonly roomName: string;\n\n constructor(roomName: string) {\n super(\n `@mastra/livekit: recording requires a new room, but room \"${roomName}\" already exists. Use a fresh room name.`,\n );\n this.name = 'LiveKitRecordingRoomConflictError';\n this.roomName = roomName;\n }\n}\n\n/** Creates a fresh room with recording configured before any agent or caller joins. */\nexport async function createRecordingRoom(options: {\n roomName: string;\n serverUrl: string;\n apiKey: string;\n apiSecret: string;\n recording: LiveKitRecordingOptions;\n}) {\n const { recording, roomName } = options;\n if (!recording.room && !recording.participant && !recording.tracks) {\n throw new Error('@mastra/livekit: recording must configure at least one of room, participant, or tracks.');\n }\n\n const client = new RoomServiceClient(options.serverUrl.replace(/^ws/, 'http'), options.apiKey, options.apiSecret);\n // CreateRoom reuses existing rooms without applying room-composite auto-egress.\n // This preflight is not atomic: callers must use a fresh name and serialize room creation.\n const rooms = await client.listRooms([roomName]);\n if (rooms.length > 0) {\n throw new LiveKitRecordingRoomConflictError(roomName);\n }\n\n return client.createRoom({ name: roomName, egress: new RoomEgress(recording) });\n}\n","import { AgentDispatchClient } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { createRecordingRoom } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\nexport interface DispatchVoiceSessionOptions {\n /**\n * Room to dispatch the agent into (created on demand). With recording enabled, use a unique\n * name for each call. An existing room throws LiveKitRecordingRoomConflictError.\n */\n roomName: string;\n /** Must match the worker's `agentName`. Defaults to `'mastra-voice'`. */\n agentName?: string;\n metadata?: LiveKitSessionMetadata;\n /** Create a fresh room with automatic recording before dispatch. Omit to keep dispatch-only behavior. */\n recording?: LiveKitRecordingOptions;\n /** Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n}\n\nfunction toHttpUrl(url: string): string {\n return url.replace(/^ws/, 'http');\n}\n\n/**\n * Programmatically dispatches a Mastra voice agent into a LiveKit room — for\n * server-initiated sessions such as outbound calls or joining an existing room.\n */\nexport async function dispatchVoiceSession(options: DispatchVoiceSessionOptions) {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl) {\n throw new Error('@mastra/livekit: set LIVEKIT_URL or pass serverUrl to dispatchVoiceSession.');\n }\n if (!apiKey || !apiSecret) {\n throw new Error(\n '@mastra/livekit: set LIVEKIT_API_KEY and LIVEKIT_API_SECRET or pass apiKey/apiSecret to dispatchVoiceSession.',\n );\n }\n const metadata = serializeSessionMetadata(options.metadata ?? {});\n if (options.recording !== undefined) {\n await createRecordingRoom({\n roomName: options.roomName,\n serverUrl,\n apiKey,\n apiSecret,\n recording: options.recording,\n });\n }\n const client = new AgentDispatchClient(toHttpUrl(serverUrl), apiKey, apiSecret);\n return client.createDispatch(options.roomName, options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME, {\n metadata,\n });\n}\n","import { RoomAgentDispatch, RoomConfiguration } from '@livekit/protocol';\nimport type { ContextWithMastra, ApiRoute } from '@mastra/core/server';\nimport { AccessToken } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { dispatchVoiceSession } from './dispatch';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { LiveKitRecordingRoomConflictError } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\n/** Response body of the connection-details route. Matches LiveKit's frontend starter contract. */\nexport interface LiveKitConnectionDetails {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n}\n\nexport interface ConnectionRequestArgs {\n body: Record<string, unknown>;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitConnectionRouteOptions {\n /**\n * Route path. Defaults to `/voice/livekit/connection-details`. Mastra reserves the\n * `/api` prefix for built-in routes, so custom paths must not start with `/api`.\n */\n path?: string;\n /** LiveKit server URL (`wss://...`). Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n /** LiveKit agent name for explicit dispatch. Must match the worker's `agentName`. */\n agentName?: string;\n /** Token time-to-live. Defaults to `'15m'`. */\n ttl?: string | number;\n /** Defaults to `true` (Mastra custom routes require auth unless opted out). */\n requiresAuth?: boolean;\n /**\n * Room name or a function that derives one from the request. Defaults to a generated name.\n * With recording enabled, use a unique name for each call. An existing room returns HTTP 409\n * before agent dispatch or token issuance.\n */\n roomName?: string | ((args: ConnectionRequestArgs) => string);\n participantIdentity?: string | ((args: ConnectionRequestArgs) => string);\n /**\n * Session metadata delivered to the worker via agent dispatch. Defaults to passing\n * through `agentId`, `threadId`, and `resourceId` from the request body.\n */\n metadata?: (args: ConnectionRequestArgs) => LiveKitSessionMetadata | Promise<LiveKitSessionMetadata>;\n /**\n * Create a fresh room with automatic recording and dispatch the agent before returning a token.\n * The server-side callback receives the resolved room name; return undefined to skip recording.\n * Recording settings and storage credentials are never included in the participant token.\n */\n recording?:\n | LiveKitRecordingOptions\n | ((\n args: ConnectionRequestArgs & { roomName: string },\n ) => LiveKitRecordingOptions | undefined | Promise<LiveKitRecordingOptions | undefined>);\n}\n\nfunction stringField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * An ApiRoute for `server.apiRoutes` that mints a LiveKit access token with the Mastra\n * voice agent dispatched into the room. Frontends call it to join a voice session:\n *\n * ```ts\n * export const mastra = new Mastra({\n * agents: { support },\n * server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })] },\n * });\n * ```\n */\nexport function liveKitConnectionRoute(options: LiveKitConnectionRouteOptions = {}): ApiRoute {\n const handler = async (c: ContextWithMastra) => {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl || !apiKey || !apiSecret) {\n return c.json(\n {\n error:\n 'LiveKit is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET ' +\n '(or pass serverUrl/apiKey/apiSecret to liveKitConnectionRoute).',\n },\n 500,\n );\n }\n\n const body: Record<string, unknown> = await c.req\n .json()\n .then((parsed: unknown) =>\n parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {},\n )\n .catch(() => ({}));\n const args: ConnectionRequestArgs = { body, context: c };\n\n const metadata: LiveKitSessionMetadata = options.metadata\n ? await options.metadata(args)\n : {\n agentId: stringField(body, 'agentId'),\n threadId: stringField(body, 'threadId'),\n resourceId: stringField(body, 'resourceId'),\n };\n\n const roomName =\n typeof options.roomName === 'function'\n ? options.roomName(args)\n : (options.roomName ?? `mastra-voice-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n const identity =\n typeof options.participantIdentity === 'function'\n ? options.participantIdentity(args)\n : (options.participantIdentity ?? metadata.resourceId ?? `user-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n // One memory thread per room unless the caller pins a thread explicitly.\n metadata.threadId ??= roomName;\n\n const recording =\n typeof options.recording === 'function' ? await options.recording({ ...args, roomName }) : options.recording;\n if (recording !== undefined) {\n try {\n await dispatchVoiceSession({\n roomName,\n agentName: options.agentName,\n metadata,\n serverUrl,\n apiKey,\n apiSecret,\n recording,\n });\n } catch (error) {\n if (error instanceof LiveKitRecordingRoomConflictError) {\n return c.json({ error: 'Recording requires a new room. Use a unique roomName for each call.' }, 409);\n }\n throw error;\n }\n }\n\n const token = new AccessToken(apiKey, apiSecret, { identity, ttl: options.ttl ?? '15m' });\n token.addGrant({\n room: roomName,\n roomJoin: true,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n canUpdateOwnMetadata: true,\n });\n // A pre-created room needs explicit dispatch; token roomConfig only applies at room creation.\n if (recording === undefined) {\n token.roomConfig = new RoomConfiguration({\n agents: [\n new RoomAgentDispatch({\n agentName: options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME,\n metadata: serializeSessionMetadata(metadata),\n }),\n ],\n });\n }\n\n const details: LiveKitConnectionDetails = {\n serverUrl,\n roomName,\n participantName: identity,\n participantToken: await token.toJwt(),\n };\n return c.json(details);\n };\n\n return {\n // Studio calls this default path and /api/system/packages reports it — change all three together.\n path: options.path ?? '/voice/livekit/connection-details',\n method: 'POST',\n requiresAuth: options.requiresAuth,\n handler,\n } as ApiRoute;\n}\n","import type { ApiRoute, ContextWithMastra } from '@mastra/core/server';\nimport type { SpanRecord } from '@mastra/core/storage';\nimport { z } from 'zod/v4';\nimport type { LiveKitRecording, LiveKitRecordingResponse } from './recording-types';\n\nconst traceIdSchema = z.string().min(1).max(256);\nexport const recordingSchema = z.object({\n url: z.url({ protocol: /^https?$/ }),\n expiresAt: z.string().datetime().optional(),\n}) satisfies z.ZodType<LiveKitRecording>;\n\nexport interface LiveKitRecordingResolverArgs {\n traceId: string;\n /** Taken from the stored voice call span, never from the browser request. */\n roomName: string;\n span: SpanRecord;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitRecordingRouteOptions {\n /** Studio discovers this route at its default path: /voice/livekit/recordings/:traceId. */\n path?: string;\n /** Defaults to true. Only disable for a local demo. */\n requiresAuth?: boolean;\n /** Required trace-specific access policy. Only true permits trace lookup and URL resolution. */\n authorize: (args: { traceId: string; context: ContextWithMastra }) => boolean | Promise<boolean>;\n /** Return a playable URL, or undefined if the recording is not available yet. */\n resolveRecording: (args: LiveKitRecordingResolverArgs) => Promise<LiveKitRecording | undefined>;\n}\n\n/** Resolves a voice call trace to a recording without persisting signed URLs in traces. */\nexport function liveKitRecordingRoute(options: LiveKitRecordingRouteOptions): ApiRoute {\n if (typeof options.authorize !== 'function') {\n throw new Error('liveKitRecordingRoute requires an authorize callback.');\n }\n return {\n path: options.path ?? '/voice/livekit/recordings/:traceId',\n method: 'GET',\n requiresAuth: options.requiresAuth ?? true,\n handler: async (context: ContextWithMastra) => {\n context.header('Cache-Control', 'no-store');\n const input = traceIdSchema.safeParse(context.req.param('traceId'));\n if (!input.success) return context.json({ error: 'A valid trace ID is required.' }, 400);\n const traceId = input.data;\n try {\n if ((await options.authorize({ traceId, context })) !== true) {\n return context.json({ error: 'Access to this recording is denied.' }, 403);\n }\n const store = await context.get('mastra').getStorage()?.getStore('observability');\n if (!store) return context.json({ error: 'Trace storage is not configured.' }, 503);\n const trace = await store.getTrace({ traceId });\n const calls =\n trace?.spans.filter(\n span =>\n span.name === 'voice call' &&\n typeof span.metadata?.roomName === 'string' &&\n span.metadata.roomName.length > 0,\n ) ?? [];\n if (!calls.length) return context.json({ error: 'Voice call trace not found.' }, 404);\n if (calls.length !== 1) return context.json({ error: 'This trace contains multiple voice calls.' }, 409);\n const span = calls[0]!;\n const roomName = span.metadata?.roomName;\n if (typeof roomName !== 'string') return context.json({ error: 'Voice call trace not found.' }, 404);\n const recording = await options.resolveRecording({ traceId, roomName, span, context });\n if (!recording) return context.json({ status: 'unavailable' } satisfies LiveKitRecordingResponse);\n // Validate and strip extra fields so storage credentials cannot accidentally become response fields.\n const result = recordingSchema.safeParse(recording);\n if (!result.success) return context.json({ error: 'Unable to load the call recording.' }, 502);\n return context.json({ status: 'ready', ...result.data } satisfies LiveKitRecordingResponse);\n } catch {\n // Storage/provider errors can contain credentials or signed request details.\n return context.json({ error: 'Unable to load the call recording.' }, 502);\n }\n },\n };\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** A consent decision captured from the caller during a call. */\nexport interface ConsentGrant {\n /**\n * The consent item, matching a key in the worker's `configuration.consentPolicy` (e.g.\n * `'summaryStorage'`).\n */\n item: string;\n /** `true` if the caller agreed, `false` if they declined. */\n granted: boolean;\n /** The caller the grant belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread the grant was captured on, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface ConsentToolOptions {\n /**\n * Persist the caller's decision to YOUR system of record (database, CRM, consent ledger). Called\n * each time the caller answers a consent question — keep it quick, it runs inside the turn. This\n * tool owns extracting the caller's identity from the tool execution context; you own storage, so\n * consent lives in a durable, compliant place rather than an opaque plugin store. Read those\n * grants back at `onCallEnd` (or before any consent-gated step) to enforce the requirement.\n */\n onGrant: (grant: ConsentGrant) => void | Promise<void>;\n /** Tool id the agent calls. Defaults to `'recordConsent'`. */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /** Restrict the accepted consent items to this set (becomes an enum). Defaults to any string. */\n items?: readonly string[];\n}\n\nconst DEFAULT_DESCRIPTION =\n \"Record the caller's consent decision for a data-processing item (for example, storing a summary \" +\n 'of the call). Call this as soon as the caller answers a consent question: granted = true if they ' +\n 'agree, false if they decline. Do not proceed with an activity that requires consent until you ' +\n 'have recorded the caller granting it.';\n\n/**\n * Builds a Mastra tool that captures the caller's consent decisions at runtime — the companion to\n * the worker's `configuration.consentPolicy`, which only DECLARES which consents a call needs. Add\n * it to the agent that answers the call; on each call the tool reads the caller's `resourceId` /\n * `threadId` from its execution context and hands the decision to your `onGrant` store. The runtime\n * grants live only where `onGrant` put them: enforce consent by reading them back from your own\n * store — at `onCallEnd` or before any consent-gated action — cross-checking against the declared\n * requirements in `VoiceCallEndArgs.configuration.consentPolicy` (the configuration carries the\n * policy, never the grants).\n *\n * ```ts\n * // agent tools\n * recordConsent: createConsentTool({\n * items: ['summaryStorage'],\n * onGrant: async ({ item, granted, resourceId }) => {\n * if (resourceId) await db.saveConsent(resourceId, item, granted);\n * },\n * }),\n * ```\n */\nexport function createConsentTool(options: ConsentToolOptions) {\n const items = options.items;\n const itemSchema = items && items.length > 0 ? z.enum([...items] as [string, ...string[]]) : z.string();\n return createTool({\n id: options.id ?? 'recordConsent',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n item: itemSchema.describe(\n \"The consent item the caller is answering for (matches the deployment's required consents).\",\n ),\n granted: z.boolean().describe('True if the caller agreed, false if they declined.'),\n }),\n execute: async ({ item, granted }, { agent }) => {\n await options.onGrant({ item, granted, resourceId: agent?.resourceId, threadId: agent?.threadId });\n return { recorded: true as const, item, granted };\n },\n });\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** Details of the agent's decision to end the call, handed to {@link EndCallToolOptions.onEndCall}. */\nexport interface EndCallRequest {\n /** Short reason the agent gave for ending the call, if any (e.g. \"caller said goodbye\"). */\n reason?: string;\n /** The caller the call belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface EndCallToolOptions {\n /**\n * Tool id the agent calls to end the call. Must match the worker's\n * `configuration.endCall.tool` (both default to `'endCall'`), since that's the name the worker\n * watches for to hang up.\n */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /**\n * Optional bookkeeping hook, called when the agent invokes the tool (records the reason, marks\n * the call resolved in your CRM, …). Runs inside the turn — keep it quick. It does NOT hang up\n * the call; the worker does that once the agent's final words finish playing. The tool reads the\n * caller's identity from its execution context and passes it here.\n */\n onEndCall?: (request: EndCallRequest) => void | Promise<void>;\n}\n\nconst DEFAULT_DESCRIPTION =\n 'End the phone call. Call this only after you have said goodbye and there is nothing left to do — ' +\n 'for example the caller says goodbye, or the task is complete and you have wrapped up. Say your ' +\n 'closing line first, then call this as your final action; the call hangs up once your words finish ' +\n 'playing. Your closing line must be a final statement, never a question — if you are asking the ' +\n 'caller anything (for example whether they need anything else), do NOT call this in that reply; ' +\n 'wait for their answer. Do not call it if the caller still needs something.';\n\n/**\n * Builds a Mastra tool the agent calls to end the call itself (say goodbye → hang up). It is the\n * agent-visible half of agent-initiated hang-up: the tool only SIGNALS intent (and runs optional\n * bookkeeping) — it can't reach the LiveKit room from inside `agent.stream()`. The worker owns the\n * hang-up: with `configuration.endCall` set, it watches each turn for this tool, waits for the\n * agent's closing words to finish playing, then disconnects (running `onCallEnd` on the way out).\n * Keeping the tool inside the single agent means it stays visible/editable in Studio and works on\n * the agent and workflow reply paths alike.\n *\n * ```ts\n * // agent tools\n * endCall: createEndCallTool({\n * onEndCall: ({ reason, resourceId }) => log.info('agent ended call', { reason, resourceId }),\n * }),\n * // worker\n * createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } });\n * ```\n */\nexport function createEndCallTool(options: EndCallToolOptions = {}) {\n return createTool({\n id: options.id ?? 'endCall',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n reason: z\n .string()\n .nullish()\n .describe('Short reason the call is ending, e.g. \"caller said goodbye\" or \"task complete\".'),\n }),\n outputSchema: z.object({ ended: z.boolean() }),\n execute: async ({ reason }, { agent }) => {\n try {\n await options.onEndCall?.({\n reason: reason ?? undefined,\n resourceId: agent?.resourceId,\n threadId: agent?.threadId,\n });\n } catch (error) {\n // Bookkeeping is best-effort — the tool must still signal `{ ended: true }` so the worker's\n // end-call detector runs and the call actually hangs up.\n console.warn('@mastra/livekit: onEndCall hook threw', error);\n }\n return { ended: true as const };\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["DEFAULT_DESCRIPTION","z","z"],"sources":["../src/recording.ts","../src/dispatch.ts","../src/routes.ts","../src/recording-route.ts","../src/consent.ts","../src/end-call.ts"],"sourcesContent":["import { RoomEgress, RoomServiceClient } from 'livekit-server-sdk';\n\n/**\n * LiveKit auto-egress settings, accepted as a plain object or a RoomEgress instance.\n * Use `room.audioOnly` and `room.fileOutputs` for a single mixed audio recording.\n * Storage credentials belong here on the server, never in session metadata or tokens.\n */\nexport type LiveKitRecordingOptions = NonNullable<ConstructorParameters<typeof RoomEgress>[0]>;\n\n/** A recording-enabled session cannot reuse an existing LiveKit room. */\nexport class LiveKitRecordingRoomConflictError extends Error {\n readonly roomName: string;\n\n constructor(roomName: string) {\n super(\n `@mastra/livekit: recording requires a new room, but room \"${roomName}\" already exists. Use a fresh room name.`,\n );\n this.name = 'LiveKitRecordingRoomConflictError';\n this.roomName = roomName;\n }\n}\n\n/** Creates a fresh room with recording configured before any agent or caller joins. */\nexport async function createRecordingRoom(options: {\n roomName: string;\n serverUrl: string;\n apiKey: string;\n apiSecret: string;\n recording: LiveKitRecordingOptions;\n}) {\n const { recording, roomName } = options;\n if (!recording.room && !recording.participant && !recording.tracks) {\n throw new Error('@mastra/livekit: recording must configure at least one of room, participant, or tracks.');\n }\n\n const client = new RoomServiceClient(options.serverUrl.replace(/^ws/, 'http'), options.apiKey, options.apiSecret);\n // CreateRoom reuses existing rooms without applying room-composite auto-egress.\n // This preflight is not atomic: callers must use a fresh name and serialize room creation.\n const rooms = await client.listRooms([roomName]);\n if (rooms.length > 0) {\n throw new LiveKitRecordingRoomConflictError(roomName);\n }\n\n return client.createRoom({ name: roomName, egress: new RoomEgress(recording) });\n}\n","import { AgentDispatchClient } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { createRecordingRoom } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\nexport interface DispatchVoiceSessionOptions {\n /**\n * Room to dispatch the agent into (created on demand). With recording enabled, use a unique\n * name for each call. An existing room throws LiveKitRecordingRoomConflictError.\n */\n roomName: string;\n /** Must match the worker's `agentName`. Defaults to `'mastra-voice'`. */\n agentName?: string;\n metadata?: LiveKitSessionMetadata;\n /** Create a fresh room with automatic recording before dispatch. Omit to keep dispatch-only behavior. */\n recording?: LiveKitRecordingOptions;\n /** Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n}\n\nfunction toHttpUrl(url: string): string {\n return url.replace(/^ws/, 'http');\n}\n\n/**\n * Programmatically dispatches a Mastra voice agent into a LiveKit room — for\n * server-initiated sessions such as outbound calls or joining an existing room.\n */\nexport async function dispatchVoiceSession(options: DispatchVoiceSessionOptions) {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl) {\n throw new Error('@mastra/livekit: set LIVEKIT_URL or pass serverUrl to dispatchVoiceSession.');\n }\n if (!apiKey || !apiSecret) {\n throw new Error(\n '@mastra/livekit: set LIVEKIT_API_KEY and LIVEKIT_API_SECRET or pass apiKey/apiSecret to dispatchVoiceSession.',\n );\n }\n const metadata = serializeSessionMetadata(options.metadata ?? {});\n if (options.recording !== undefined) {\n await createRecordingRoom({\n roomName: options.roomName,\n serverUrl,\n apiKey,\n apiSecret,\n recording: options.recording,\n });\n }\n const client = new AgentDispatchClient(toHttpUrl(serverUrl), apiKey, apiSecret);\n return client.createDispatch(options.roomName, options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME, {\n metadata,\n });\n}\n","import type { ContextWithMastra, ApiRoute } from '@mastra/core/server';\nimport { AccessToken, RoomAgentDispatch, RoomConfiguration } from 'livekit-server-sdk';\nimport { DEFAULT_LIVEKIT_AGENT_NAME } from './constants';\nimport { dispatchVoiceSession } from './dispatch';\nimport { serializeSessionMetadata } from './metadata';\nimport type { LiveKitSessionMetadata } from './metadata';\nimport { LiveKitRecordingRoomConflictError } from './recording';\nimport type { LiveKitRecordingOptions } from './recording';\n\n/** Response body of the connection-details route. Matches LiveKit's frontend starter contract. */\nexport interface LiveKitConnectionDetails {\n serverUrl: string;\n roomName: string;\n participantName: string;\n participantToken: string;\n}\n\nexport interface ConnectionRequestArgs {\n body: Record<string, unknown>;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitConnectionRouteOptions {\n /**\n * Route path. Defaults to `/voice/livekit/connection-details`. Mastra reserves the\n * `/api` prefix for built-in routes, so custom paths must not start with `/api`.\n */\n path?: string;\n /** LiveKit server URL (`wss://...`). Defaults to `LIVEKIT_URL`. */\n serverUrl?: string;\n /** Defaults to `LIVEKIT_API_KEY`. */\n apiKey?: string;\n /** Defaults to `LIVEKIT_API_SECRET`. */\n apiSecret?: string;\n /** LiveKit agent name for explicit dispatch. Must match the worker's `agentName`. */\n agentName?: string;\n /** Token time-to-live. Defaults to `'15m'`. */\n ttl?: string | number;\n /** Defaults to `true` (Mastra custom routes require auth unless opted out). */\n requiresAuth?: boolean;\n /**\n * Room name or a function that derives one from the request. Defaults to a generated name.\n * With recording enabled, use a unique name for each call. An existing room returns HTTP 409\n * before agent dispatch or token issuance.\n */\n roomName?: string | ((args: ConnectionRequestArgs) => string);\n participantIdentity?: string | ((args: ConnectionRequestArgs) => string);\n /**\n * Session metadata delivered to the worker via agent dispatch. Defaults to passing\n * through `agentId`, `threadId`, and `resourceId` from the request body.\n */\n metadata?: (args: ConnectionRequestArgs) => LiveKitSessionMetadata | Promise<LiveKitSessionMetadata>;\n /**\n * Create a fresh room with automatic recording and dispatch the agent before returning a token.\n * The server-side callback receives the resolved room name; return undefined to skip recording.\n * Recording settings and storage credentials are never included in the participant token.\n */\n recording?:\n | LiveKitRecordingOptions\n | ((\n args: ConnectionRequestArgs & { roomName: string },\n ) => LiveKitRecordingOptions | undefined | Promise<LiveKitRecordingOptions | undefined>);\n}\n\nfunction stringField(body: Record<string, unknown>, key: string): string | undefined {\n const value = body[key];\n return typeof value === 'string' && value.length > 0 ? value : undefined;\n}\n\n/**\n * An ApiRoute for `server.apiRoutes` that mints a LiveKit access token with the Mastra\n * voice agent dispatched into the room. Frontends call it to join a voice session:\n *\n * ```ts\n * export const mastra = new Mastra({\n * agents: { support },\n * server: { apiRoutes: [liveKitConnectionRoute({ agentName: 'mastra-voice' })] },\n * });\n * ```\n */\nexport function liveKitConnectionRoute(options: LiveKitConnectionRouteOptions = {}): ApiRoute {\n const handler = async (c: ContextWithMastra) => {\n const serverUrl = options.serverUrl ?? process.env.LIVEKIT_URL;\n const apiKey = options.apiKey ?? process.env.LIVEKIT_API_KEY;\n const apiSecret = options.apiSecret ?? process.env.LIVEKIT_API_SECRET;\n if (!serverUrl || !apiKey || !apiSecret) {\n return c.json(\n {\n error:\n 'LiveKit is not configured. Set LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET ' +\n '(or pass serverUrl/apiKey/apiSecret to liveKitConnectionRoute).',\n },\n 500,\n );\n }\n\n const body: Record<string, unknown> = await c.req\n .json()\n .then((parsed: unknown) =>\n parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {},\n )\n .catch(() => ({}));\n const args: ConnectionRequestArgs = { body, context: c };\n\n const metadata: LiveKitSessionMetadata = options.metadata\n ? await options.metadata(args)\n : {\n agentId: stringField(body, 'agentId'),\n threadId: stringField(body, 'threadId'),\n resourceId: stringField(body, 'resourceId'),\n };\n\n const roomName =\n typeof options.roomName === 'function'\n ? options.roomName(args)\n : (options.roomName ?? `mastra-voice-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n const identity =\n typeof options.participantIdentity === 'function'\n ? options.participantIdentity(args)\n : (options.participantIdentity ?? metadata.resourceId ?? `user-${globalThis.crypto.randomUUID().slice(0, 8)}`);\n // One memory thread per room unless the caller pins a thread explicitly.\n metadata.threadId ??= roomName;\n\n const recording =\n typeof options.recording === 'function' ? await options.recording({ ...args, roomName }) : options.recording;\n if (recording !== undefined) {\n try {\n await dispatchVoiceSession({\n roomName,\n agentName: options.agentName,\n metadata,\n serverUrl,\n apiKey,\n apiSecret,\n recording,\n });\n } catch (error) {\n if (error instanceof LiveKitRecordingRoomConflictError) {\n return c.json({ error: 'Recording requires a new room. Use a unique roomName for each call.' }, 409);\n }\n throw error;\n }\n }\n\n const token = new AccessToken(apiKey, apiSecret, { identity, ttl: options.ttl ?? '15m' });\n token.addGrant({\n room: roomName,\n roomJoin: true,\n canPublish: true,\n canSubscribe: true,\n canPublishData: true,\n canUpdateOwnMetadata: true,\n });\n // A pre-created room needs explicit dispatch; token roomConfig only applies at room creation.\n if (recording === undefined) {\n token.roomConfig = new RoomConfiguration({\n agents: [\n new RoomAgentDispatch({\n agentName: options.agentName ?? DEFAULT_LIVEKIT_AGENT_NAME,\n metadata: serializeSessionMetadata(metadata),\n }),\n ],\n });\n }\n\n const details: LiveKitConnectionDetails = {\n serverUrl,\n roomName,\n participantName: identity,\n participantToken: await token.toJwt(),\n };\n return c.json(details);\n };\n\n return {\n // Studio calls this default path and /api/system/packages reports it — change all three together.\n path: options.path ?? '/voice/livekit/connection-details',\n method: 'POST',\n requiresAuth: options.requiresAuth,\n handler,\n } as ApiRoute;\n}\n","import type { ApiRoute, ContextWithMastra } from '@mastra/core/server';\nimport type { SpanRecord } from '@mastra/core/storage';\nimport { z } from 'zod/v4';\nimport type { LiveKitRecording, LiveKitRecordingResponse } from './recording-types';\n\nconst traceIdSchema = z.string().min(1).max(256);\nexport const recordingSchema = z.object({\n url: z.url({ protocol: /^https?$/ }),\n expiresAt: z.string().datetime().optional(),\n}) satisfies z.ZodType<LiveKitRecording>;\n\nexport interface LiveKitRecordingResolverArgs {\n traceId: string;\n /** Taken from the stored voice call span, never from the browser request. */\n roomName: string;\n span: SpanRecord;\n context: ContextWithMastra;\n}\n\nexport interface LiveKitRecordingRouteOptions {\n /** Studio discovers this route at its default path: /voice/livekit/recordings/:traceId. */\n path?: string;\n /** Defaults to true. Only disable for a local demo. */\n requiresAuth?: boolean;\n /** Required trace-specific access policy. Only true permits trace lookup and URL resolution. */\n authorize: (args: { traceId: string; context: ContextWithMastra }) => boolean | Promise<boolean>;\n /** Return a playable URL, or undefined if the recording is not available yet. */\n resolveRecording: (args: LiveKitRecordingResolverArgs) => Promise<LiveKitRecording | undefined>;\n}\n\n/** Resolves a voice call trace to a recording without persisting signed URLs in traces. */\nexport function liveKitRecordingRoute(options: LiveKitRecordingRouteOptions): ApiRoute {\n if (typeof options.authorize !== 'function') {\n throw new Error('liveKitRecordingRoute requires an authorize callback.');\n }\n return {\n path: options.path ?? '/voice/livekit/recordings/:traceId',\n method: 'GET',\n requiresAuth: options.requiresAuth ?? true,\n handler: async (context: ContextWithMastra) => {\n context.header('Cache-Control', 'no-store');\n const input = traceIdSchema.safeParse(context.req.param('traceId'));\n if (!input.success) return context.json({ error: 'A valid trace ID is required.' }, 400);\n const traceId = input.data;\n try {\n if ((await options.authorize({ traceId, context })) !== true) {\n return context.json({ error: 'Access to this recording is denied.' }, 403);\n }\n const store = await context.get('mastra').getStorage()?.getStore('observability');\n if (!store) return context.json({ error: 'Trace storage is not configured.' }, 503);\n const trace = await store.getTrace({ traceId });\n const calls =\n trace?.spans.filter(\n span =>\n span.name === 'voice call' &&\n typeof span.metadata?.roomName === 'string' &&\n span.metadata.roomName.length > 0,\n ) ?? [];\n if (!calls.length) return context.json({ error: 'Voice call trace not found.' }, 404);\n if (calls.length !== 1) return context.json({ error: 'This trace contains multiple voice calls.' }, 409);\n const span = calls[0]!;\n const roomName = span.metadata?.roomName;\n if (typeof roomName !== 'string') return context.json({ error: 'Voice call trace not found.' }, 404);\n const recording = await options.resolveRecording({ traceId, roomName, span, context });\n if (!recording) return context.json({ status: 'unavailable' } satisfies LiveKitRecordingResponse);\n // Validate and strip extra fields so storage credentials cannot accidentally become response fields.\n const result = recordingSchema.safeParse(recording);\n if (!result.success) return context.json({ error: 'Unable to load the call recording.' }, 502);\n return context.json({ status: 'ready', ...result.data } satisfies LiveKitRecordingResponse);\n } catch {\n // Storage/provider errors can contain credentials or signed request details.\n return context.json({ error: 'Unable to load the call recording.' }, 502);\n }\n },\n };\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** A consent decision captured from the caller during a call. */\nexport interface ConsentGrant {\n /**\n * The consent item, matching a key in the worker's `configuration.consentPolicy` (e.g.\n * `'summaryStorage'`).\n */\n item: string;\n /** `true` if the caller agreed, `false` if they declined. */\n granted: boolean;\n /** The caller the grant belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread the grant was captured on, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface ConsentToolOptions {\n /**\n * Persist the caller's decision to YOUR system of record (database, CRM, consent ledger). Called\n * each time the caller answers a consent question — keep it quick, it runs inside the turn. This\n * tool owns extracting the caller's identity from the tool execution context; you own storage, so\n * consent lives in a durable, compliant place rather than an opaque plugin store. Read those\n * grants back at `onCallEnd` (or before any consent-gated step) to enforce the requirement.\n */\n onGrant: (grant: ConsentGrant) => void | Promise<void>;\n /** Tool id the agent calls. Defaults to `'recordConsent'`. */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /** Restrict the accepted consent items to this set (becomes an enum). Defaults to any string. */\n items?: readonly string[];\n}\n\nconst DEFAULT_DESCRIPTION =\n \"Record the caller's consent decision for a data-processing item (for example, storing a summary \" +\n 'of the call). Call this as soon as the caller answers a consent question: granted = true if they ' +\n 'agree, false if they decline. Do not proceed with an activity that requires consent until you ' +\n 'have recorded the caller granting it.';\n\n/**\n * Builds a Mastra tool that captures the caller's consent decisions at runtime — the companion to\n * the worker's `configuration.consentPolicy`, which only DECLARES which consents a call needs. Add\n * it to the agent that answers the call; on each call the tool reads the caller's `resourceId` /\n * `threadId` from its execution context and hands the decision to your `onGrant` store. The runtime\n * grants live only where `onGrant` put them: enforce consent by reading them back from your own\n * store — at `onCallEnd` or before any consent-gated action — cross-checking against the declared\n * requirements in `VoiceCallEndArgs.configuration.consentPolicy` (the configuration carries the\n * policy, never the grants).\n *\n * ```ts\n * // agent tools\n * recordConsent: createConsentTool({\n * items: ['summaryStorage'],\n * onGrant: async ({ item, granted, resourceId }) => {\n * if (resourceId) await db.saveConsent(resourceId, item, granted);\n * },\n * }),\n * ```\n */\nexport function createConsentTool(options: ConsentToolOptions) {\n const items = options.items;\n const itemSchema = items && items.length > 0 ? z.enum([...items] as [string, ...string[]]) : z.string();\n return createTool({\n id: options.id ?? 'recordConsent',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n item: itemSchema.describe(\n \"The consent item the caller is answering for (matches the deployment's required consents).\",\n ),\n granted: z.boolean().describe('True if the caller agreed, false if they declined.'),\n }),\n execute: async ({ item, granted }, { agent }) => {\n await options.onGrant({ item, granted, resourceId: agent?.resourceId, threadId: agent?.threadId });\n return { recorded: true as const, item, granted };\n },\n });\n}\n","// Server-safe: only depends on `@mastra/core` + zod, never the `@livekit/agents` runtime — so it\n// can be added to agents defined in server/shared code and re-exported from the root entry\n// (`index.ts`). `index.test.ts` enforces that boundary.\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\n\n/** Details of the agent's decision to end the call, handed to {@link EndCallToolOptions.onEndCall}. */\nexport interface EndCallRequest {\n /** Short reason the agent gave for ending the call, if any (e.g. \"caller said goodbye\"). */\n reason?: string;\n /** The caller the call belongs to (the memory `resource`), when the call is memory-scoped. */\n resourceId?: string;\n /** The call thread, when the call is memory-scoped. */\n threadId?: string;\n}\n\nexport interface EndCallToolOptions {\n /**\n * Tool id the agent calls to end the call. Must match the worker's\n * `configuration.endCall.tool` (both default to `'endCall'`), since that's the name the worker\n * watches for to hang up.\n */\n id?: string;\n /** Override the description the model sees when deciding to call the tool. */\n description?: string;\n /**\n * Optional bookkeeping hook, called when the agent invokes the tool (records the reason, marks\n * the call resolved in your CRM, …). Runs inside the turn — keep it quick. It does NOT hang up\n * the call; the worker does that once the agent's final words finish playing. The tool reads the\n * caller's identity from its execution context and passes it here.\n */\n onEndCall?: (request: EndCallRequest) => void | Promise<void>;\n}\n\nconst DEFAULT_DESCRIPTION =\n 'End the phone call. Call this only after you have said goodbye and there is nothing left to do — ' +\n 'for example the caller says goodbye, or the task is complete and you have wrapped up. Say your ' +\n 'closing line first, then call this as your final action; the call hangs up once your words finish ' +\n 'playing. Your closing line must be a final statement, never a question — if you are asking the ' +\n 'caller anything (for example whether they need anything else), do NOT call this in that reply; ' +\n 'wait for their answer. Do not call it if the caller still needs something.';\n\n/**\n * Builds a Mastra tool the agent calls to end the call itself (say goodbye → hang up). It is the\n * agent-visible half of agent-initiated hang-up: the tool only SIGNALS intent (and runs optional\n * bookkeeping) — it can't reach the LiveKit room from inside `agent.stream()`. The worker owns the\n * hang-up: with `configuration.endCall` set, it watches each turn for this tool, waits for the\n * agent's closing words to finish playing, then disconnects (running `onCallEnd` on the way out).\n * Keeping the tool inside the single agent means it stays visible/editable in Studio and works on\n * the agent and workflow reply paths alike.\n *\n * ```ts\n * // agent tools\n * endCall: createEndCallTool({\n * onEndCall: ({ reason, resourceId }) => log.info('agent ended call', { reason, resourceId }),\n * }),\n * // worker\n * createLiveKitWorker({ mastra, agent: 'support', configuration: { endCall: {} } });\n * ```\n */\nexport function createEndCallTool(options: EndCallToolOptions = {}) {\n return createTool({\n id: options.id ?? 'endCall',\n description: options.description ?? DEFAULT_DESCRIPTION,\n inputSchema: z.object({\n reason: z\n .string()\n .nullish()\n .describe('Short reason the call is ending, e.g. \"caller said goodbye\" or \"task complete\".'),\n }),\n outputSchema: z.object({ ended: z.boolean() }),\n execute: async ({ reason }, { agent }) => {\n try {\n await options.onEndCall?.({\n reason: reason ?? undefined,\n resourceId: agent?.resourceId,\n threadId: agent?.threadId,\n });\n } catch (error) {\n // Bookkeeping is best-effort — the tool must still signal `{ ended: true }` so the worker's\n // end-call detector runs and the call actually hangs up.\n console.warn('@mastra/livekit: onEndCall hook threw', error);\n }\n return { ended: true as const };\n },\n });\n}\n"],"mappings":"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"}
|
package/dist/plugin-entry.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_turn_metrics = require("./turn-metrics-Dd8pYEbh.cjs");
|
|
3
|
-
const require_remote = require("./remote-
|
|
3
|
+
const require_remote = require("./remote-DO33RaMO.cjs");
|
|
4
4
|
let _livekit_agents = require("@livekit/agents");
|
|
5
5
|
let _mastra_core_request_context = require("@mastra/core/request-context");
|
|
6
6
|
// src/llm-plugin.ts
|
package/dist/plugin-entry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as VOICE_TEXT_FLUSH, r as VOICE_TURN_METADATA, t as VOICE_FLUSH_METADATA } from "./turn-metrics-K8_ttSW7.js";
|
|
2
|
-
import { a as createAgentReplyGenerator, c as chatContextToMessages, l as extractNewTurnMessages, n as mastraLLMNode, r as observeVoiceSession, s as instrumentVoiceReply, t as createRemoteAgentReplyGenerator } from "./remote-
|
|
2
|
+
import { a as createAgentReplyGenerator, c as chatContextToMessages, l as extractNewTurnMessages, n as mastraLLMNode, r as observeVoiceSession, s as instrumentVoiceReply, t as createRemoteAgentReplyGenerator } from "./remote-DNmutTEU.js";
|
|
3
3
|
import { DEFAULT_API_CONNECT_OPTIONS, llm } from "@livekit/agents";
|
|
4
4
|
import { RequestContext } from "@mastra/core/request-context";
|
|
5
5
|
// src/llm-plugin.ts
|
|
@@ -387,7 +387,7 @@ var MastraVoiceAgent = class extends voice.Agent {
|
|
|
387
387
|
extra: { [VOICE_TURN_METADATA]: identity }
|
|
388
388
|
}
|
|
389
389
|
};
|
|
390
|
-
|
|
390
|
+
return FlushSentinel;
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
};
|
|
@@ -459,7 +459,7 @@ async function mastraLLMNode(agent, chatCtx, toolCtx, modelSettings) {
|
|
|
459
459
|
if (!source) return null;
|
|
460
460
|
return mapVoiceStream(source, (chunk) => {
|
|
461
461
|
if (typeof chunk === "object" && chunk.delta?.extra?.["mastra.voice.flush"] === true) return FlushSentinel;
|
|
462
|
-
|
|
462
|
+
return chunk;
|
|
463
463
|
});
|
|
464
464
|
}
|
|
465
465
|
function speechResult(handle, outcome) {
|
|
@@ -813,4 +813,4 @@ function createRemoteAgentReplyGenerator(options) {
|
|
|
813
813
|
}
|
|
814
814
|
export { createAgentReplyGenerator as a, chatContextToMessages as c, MastraVoiceAgent as i, extractNewTurnMessages as l, mastraLLMNode as n, createMastraVoiceAgent as o, observeVoiceSession as r, instrumentVoiceReply as s, createRemoteAgentReplyGenerator as t };
|
|
815
815
|
|
|
816
|
-
//# sourceMappingURL=remote-
|
|
816
|
+
//# sourceMappingURL=remote-DNmutTEU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-DNmutTEU.js","names":["ReadableStream","ReadableStream"],"sources":["../src/messages.ts","../src/voice-stream.ts","../src/bridge.ts","../src/speech.ts","../src/remote.ts"],"sourcesContent":["import type { llm } from '@livekit/agents';\n\n/**\n * Fixed id LiveKit gives the customer Agent's instructions when it injects them as a leading\n * `role: 'system'` message into the chat context passed to `chat()` / `llmNode`. We drop this\n * item so the server-side Mastra agent's own system prompt is authoritative.\n */\nexport const LIVEKIT_INSTRUCTIONS_MESSAGE_ID = 'lk.agent_task.instructions';\n\n/**\n * A message bound for `agent.stream(...)` (in-process) or the Mastra server stream route (remote).\n * `id` carries the LiveKit `ChatMessage.id` so the server can dedupe/upsert by id — making\n * base-class retries, preemptive double-sends, and the interrupted-turn reconciliation recipe idempotent.\n */\nexport type VoiceTurnMessage =\n | { role: 'system'; content: string; id?: string }\n | { role: 'user'; content: string; id?: string }\n | { role: 'assistant'; content: string; id?: string };\n\nfunction textOfMessage(message: llm.ChatMessage): string {\n const parts: string[] = [];\n for (const part of message.content) {\n if (typeof part === 'string') {\n parts.push(part);\n } else if (part.type === 'instructions') {\n parts.push(part.value);\n } else if (part.type === 'audio_content' && part.transcript) {\n parts.push(part.transcript);\n }\n }\n return parts.join('\\n').trim();\n}\n\nfunction toVoiceTurnMessage(item: llm.ChatItem): VoiceTurnMessage | undefined {\n if (item.type !== 'message') return undefined;\n const content = textOfMessage(item);\n if (!content) return undefined;\n const id = item.id;\n if (item.role === 'user') return { role: 'user', content, id };\n if (item.role === 'assistant') return { role: 'assistant', content, id };\n // 'system' and 'developer' both map to a Mastra system message.\n return { role: 'system', content, id };\n}\n\n/**\n * Extracts only the messages added since the agent last spoke. Used when Mastra Memory is\n * the source of truth for conversation history: prior turns are already persisted in the\n * thread, so re-sending them would duplicate history.\n *\n * Two extensions over the naive \"slice after the last assistant message\":\n *\n * - **Interrupted-turn self-heal:** when the last assistant message was cut off by barge-in\n * (`interrupted: true`), the server never persisted it — aborted runs skip persistence — so\n * its heard-only text is missing from the thread. Re-send that fragment (ordered first) this\n * turn to backfill it. It stops being \"the last assistant message\" once a full reply lands,\n * so each interrupted fragment is sent exactly once, on the following turn.\n * - **Instructions filter:** LiveKit injects the customer Agent's `instructions` as a\n * leading `system` message ({@link LIVEKIT_INSTRUCTIONS_MESSAGE_ID}); the server-side Mastra\n * agent owns its own system prompt, so drop it (it would otherwise ship on the first turn,\n * before any assistant message).\n */\nexport function extractNewTurnMessages(chatCtx: llm.ChatContext): VoiceTurnMessage[] {\n const items = chatCtx.items;\n let lastAssistantIdx = -1;\n for (let i = items.length - 1; i >= 0; i--) {\n const item = items[i];\n if (item?.type === 'message' && item.role === 'assistant') {\n lastAssistantIdx = i;\n break;\n }\n }\n const lastAssistant = lastAssistantIdx >= 0 ? items[lastAssistantIdx] : undefined;\n const healInterrupted =\n lastAssistant?.type === 'message' && lastAssistant.role === 'assistant' && lastAssistant.interrupted;\n // Include the interrupted fragment by starting the slice AT it, otherwise start strictly after.\n const startIdx = healInterrupted ? lastAssistantIdx : lastAssistantIdx + 1;\n\n const messages: VoiceTurnMessage[] = [];\n for (const item of items.slice(startIdx)) {\n if (item.type === 'message' && item.id === LIVEKIT_INSTRUCTIONS_MESSAGE_ID) continue;\n const message = toVoiceTurnMessage(item);\n if (message) messages.push(message);\n }\n return messages;\n}\n\n/**\n * Converts the full LiveKit chat context to Mastra messages. Used when the bridge runs\n * without Mastra Memory and LiveKit's in-session context is the only history. The agent's\n * LiveKit-level instructions are excluded — the Mastra agent applies its own instructions.\n */\nexport function chatContextToMessages(chatCtx: llm.ChatContext): VoiceTurnMessage[] {\n const withoutInstructions = chatCtx.copy({ excludeInstructions: true, excludeFunctionCall: true });\n const messages: VoiceTurnMessage[] = [];\n for (const item of withoutInstructions.items) {\n const message = toVoiceTurnMessage(item);\n if (message) messages.push(message);\n }\n return messages;\n}\n","/** A one-to-one mapping that awaits upstream cancellation, including pending reads. */\nexport function mapVoiceStream<Input, Output>(\n source: ReadableStream<Input>,\n map: (chunk: Input) => Output,\n): ReadableStream<Output> {\n const reader = source.getReader();\n let cancelled = false;\n return new ReadableStream<Output>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (cancelled) return;\n if (done) controller.close();\n else controller.enqueue(map(value));\n } catch (error) {\n if (!cancelled) controller.error(error);\n }\n },\n cancel(reason) {\n cancelled = true;\n return reader.cancel(reason);\n },\n });\n}\n","import { ReadableStream } from 'node:stream/web';\nimport { FlushSentinel, llm, voice } from '@livekit/agents';\nimport type { Agent as MastraAgent, AgentExecutionOptionsBase, AgentMemoryOption } from '@mastra/core/agent';\nimport type { TracingContext } from '@mastra/core/observability';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { chatContextToMessages, extractNewTurnMessages } from './messages';\nimport type { VoiceTurnMessage } from './messages';\nimport { createGenerationMetrics, VOICE_TEXT_FLUSH, VOICE_TURN_METADATA } from './turn-metrics';\nimport type { VoiceReplyChunk, VoiceTurnMetricsHook } from './turn-metrics';\nimport { mapVoiceStream } from './voice-stream';\n\nconst DEFAULT_INSTRUCTIONS = 'You are a helpful voice assistant powered by a Mastra agent.';\n\n/** Default spoken text for periodic AI re-disclosure. See {@link MastraVoiceAgentOptions.greetingReminder}. */\nexport const DEFAULT_DISCLOSURE_REMINDER = \"Just a reminder, you're speaking with an AI assistant.\";\n\n/**\n * Tracks periodic AI re-disclosure for a single call. `due()` returns the reminder text once\n * `everyMs` has elapsed since the last disclosure (resetting the clock), otherwise `undefined`.\n * Time is injectable so the interval logic is deterministically testable.\n */\nexport class DisclosureReminder {\n private lastAt: number;\n constructor(\n private readonly everyMs: number,\n private readonly text: string,\n now: number = Date.now(),\n ) {\n this.lastAt = now;\n }\n /** Call once per turn: the reminder text if it's due, else `undefined`. Does not reset the clock —\n * call {@link DisclosureReminder.markDelivered} once the reminder is actually threaded into the\n * outgoing reply, so a reminder that never makes it out isn't silently skipped for a full interval. */\n due(now: number = Date.now()): string | undefined {\n if (now - this.lastAt < this.everyMs) return undefined;\n return this.text;\n }\n /** Resets the clock. Call only once the reminder text from {@link due} was actually emitted. */\n markDelivered(now: number = Date.now()): void {\n this.lastAt = now;\n }\n}\n\n/**\n * Wraps `source` in a stream that emits `text` as a single leading chunk (with a trailing space, so\n * TTS pauses before the reply) before piping the rest of `source` through unchanged. Cancelling the\n * wrapper cancels `source` — so barge-in still aborts the underlying generation.\n */\nexport function prependText<T>(source: ReadableStream<T>, text: string): ReadableStream<T | string> {\n const prefix = text.endsWith(' ') ? text : `${text} `;\n const reader = source.getReader();\n return new ReadableStream<T | string>({\n start(controller) {\n controller.enqueue(prefix);\n },\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (done) controller.close();\n else controller.enqueue(value);\n } catch (error) {\n controller.error(error);\n }\n },\n cancel(reason) {\n return reader.cancel(reason);\n },\n });\n}\n\nexport type MastraStreamOptions = Partial<AgentExecutionOptionsBase<unknown>>;\n\nexport interface VoiceToolCall {\n toolCallId: string;\n toolName: string;\n args?: unknown;\n}\n\n/**\n * Token usage for one turn, captured from the model's `finish` chunk. Field names mirror LiveKit's\n * `CompletionUsage` so the plugin can forward it into `metrics_collected` without remapping.\n */\nexport interface VoiceTurnUsage {\n /** Tokens in the prompt (LiveKit `promptTokens`). */\n promptTokens: number;\n /** Tokens in the completion (LiveKit `completionTokens`). */\n completionTokens: number;\n /** Cached prompt tokens (LiveKit `promptCachedTokens`). */\n promptCachedTokens: number;\n /** Total tokens for the turn. */\n totalTokens: number;\n}\n\n/**\n * Maps a Mastra `finish` chunk's usage (`payload.output.usage`, AI-SDK `LanguageModelUsage`) to the\n * LiveKit-shaped {@link VoiceTurnUsage}, or `undefined` when the chunk carries no token counts.\n * Handles both the flat V2 usage shape (`inputTokens`/`outputTokens`) and the nested V3 shape\n * (`inputTokens.total`/`outputTokens.total`).\n */\nexport function mapTurnUsage(usage: unknown): VoiceTurnUsage | undefined {\n if (!usage || typeof usage !== 'object') return undefined;\n const u = usage as Record<string, unknown>;\n // Prefer the flat V2 shape (`inputTokens: number`); fall back to the nested V3 shape\n // (`inputTokens: { total, cacheRead }`).\n const totalOf = (v: unknown): number | undefined => {\n if (typeof v === 'number') return v;\n if (v && typeof v === 'object' && typeof (v as { total?: unknown }).total === 'number') {\n return (v as { total: number }).total;\n }\n return undefined;\n };\n const cacheReadOf = (v: unknown): number | undefined =>\n v && typeof v === 'object' && typeof (v as { cacheRead?: unknown }).cacheRead === 'number'\n ? (v as { cacheRead: number }).cacheRead\n : undefined;\n\n const promptTokens = totalOf(u.inputTokens) ?? 0;\n const completionTokens = totalOf(u.outputTokens) ?? 0;\n const promptCachedTokens =\n (typeof u.cachedInputTokens === 'number' ? u.cachedInputTokens : cacheReadOf(u.inputTokens)) ?? 0;\n const totalTokens = typeof u.totalTokens === 'number' ? u.totalTokens : promptTokens + completionTokens;\n // Nothing was reported at all → treat as no usage rather than emitting an all-zero chunk.\n if (promptTokens === 0 && completionTokens === 0 && totalTokens === 0 && promptCachedTokens === 0) {\n return undefined;\n }\n return { promptTokens, completionTokens, promptCachedTokens, totalTokens };\n}\n\nexport interface MastraVoiceAgentMemory {\n thread: string;\n resource?: string;\n /**\n * Per-call Mastra memory config forwarded to the agent as `memory.options` (see core's\n * `AgentMemoryOption`). The notable use is `{ readOnly: true }`: the agent still reads thread\n * history but persists nothing, which keeps LiveKit's preemptive (speculative) turns from writing\n * partial user and assistant messages to the thread. With `readOnly` the caller owns persistence\n * of committed turns (e.g. from `onTurnComplete` via `memory.saveMessages`).\n */\n options?: AgentMemoryOption['options'];\n}\n\n/**\n * Per-turn context handed to a {@link VoiceReplyGenerator}. LiveKit calls `llmNode` once per\n * detected user turn; the bridge builds this context and asks the generator for the reply.\n */\nexport interface VoiceTurnContext {\n /** Stable user-turn identifier, supplied by the worker or plugin. */\n turnId?: string;\n /** Unique generation attempt identifier, including speculative replies and retries. */\n attemptId?: string;\n /** @internal Bridge-side timing collector. */\n metrics?: ReturnType<typeof createGenerationMetrics>;\n /**\n * The messages to generate a reply from. With Mastra Memory on, only the messages new since\n * the agent last spoke (history comes from the thread); with memory off, the full session.\n *\n * For a workflow / custom generator: pass these straight to a memory-backed `agent.stream(...,\n * { memory })` inside a step so the agent backfills history from the thread (no duplication). A\n * stateless workflow that wants the entire transcript every turn should read `chatCtx` instead\n * (e.g. `chatContextToMessages(chatCtx)`), since there is no thread to backfill from.\n */\n messages: VoiceTurnMessage[];\n /** The raw LiveKit chat context, for generators that want the full transcript or message parts. */\n chatCtx: llm.ChatContext;\n /** Resolved memory mapping for the call, or `false` when memory is disabled. */\n memory: MastraVoiceAgentMemory | false;\n /** Request context forwarded to generation. */\n requestContext?: RequestContext;\n /** Voice-call span context, so each turn's generation nests under the call trace. */\n tracingContext?: TracingContext;\n /**\n * Internal, per-turn side channel for token usage. A generator invokes this once, when the\n * `finish` chunk carries usage, so the caller (e.g. `MastraLLMStream`) can attribute usage to\n * exactly this turn — kept on the context (not on generator options) so overlapping turns from\n * preemptive generation can't misattribute usage. Fire-and-forget; the generator does not await it.\n */\n onUsage?: (usage: VoiceTurnUsage) => void;\n}\n\n/**\n * What a turn produced, handed to {@link VoiceTurnCompleteHook} after the reply finishes.\n */\nexport interface VoiceTurnResult {\n /** The assistant reply text streamed this turn, accumulated from the model's text deltas. */\n text: string;\n /** Tool calls the agent made during the turn, in order. */\n toolCalls: VoiceToolCall[];\n /** True when barge-in cut the turn short before it finished streaming. */\n interrupted: boolean;\n /** Token usage for the turn when the model reported it in its `finish` chunk. */\n usage?: VoiceTurnUsage;\n}\n\n/** {@link VoiceTurnContext} plus the reply it produced. Passed to {@link VoiceTurnCompleteHook}. */\nexport interface VoiceTurnCompleteContext extends VoiceTurnContext {\n /** The reply the agent produced this turn. */\n result: VoiceTurnResult;\n}\n\n/**\n * Called once per turn AFTER the reply has finished streaming to text-to-speech — off the audio\n * path. It runs fire-and-forget: the turn does not await it, so post-turn work (memory\n * maintenance, CRM writes, analytics) never delays what the caller hears or the next turn. A\n * thrown error or rejected promise is logged, not propagated. Because the resolved `memory`\n * mapping (`thread`/`resource`) is on the context, this is the place for a truly non-blocking\n * `memory.updateWorkingMemory(...)`. See {@link MastraVoiceAgentOptions.onTurnComplete}.\n */\nexport type VoiceTurnCompleteHook = (ctx: VoiceTurnCompleteContext) => void | Promise<void>;\n\n/**\n * Produces a stream of text deltas for one conversational turn, or `null` to stay silent.\n * Cancelling the returned stream (LiveKit does this on barge-in) must abort the underlying\n * generation. Built-in implementations: {@link createAgentReplyGenerator} (a Mastra agent) and\n * `createWorkflowReplyGenerator` (a Mastra workflow).\n */\nexport type VoiceReplyGenerator = (\n ctx: VoiceTurnContext,\n) => ReadableStream<VoiceReplyChunk> | null | Promise<ReadableStream<VoiceReplyChunk> | null>;\n\nexport interface AgentReplyGeneratorOptions {\n /** The Mastra agent that generates replies. Tools and memory run inside this agent. */\n agent: MastraAgent;\n /** Extra options merged into every `agent.stream()` call (e.g. `tracingContext`). */\n streamOptions?: MastraStreamOptions;\n /** Speak a short phrase while a tool call runs. See {@link MastraVoiceAgentOptions.toolFeedback}. */\n toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;\n /** Notified as each tool-call chunk arrives, mid-stream. See {@link MastraVoiceAgentOptions.onToolCall}. */\n onToolCall?: (toolCall: VoiceToolCall) => void;\n /** Fired off the audio path after the reply streams. See {@link MastraVoiceAgentOptions.onTurnComplete}. */\n onTurnComplete?: VoiceTurnCompleteHook;\n}\n\n/**\n * A {@link VoiceReplyGenerator} backed by a Mastra agent: runs the agent's full loop (model,\n * tools, memory) and streams its text deltas. On barge-in the returned stream is cancelled,\n * which aborts the in-flight `agent.stream()`.\n */\nexport function createAgentReplyGenerator(options: AgentReplyGeneratorOptions): VoiceReplyGenerator {\n const { agent, streamOptions, toolFeedback, onToolCall, onTurnComplete } = options;\n return ctx => {\n if (ctx.messages.length === 0) return null;\n\n const abortController = new AbortController();\n const mergedOptions: MastraStreamOptions = {\n ...streamOptions,\n abortSignal: abortController.signal,\n };\n if (ctx.memory) mergedOptions.memory = ctx.memory;\n if (ctx.requestContext) mergedOptions.requestContext = ctx.requestContext;\n\n let cancelled = false;\n // Accumulated as the turn streams so the post-turn hook can see what was actually produced.\n let replyText = '';\n const toolCalls: VoiceToolCall[] = [];\n let usage: VoiceTurnUsage | undefined;\n\n // Fire-and-forget after generation; playback can still be pending, and not awaited, so it never delays the next turn. Errors are logged, not thrown.\n const emitTurnComplete = (interrupted: boolean) => {\n if (!onTurnComplete) return;\n const completeCtx: VoiceTurnCompleteContext = {\n ...ctx,\n result: { text: replyText, toolCalls, interrupted, usage },\n };\n Promise.resolve()\n .then(() => onTurnComplete(completeCtx))\n .catch(error => {\n console.warn('@mastra/livekit: onTurnComplete hook threw', error);\n });\n };\n\n return new ReadableStream<VoiceReplyChunk>({\n start: async controller => {\n try {\n const result = await agent.stream(ctx.messages, mergedOptions);\n for await (const chunk of result.fullStream) {\n if (cancelled) break;\n if (chunk.type === 'text-delta') {\n if (chunk.payload.text) {\n replyText += chunk.payload.text;\n controller.enqueue(chunk.payload.text);\n }\n } else if (chunk.type === 'text-end' || chunk.type === 'step-finish') {\n controller.enqueue(VOICE_TEXT_FLUSH);\n } else if (chunk.type === 'tool-result') {\n ctx.metrics?.toolEnd(chunk.payload.toolCallId);\n } else if (chunk.type === 'tool-call') {\n const toolCall: VoiceToolCall = {\n toolCallId: chunk.payload.toolCallId,\n toolName: chunk.payload.toolName,\n args: chunk.payload.args,\n };\n toolCalls.push(toolCall);\n ctx.metrics?.toolStart({ toolCallId: toolCall.toolCallId, toolName: toolCall.toolName });\n controller.enqueue(VOICE_TEXT_FLUSH);\n // Observer hooks are customer code: a throw must not tear down an otherwise healthy\n // reply stream (same isolation as onTurnComplete).\n try {\n onToolCall?.(toolCall);\n } catch (error) {\n console.warn('@mastra/livekit: onToolCall hook threw', error);\n }\n if (toolFeedback) {\n let filler: string | undefined | void;\n try {\n filler = toolFeedback(toolCall);\n } catch (error) {\n console.warn('@mastra/livekit: toolFeedback hook threw', error);\n }\n if (filler) {\n controller.enqueue(filler.endsWith(' ') ? filler : `${filler} `);\n controller.enqueue(VOICE_TEXT_FLUSH);\n }\n }\n } else if (chunk.type === 'finish') {\n // Usage is dropped from the spoken stream but surfaced via the per-turn side channel\n // and on the turn result, so the plugin and onTurnComplete consumers can read it.\n const output = (chunk.payload as { output?: { usage?: unknown } }).output;\n const turnUsage = mapTurnUsage(output?.usage);\n if (turnUsage) {\n usage = turnUsage;\n try {\n ctx.onUsage?.(turnUsage);\n } catch (error) {\n console.warn('@mastra/livekit: onUsage hook threw', error);\n }\n }\n } else if (chunk.type === 'error') {\n const error = chunk.payload.error;\n throw error instanceof Error ? error : new Error(String(error));\n }\n }\n if (!cancelled) controller.close();\n // Success, or a clean barge-in break out of the loop: the turn is done either way.\n emitTurnComplete(cancelled);\n } catch (error) {\n // Barge-in cancels the stream and aborts generation; that's not a failure — the turn\n // still completed (interrupted), so the hook still fires for memory reconciliation.\n if (cancelled || abortController.signal.aborted) {\n emitTurnComplete(true);\n return;\n }\n controller.error(error);\n }\n },\n cancel: () => {\n cancelled = true;\n abortController.abort();\n },\n });\n };\n}\n\nexport interface MastraVoiceAgentOptions {\n /** Receives per-attempt generation metrics. Playback metrics come from observeVoiceSession. */\n onTurnMetrics?: VoiceTurnMetricsHook;\n /**\n * The Mastra agent that generates replies. Tools and memory run inside this agent. Provide\n * either this or {@link MastraVoiceAgentOptions.generate}.\n */\n agent?: MastraAgent;\n /**\n * A lower-level reply generator (e.g. from `createWorkflowReplyGenerator`). Use instead of\n * `agent` to drive replies with a workflow or any custom generator.\n */\n generate?: VoiceReplyGenerator;\n /**\n * Conversation persistence. When set, only messages new since the agent last spoke are\n * sent each turn and Mastra Memory supplies history. When `false`, the full LiveKit\n * in-session context is sent on every turn instead.\n */\n memory?: MastraVoiceAgentMemory | false;\n /** Request context entries forwarded to generation. */\n requestContext?: RequestContext | Record<string, unknown>;\n /**\n * Called when the Mastra agent starts a tool call mid-reply. Return a short phrase (e.g. \"Let\n * me look that up.\") to speak it while the tool runs; it also appears in the transcript. Return\n * nothing to stay silent. Applies to the agent generator built here; the workflow generator\n * takes its own equivalent via `createWorkflowReplyGenerator`.\n */\n toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;\n /**\n * Called as each tool call starts mid-reply (before the tool result is known), the building block\n * for tool-driven side effects — analytics, agent-initiated hang-up — without waiting for the turn\n * to finish. Runs synchronously on the stream; keep it cheap and non-throwing. Applies to the agent\n * generator built here; the workflow generator surfaces tool calls via `onTurnComplete` instead.\n */\n onToolCall?: (toolCall: VoiceToolCall) => void;\n /**\n * Called once per turn after the reply has finished streaming to text-to-speech. Runs off the\n * audio path and fire-and-forget — the turn does not await it — so post-turn memory\n * maintenance, CRM writes, or analytics never delay the caller or the next turn. The context\n * carries the produced reply ({@link VoiceTurnResult}) and the resolved `memory` mapping, so\n * this is where a truly non-blocking `memory.updateWorkingMemory(...)` belongs. A thrown error\n * or rejected promise is logged, not propagated. Applies to the agent generator built here; the\n * workflow generator takes its own via `createWorkflowReplyGenerator`.\n */\n onTurnComplete?: VoiceTurnCompleteHook;\n /**\n * Periodic AI re-disclosure. When set, once `everyMs` has elapsed since the last disclosure the\n * NEXT turn's reply is prefixed with `text` (spoken at the turn boundary, never mid-turn), so long\n * calls keep re-disclosing the AI status. Applies to the agent and workflow/custom generators. The\n * worker derives this from `configuration.greeting.repeatEvery` / `repeatText`; `text` defaults to\n * {@link DEFAULT_DISCLOSURE_REMINDER}.\n */\n greetingReminder?: { everyMs: number; text?: string };\n /** Extra options merged into every `agent.stream()` call (agent generator only). */\n streamOptions?: MastraStreamOptions;\n /** LiveKit agent instructions. Unused for reply generation (the Mastra agent/workflow applies its own). */\n instructions?: string;\n id?: voice.AgentOptions<unknown>['id'];\n stt?: voice.AgentOptions<unknown>['stt'];\n vad?: voice.AgentOptions<unknown>['vad'];\n tts?: voice.AgentOptions<unknown>['tts'];\n turnHandling?: voice.AgentOptions<unknown>['turnHandling'];\n}\n\nfunction toRequestContext(value: RequestContext | Record<string, unknown> | undefined): RequestContext | undefined {\n if (!value) return undefined;\n if (value instanceof RequestContext) return value;\n return new RequestContext<unknown>(Object.entries(value));\n}\n\n/**\n * The session only runs its cascaded reply pipeline when an `llm` instance is present —\n * `llmNode` replaces the inference step, but the gate checks `llm instanceof LLM`. This\n * placeholder satisfies the gate; the Mastra agent/workflow does the actual generation.\n */\nclass MastraPlaceholderLLM extends llm.LLM {\n label(): string {\n return 'mastra.MastraVoiceAgent';\n }\n\n override get model(): string {\n return 'mastra-agent';\n }\n\n override get provider(): string {\n return 'mastra';\n }\n\n chat(): llm.LLMStream {\n throw new Error(\n '@mastra/livekit: reply generation runs through the Mastra agent via llmNode; the placeholder LLM cannot be used for inference.',\n );\n }\n}\n\n/**\n * A LiveKit `voice.Agent` whose replies come from a Mastra agent or workflow.\n *\n * LiveKit keeps ownership of the audio loop (VAD, STT, turn detection, TTS, barge-in) and calls\n * `llmNode` once per detected user turn; the node delegates to a {@link VoiceReplyGenerator}\n * which streams text deltas back. On barge-in LiveKit cancels the returned stream, which aborts\n * the in-flight generation.\n */\nexport class MastraVoiceAgent extends voice.Agent {\n readonly mastraAgent?: MastraAgent;\n readonly memory: MastraVoiceAgentMemory | false;\n readonly requestContext?: RequestContext;\n readonly streamOptions?: MastraStreamOptions;\n private readonly replyGenerator: VoiceReplyGenerator;\n private readonly onTurnMetrics?: VoiceTurnMetricsHook;\n private readonly reminder?: DisclosureReminder;\n\n constructor(options: MastraVoiceAgentOptions) {\n if (options.agent && options.generate) {\n throw new Error(\n '@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`, not both — they are mutually exclusive reply sources.',\n );\n }\n super({\n id: options.id,\n instructions: options.instructions ?? DEFAULT_INSTRUCTIONS,\n stt: options.stt,\n vad: options.vad,\n llm: new MastraPlaceholderLLM(),\n tts: options.tts,\n turnHandling: options.turnHandling,\n });\n this.onTurnMetrics = options.onTurnMetrics;\n this.memory = options.memory ?? false;\n this.requestContext = toRequestContext(options.requestContext);\n this.streamOptions = options.streamOptions;\n if (options.greetingReminder) {\n this.reminder = new DisclosureReminder(\n options.greetingReminder.everyMs,\n options.greetingReminder.text?.trim() || DEFAULT_DISCLOSURE_REMINDER,\n );\n }\n\n if (options.generate) {\n this.replyGenerator = options.generate;\n } else if (options.agent) {\n this.mastraAgent = options.agent;\n this.replyGenerator = createAgentReplyGenerator({\n agent: options.agent,\n streamOptions: options.streamOptions,\n toolFeedback: options.toolFeedback,\n onToolCall: options.onToolCall,\n onTurnComplete: options.onTurnComplete,\n });\n } else {\n throw new Error('@mastra/livekit: MastraVoiceAgent requires `agent` or `generate`.');\n }\n }\n\n override async llmNode(\n chatCtx: llm.ChatContext,\n _toolCtx: llm.ToolContext,\n _modelSettings: voice.ModelSettings,\n ): Promise<ReadableStream<llm.ChatChunk | string | FlushSentinel> | null> {\n const messages: VoiceTurnMessage[] =\n this.memory === false ? chatContextToMessages(chatCtx) : extractNewTurnMessages(chatCtx);\n if (messages.length === 0) return null;\n\n const ctx = {\n messages,\n chatCtx,\n memory: this.memory,\n requestContext: this.requestContext,\n tracingContext: this.streamOptions?.tracingContext,\n };\n const { reply, identity } = await instrumentVoiceReply(this.replyGenerator, ctx, this.onTurnMetrics);\n if (!reply) return null;\n\n // Periodic AI re-disclosure: when the interval has elapsed, prefix this turn's spoken reply with\n // the reminder. Done at the turn boundary (never mid-turn), riding the same stream so barge-in\n // cancellation still propagates to the underlying generation. The clock only resets once the\n // reminder is actually threaded into the outgoing reply below, not just because it was due.\n // KNOWN LIMIT: \"threaded into the reply\" is stream-build time, not playout. Under LiveKit's\n // preemptive generation a discarded speculative reply still resets the clock, so the next real\n // turn can miss its reminder — hence the documented repeatEvery/preemptiveGeneration\n // incompatibility. A playout-accurate reset needs a confirmed-turn signal llmNode doesn't have.\n const reminder = this.reminder?.due();\n if (reminder) this.reminder?.markDelivered();\n const source = reminder ? prependText(reply, reminder) : reply;\n return mapVoiceStream(source, chunk => {\n if (typeof chunk === 'string') {\n return {\n id: identity.attemptId,\n delta: { role: 'assistant' as const, content: chunk, extra: { [VOICE_TURN_METADATA]: identity } },\n };\n }\n return FlushSentinel;\n });\n }\n}\n\nexport function createMastraVoiceAgent(options: MastraVoiceAgentOptions): MastraVoiceAgent {\n return new MastraVoiceAgent(options);\n}\n\n/** @internal Wrap every reply source, including custom generators, with one terminal metric. */\nexport async function instrumentVoiceReply(\n generator: VoiceReplyGenerator,\n ctx: VoiceTurnContext,\n onTurnMetrics?: VoiceTurnMetricsHook,\n): Promise<{ reply: ReadableStream<VoiceReplyChunk> | null; identity: { turnId: string; attemptId: string } }> {\n const identity = {\n turnId: ctx.turnId ?? ctx.messages.findLast(message => message.role === 'user')?.id ?? crypto.randomUUID(),\n attemptId: crypto.randomUUID(),\n };\n const metrics = createGenerationMetrics(identity, onTurnMetrics);\n let reply: ReadableStream<VoiceReplyChunk> | null;\n try {\n reply = await generator({ ...ctx, ...identity, metrics });\n } catch (error) {\n metrics.end('failed');\n throw error;\n }\n if (!reply) {\n metrics.end('completed');\n return { reply: null, identity };\n }\n const reader = reply.getReader();\n let cancelled = false;\n return {\n identity,\n reply: new ReadableStream<VoiceReplyChunk>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n if (cancelled) return;\n if (done) {\n metrics.end('completed');\n controller.close();\n } else {\n if (typeof value === 'string' && value) metrics.text();\n controller.enqueue(value);\n }\n } catch (error) {\n if (cancelled) return;\n metrics.end('failed');\n controller.error(error);\n }\n },\n cancel(reason) {\n cancelled = true;\n metrics.end('interrupted');\n return reader.cancel(reason);\n },\n }),\n };\n}\n","import { FlushSentinel, voice } from '@livekit/agents';\nimport type { llm } from '@livekit/agents';\nimport { notifyVoiceObserver, VOICE_FLUSH_METADATA, VOICE_TURN_METADATA } from './turn-metrics';\nimport type { VoiceOutcome, VoiceSpeechCompleteHook, VoiceSpeechResult, VoiceTurnMetricsHook } from './turn-metrics';\nimport { mapVoiceStream } from './voice-stream';\n\n/**\n * Use from a custom Agent.llmNode when using MastraLLM. LiveKit's LLMStream only accepts\n * ChatChunks; this node translates our boundary metadata into LiveKit's supported marker.\n */\nexport async function mastraLLMNode(\n agent: voice.Agent,\n chatCtx: llm.ChatContext,\n toolCtx: llm.ToolContext,\n modelSettings: voice.ModelSettings,\n): Promise<ReadableStream<llm.ChatChunk | string | FlushSentinel> | null> {\n const source = await voice.Agent.default.llmNode(agent, chatCtx, toolCtx, modelSettings);\n if (!source) return null;\n return mapVoiceStream(source, chunk => {\n if (typeof chunk === 'object' && chunk.delta?.extra?.[VOICE_FLUSH_METADATA] === true) {\n return FlushSentinel;\n }\n return chunk;\n });\n}\n\nexport interface ObserveVoiceSessionOptions {\n onTurnMetrics?: VoiceTurnMetricsHook;\n onSpeechComplete?: VoiceSpeechCompleteHook;\n}\n\nfunction speechResult(handle: voice.SpeechHandle, outcome: VoiceOutcome): VoiceSpeechResult {\n const messages = handle.chatItems.filter(\n (item): item is llm.ChatMessage => item.type === 'message' && item.role === 'assistant',\n );\n const identities = messages.map(\n message => message.extra[VOICE_TURN_METADATA] as { turnId?: unknown; attemptId?: unknown } | undefined,\n );\n const identity = identities[0];\n const sameIdentity = identities.every(\n value => value?.turnId === identity?.turnId && value?.attemptId === identity?.attemptId,\n );\n const starts = messages\n .map(message => message.metrics.startedSpeakingAt)\n .filter((n): n is number => typeof n === 'number' && Number.isFinite(n));\n const stops = messages\n .map(message => message.metrics.stoppedSpeakingAt)\n .filter((n): n is number => typeof n === 'number' && Number.isFinite(n));\n const firstAudio = messages\n .map(message => message.metrics.e2eLatency)\n .find(n => typeof n === 'number' && Number.isFinite(n));\n const speechStartedAt = starts.length ? Math.min(...starts) * 1000 : undefined;\n const speechEndedAt = stops.length ? Math.max(...stops) * 1000 : undefined;\n const firstAudioMs = firstAudio === undefined ? undefined : firstAudio * 1000;\n const playedText = messages.map(message => message.textContent).join('');\n return {\n version: 1,\n phase: 'speech',\n turnId: sameIdentity && typeof identity?.turnId === 'string' ? identity.turnId : handle.id,\n attemptId: sameIdentity && typeof identity?.attemptId === 'string' ? identity.attemptId : undefined,\n speechId: handle.id,\n outcome,\n measurement: 'server-playout',\n speechStartedAt,\n speechEndedAt,\n firstAudioMs,\n completionMs:\n outcome === 'completed' &&\n firstAudioMs !== undefined &&\n speechStartedAt !== undefined &&\n speechEndedAt !== undefined\n ? firstAudioMs + speechEndedAt - speechStartedAt\n : undefined,\n playedText: messages.length ? playedText : undefined,\n transcriptSource: messages.length ? 'livekit' : 'unavailable',\n };\n}\n\n/**\n * Attach before session.start(). Observes generation-independent playback completion;\n * never writes memory. Return value detaches listeners and finalizes pending speech as cancelled.\n * LiveKit's transcript can be estimated. Server output cannot confirm remote human hearing.\n */\nexport function observeVoiceSession(session: voice.AgentSession, options: ObserveVoiceSessionOptions): () => void {\n const pending = new Set<voice.SpeechHandle>();\n let detached = false;\n const finish = (handle: voice.SpeechHandle, outcome: VoiceOutcome) => {\n if (!pending.delete(handle)) return;\n const result = speechResult(handle, outcome);\n const { playedText: _text, transcriptSource: _source, ...metrics } = result;\n notifyVoiceObserver(options.onTurnMetrics, metrics);\n notifyVoiceObserver(options.onSpeechComplete, result);\n };\n const onSpeech = ({ speechHandle }: voice.SpeechCreatedEvent) => {\n if (detached || pending.has(speechHandle)) return;\n pending.add(speechHandle);\n void speechHandle.waitForPlayout().then(\n () => {\n finish(\n speechHandle,\n speechHandle.exception() ? 'failed' : speechHandle.interrupted ? 'interrupted' : 'completed',\n );\n },\n () => finish(speechHandle, 'failed'),\n );\n };\n const detach = (outcome: VoiceOutcome) => {\n if (detached) return;\n detached = true;\n session.off(voice.AgentSessionEventTypes.SpeechCreated, onSpeech);\n session.off(voice.AgentSessionEventTypes.Close, onClose);\n for (const handle of pending) finish(handle, outcome);\n };\n const onClose = (event: voice.CloseEvent) => detach(event.error ? 'failed' : 'cancelled');\n session.on(voice.AgentSessionEventTypes.SpeechCreated, onSpeech);\n session.on(voice.AgentSessionEventTypes.Close, onClose);\n return () => detach('cancelled');\n}\n","import { ReadableStream } from 'node:stream/web';\nimport { APIConnectionError, APIError, APIStatusError, APITimeoutError } from '@livekit/agents';\nimport { RequestContext } from '@mastra/core/request-context';\nimport { mapTurnUsage } from './bridge';\nimport type {\n VoiceReplyGenerator,\n VoiceToolCall,\n VoiceTurnCompleteContext,\n VoiceTurnCompleteHook,\n VoiceTurnUsage,\n} from './bridge';\nimport { VOICE_TEXT_FLUSH } from './turn-metrics';\nimport type { VoiceReplyChunk } from './turn-metrics';\n\nconst DEFAULT_API_PREFIX = '/api';\n/** Connect + first-token budget when not overridden. Plugin mode passes `connOptions.timeoutMs`. */\nexport const DEFAULT_REMOTE_TIMEOUT_MS = 10_000;\n/** Standalone initial-connection retry attempts. Plugin mode forces this to 0 (base class owns retries). */\nexport const DEFAULT_REMOTE_RETRIES = 2;\n\n/** Thrown (as a plain, non-retryable error) when the server emits a chunk that needs client action. */\nconst HITL_UNSUPPORTED_MESSAGE =\n '@mastra/livekit: the agent requested tool approval or suspended a tool call; human-in-the-loop ' +\n 'flows (approve-tool-call / resume-stream) are not supported on the voice path. Remove requireApproval ' +\n 'or suspend from the tools this agent uses on voice calls.';\n\n/**\n * Options for the remote Mastra transport. Shape mirrors the in-process `AgentReplyGeneratorOptions`\n * so `MastraLLM` can accept either source interchangeably.\n */\nexport interface RemoteMastraAgentOptions {\n /** Base URL of the remote Mastra server, e.g. `https://my-app.mastra.cloud`. */\n baseUrl: string;\n /** Agent key in the Mastra config's `agents`. */\n agentId: string;\n /** Path prefix for the Mastra API. Defaults to `'/api'`. */\n apiPrefix?: string;\n /** Static headers, or a (possibly async) resolver invoked per turn — e.g. to mint a fresh token. */\n headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);\n /** Injectable `fetch` for tests/proxies. Defaults to `globalThis.fetch`. */\n fetch?: typeof globalThis.fetch;\n /**\n * Connect + first-token timeout in ms. Plugin mode default: LiveKit's `connOptions.timeoutMs` (10s).\n * Standalone default: {@link DEFAULT_REMOTE_TIMEOUT_MS}.\n */\n timeoutMs?: number;\n /**\n * Initial-connection retry attempts (before the first chunk only). Standalone default:\n * {@link DEFAULT_REMOTE_RETRIES}. In plugin mode the LiveKit base class owns retries and this is\n * forced to 0.\n */\n retries?: number;\n /** Extra fields merged into each stream request body (advanced). */\n body?: Record<string, unknown>;\n}\n\n/** {@link RemoteMastraAgentOptions} plus the per-turn observer hooks the generator threads through. */\nexport interface RemoteAgentReplyGeneratorOptions extends RemoteMastraAgentOptions {\n /** Speak a short phrase while a tool runs. See {@link MastraVoiceAgentOptions.toolFeedback}. */\n toolFeedback?: (toolCall: VoiceToolCall) => string | undefined | void;\n /** Notified as each tool-call chunk arrives, mid-stream. See {@link MastraVoiceAgentOptions.onToolCall}. */\n onToolCall?: (toolCall: VoiceToolCall) => void;\n /** Fired off the audio path after the reply streams. See {@link MastraVoiceAgentOptions.onTurnComplete}. */\n onTurnComplete?: VoiceTurnCompleteHook;\n}\n\ntype RawChunk = { type?: string; payload?: Record<string, unknown> };\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\nfunction toMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nasync function resolveHeaders(headers: RemoteMastraAgentOptions['headers']): Promise<Record<string, string>> {\n if (!headers) return {};\n if (typeof headers === 'function') return (await headers()) ?? {};\n return headers;\n}\n\nfunction serializeRequestContext(\n requestContext: RequestContext | Record<string, unknown> | undefined,\n): Record<string, unknown> | undefined {\n if (!requestContext) return undefined;\n // Mirror client-js `parseClientRequestContext`.\n if (requestContext instanceof RequestContext) return Object.fromEntries(requestContext.entries());\n return requestContext;\n}\n\nasync function safeReadBody(response: Response): Promise<object | null> {\n try {\n const text = await response.text();\n if (!text) return null;\n try {\n const parsed: unknown = JSON.parse(text);\n return parsed && typeof parsed === 'object' ? (parsed as object) : { message: String(parsed) };\n } catch {\n return { message: text };\n }\n } catch {\n return null;\n }\n}\n\n/**\n * Reads a Mastra SSE stream and yields each event's parsed JSON. Framing matches the server's\n * `processMastraStream` (buffer, split on `\\n\\n`, strip `data: `, stop on `[DONE]`); undecodable\n * `data:` lines are skipped. Aborting `signal` cancels the underlying reader.\n */\nexport async function* readMastraSSE(\n body: globalThis.ReadableStream<Uint8Array>,\n signal: AbortSignal,\n): AsyncGenerator<RawChunk> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n const onAbort = () => void reader.cancel().catch(() => {});\n if (signal.aborted) {\n void reader.cancel().catch(() => {});\n return;\n }\n signal.addEventListener('abort', onAbort, { once: true });\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const events = buffer.split('\\n\\n');\n buffer = events.pop() ?? '';\n for (const event of events) {\n if (!event.startsWith('data:')) continue;\n const data = event.slice(event.startsWith('data: ') ? 6 : 5).trim();\n if (data === '[DONE]') return;\n if (!data) continue;\n let json: unknown;\n try {\n json = JSON.parse(data);\n } catch {\n continue; // tolerate a stray non-JSON line\n }\n if (json && typeof json === 'object') yield json as RawChunk;\n }\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n try {\n reader.releaseLock();\n } catch {\n // A read may still be pending when the fetch was aborted; the stream is torn down anyway.\n }\n }\n}\n\n/**\n * A {@link VoiceReplyGenerator} that runs the Mastra agent loop on a **remote** Mastra server over\n * HTTP/SSE. Shaped exactly like the in-process `createAgentReplyGenerator`: it consumes the same\n * chunk vocabulary, drives the same `toolFeedback` / `onToolCall` / `onTurnComplete` seams, and\n * cancelling the returned stream (LiveKit does this on barge-in) tears down the HTTP request so the\n * server aborts generation.\n *\n * Usable standalone via the worker's `generate:` hatch (a minimum-viable remote worker mode), and as\n * the transport `MastraLLM` wraps. Errors are thrown as LiveKit `APIError` subclasses so the plugin's\n * base-class retry loop and `FallbackAdapter` behave; a connect + first-token watchdog prevents\n * indefinite dead air.\n */\nexport function createRemoteAgentReplyGenerator(options: RemoteAgentReplyGeneratorOptions): VoiceReplyGenerator {\n const {\n baseUrl,\n agentId,\n apiPrefix = DEFAULT_API_PREFIX,\n headers,\n fetch: fetchImpl = globalThis.fetch,\n timeoutMs = DEFAULT_REMOTE_TIMEOUT_MS,\n retries = DEFAULT_REMOTE_RETRIES,\n body: extraBody,\n toolFeedback,\n onToolCall,\n onTurnComplete,\n } = options;\n\n if (!fetchImpl) {\n throw new Error('@mastra/livekit: no fetch implementation available; pass `fetch` or run on Node ≥ 22.');\n }\n const url = `${trimTrailingSlash(baseUrl)}${apiPrefix}/agents/${agentId}/stream`;\n\n return ctx => {\n if (ctx.messages.length === 0) return null;\n\n // Reassigned per retry attempt (see the loop below) so a watchdog abort on one attempt can't\n // poison the next; `cancel()` always aborts whichever attempt is currently in flight.\n let currentAbortController: AbortController | undefined;\n let cancelled = false;\n // Accumulated as the turn streams so the post-turn hook sees what was actually produced.\n let replyText = '';\n const toolCalls: VoiceToolCall[] = [];\n let usage: VoiceTurnUsage | undefined;\n\n const emitTurnComplete = (interrupted: boolean) => {\n if (!onTurnComplete) return;\n const completeCtx: VoiceTurnCompleteContext = {\n ...ctx,\n result: { text: replyText, toolCalls, interrupted, usage },\n };\n Promise.resolve()\n .then(() => onTurnComplete(completeCtx))\n .catch(error => {\n console.warn('@mastra/livekit: onTurnComplete hook threw', error);\n });\n };\n\n const requestBody: Record<string, unknown> = {\n messages: ctx.messages,\n // The server schema requires a resource when memory is present; default it to the thread id,\n // matching the worker's own thread bootstrap.\n memory: ctx.memory\n ? {\n thread: ctx.memory.thread,\n resource: ctx.memory.resource ?? ctx.memory.thread,\n ...(ctx.memory.options ? { options: ctx.memory.options } : {}),\n }\n : undefined,\n requestContext: serializeRequestContext(ctx.requestContext),\n ...extraBody,\n };\n\n return new ReadableStream<VoiceReplyChunk>({\n start: async controller => {\n // `retryable` is the LiveKit contract flag: true only before the first chunk is emitted, so a\n // voice turn is never replayed half-heard. It also gates the standalone connect-retry.\n let retryable = true;\n try {\n for (let attempt = 0; ; attempt++) {\n // A fresh controller per attempt: reusing one across retries meant a watchdog abort on an\n // earlier attempt left every subsequent attempt's fetch already-aborted before it started.\n const abortController = new AbortController();\n currentAbortController = abortController;\n let timedOut = false;\n let watchdog: ReturnType<typeof setTimeout> | undefined;\n const clearWatchdog = () => {\n if (watchdog) {\n clearTimeout(watchdog);\n watchdog = undefined;\n }\n };\n try {\n watchdog = setTimeout(() => {\n timedOut = true;\n abortController.abort();\n }, timeoutMs);\n (watchdog as { unref?: () => void }).unref?.();\n\n const resolvedHeaders = await resolveHeaders(headers);\n if (cancelled) {\n clearWatchdog();\n break;\n }\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', accept: 'text/event-stream', ...resolvedHeaders },\n body: JSON.stringify(requestBody),\n signal: abortController.signal,\n });\n if (!response.ok) {\n const errorBody = await safeReadBody(response);\n throw new APIStatusError({\n message: `@mastra/livekit: Mastra agent stream request failed with status ${response.status}`,\n options: { statusCode: response.status, body: errorBody, retryable },\n });\n }\n if (!response.body) {\n throw new APIConnectionError({\n message: '@mastra/livekit: Mastra agent stream returned an empty response body',\n options: { retryable },\n });\n }\n\n for await (const chunk of readMastraSSE(\n response.body as unknown as globalThis.ReadableStream<Uint8Array>,\n abortController.signal,\n )) {\n if (cancelled) break;\n // First chunk: the server has committed to this generation — forbid any further\n // retry so the turn can't be replayed mid-stream. The watchdog is NOT cleared here:\n // lifecycle metadata (step-start, text-start, ...) isn't proof the model is\n // producing anything, and disarming on it would turn a post-metadata stall into\n // indefinite dead air. It disarms on the first sign of model output below.\n retryable = false;\n const payload = chunk.payload ?? {};\n switch (chunk.type) {\n case 'text-delta': {\n const text = payload.text;\n if (typeof text === 'string' && text) {\n clearWatchdog();\n replyText += text;\n controller.enqueue(text);\n }\n break;\n }\n case 'text-end':\n case 'step-finish':\n controller.enqueue(VOICE_TEXT_FLUSH);\n break;\n case 'tool-result':\n if (typeof payload.toolCallId === 'string') ctx.metrics?.toolEnd(payload.toolCallId);\n break;\n case 'tool-call': {\n // A tool call is first-token progress too — the model committed to a tool run,\n // which may legitimately outlast the connect budget before any text streams.\n clearWatchdog();\n const toolCall: VoiceToolCall = {\n toolCallId: String(payload.toolCallId ?? ''),\n toolName: String(payload.toolName ?? ''),\n args: payload.args,\n };\n toolCalls.push(toolCall);\n ctx.metrics?.toolStart({ toolCallId: toolCall.toolCallId, toolName: toolCall.toolName });\n controller.enqueue(VOICE_TEXT_FLUSH);\n // Observer hooks are customer code: a throw must not tear down an otherwise\n // healthy reply stream (same isolation as onTurnComplete).\n try {\n onToolCall?.(toolCall);\n } catch (error) {\n console.warn('@mastra/livekit: onToolCall hook threw', error);\n }\n if (toolFeedback) {\n let filler: string | undefined | void;\n try {\n filler = toolFeedback(toolCall);\n } catch (error) {\n console.warn('@mastra/livekit: toolFeedback hook threw', error);\n }\n if (filler) {\n controller.enqueue(filler.endsWith(' ') ? filler : `${filler} `);\n controller.enqueue(VOICE_TEXT_FLUSH);\n }\n }\n break;\n }\n case 'finish': {\n clearWatchdog();\n const output = payload.output as { usage?: unknown } | undefined;\n const turnUsage = mapTurnUsage(output?.usage);\n if (turnUsage) {\n usage = turnUsage;\n try {\n ctx.onUsage?.(turnUsage);\n } catch (error) {\n console.warn('@mastra/livekit: onUsage hook threw', error);\n }\n }\n break;\n }\n case 'tool-call-approval':\n case 'tool-call-suspended':\n throw new Error(HITL_UNSUPPORTED_MESSAGE);\n case 'error': {\n const error = payload.error;\n throw error instanceof Error ? error : new Error(String(error));\n }\n default:\n break; // ignore everything else (text-start, step-start, tool-result, ...)\n }\n }\n clearWatchdog();\n break; // success, or a clean barge-in break out of the SSE loop\n } catch (error) {\n clearWatchdog();\n if (cancelled) break; // barge-in: not a failure, emit interrupted below\n\n // Classify into the LiveKit error vocabulary. Application errors thrown after streaming\n // began (an `error` chunk, a HITL chunk) are not connection failures — propagate as-is.\n let typed: APIError;\n if (timedOut) {\n typed = new APITimeoutError({ options: { retryable } });\n } else if (error instanceof APIError) {\n typed = error;\n } else if (retryable) {\n typed = new APIConnectionError({ message: toMessage(error), options: { retryable } });\n } else {\n throw error;\n }\n if (typed.retryable && attempt < retries) continue;\n throw typed;\n }\n }\n if (!cancelled) controller.close();\n // Success or clean barge-in: the turn is done either way.\n emitTurnComplete(cancelled);\n } catch (error) {\n // Barge-in never reaches here (handled above); a real failure errors the stream and does\n // not fire onTurnComplete — same contract as the in-process generator.\n if (cancelled) {\n emitTurnComplete(true);\n return;\n }\n controller.error(error);\n }\n },\n cancel: () => {\n cancelled = true;\n currentAbortController?.abort();\n },\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -387,7 +387,7 @@ var MastraVoiceAgent = class extends _livekit_agents.voice.Agent {
|
|
|
387
387
|
extra: { [require_turn_metrics.VOICE_TURN_METADATA]: identity }
|
|
388
388
|
}
|
|
389
389
|
};
|
|
390
|
-
|
|
390
|
+
return _livekit_agents.FlushSentinel;
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
};
|
|
@@ -459,7 +459,7 @@ async function mastraLLMNode(agent, chatCtx, toolCtx, modelSettings) {
|
|
|
459
459
|
if (!source) return null;
|
|
460
460
|
return mapVoiceStream(source, (chunk) => {
|
|
461
461
|
if (typeof chunk === "object" && chunk.delta?.extra?.["mastra.voice.flush"] === true) return _livekit_agents.FlushSentinel;
|
|
462
|
-
|
|
462
|
+
return chunk;
|
|
463
463
|
});
|
|
464
464
|
}
|
|
465
465
|
function speechResult(handle, outcome) {
|
|
@@ -866,4 +866,4 @@ Object.defineProperty(exports, "observeVoiceSession", {
|
|
|
866
866
|
}
|
|
867
867
|
});
|
|
868
868
|
|
|
869
|
-
//# sourceMappingURL=remote-
|
|
869
|
+
//# sourceMappingURL=remote-DO33RaMO.cjs.map
|