@elizaos/plugin-streaming 2.0.0-beta.1 → 2.0.11-beta.7

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.
@@ -1,5 +1,9 @@
1
1
  import { spawn } from "node:child_process";
2
- import { logger, sanitizeSpeechText } from "@elizaos/core";
2
+ import {
3
+ logger,
4
+ ModelType,
5
+ sanitizeSpeechText
6
+ } from "@elizaos/core";
3
7
  const TAG = "[TtsStreamBridge]";
4
8
  const SAMPLE_RATE = 24e3;
5
9
  const CHANNELS = 1;
@@ -8,6 +12,12 @@ const CHUNK_MS = 50;
8
12
  const CHUNK_BYTES = SAMPLE_RATE * BYTES_PER_SAMPLE * CHANNELS * CHUNK_MS / 1e3;
9
13
  const ELEVENLABS_TIMEOUT_MS = 2e4;
10
14
  const OPENAI_TIMEOUT_MS = 2e4;
15
+ const LOCAL_TTS_PROVIDER_IDS = [
16
+ "eliza-local-inference",
17
+ "capacitor-llama",
18
+ "eliza-device-bridge",
19
+ "eliza-aosp-llama"
20
+ ];
11
21
  class TtsStreamBridge {
12
22
  writeStream = null;
13
23
  tickTimer = null;
@@ -54,18 +64,18 @@ class TtsStreamBridge {
54
64
  logger.warn(`${TAG} Cannot speak \u2014 not attached to FFmpeg`);
55
65
  return false;
56
66
  }
57
- const speakableText = sanitizeSpeechText(text);
67
+ const speakableText = config.provider === "local-inference" ? sanitizeLocalInferenceSpeechText(text) : sanitizeSpeechText(text);
58
68
  if (!speakableText) return false;
59
69
  try {
60
70
  logger.info(
61
71
  `${TAG} Generating TTS (${config.provider}, ${speakableText.length} chars)`
62
72
  );
63
- const mp3 = await this.generateTts(speakableText, config);
64
- if (!mp3 || mp3.length === 0) {
73
+ const audio = await this.generateTts(speakableText, config);
74
+ if (!audio || audio.length === 0) {
65
75
  logger.warn(`${TAG} TTS returned empty audio`);
66
76
  return false;
67
77
  }
68
- const pcm = await this.decodeMp3ToPcm(mp3);
78
+ const pcm = await this.decodeAudioToPcm(audio);
69
79
  if (!pcm || pcm.length === 0) {
70
80
  logger.warn(`${TAG} PCM decode returned empty buffer`);
71
81
  return false;
@@ -115,10 +125,30 @@ class TtsStreamBridge {
115
125
  return this.generateOpenai(text, config);
116
126
  case "edge":
117
127
  return this.generateEdge(text, config);
128
+ case "local-inference":
129
+ return this.generateLocalInference(text, config);
118
130
  default:
119
131
  throw new Error(`Unknown TTS provider: ${config.provider}`);
120
132
  }
121
133
  }
134
+ async generateLocalInference(text, config) {
135
+ const runtime = config.runtime;
136
+ if (!runtime) {
137
+ throw new Error(
138
+ "Local inference TEXT_TO_SPEECH handler is not available"
139
+ );
140
+ }
141
+ const audio = await useLocalInferenceTts(runtime, text);
142
+ if (audio instanceof Uint8Array) {
143
+ return Buffer.from(audio.buffer, audio.byteOffset, audio.byteLength);
144
+ }
145
+ if (audio instanceof ArrayBuffer) {
146
+ return Buffer.from(audio);
147
+ }
148
+ throw new Error(
149
+ "Local inference TEXT_TO_SPEECH returned invalid audio bytes"
150
+ );
151
+ }
122
152
  async generateElevenlabs(text, config) {
123
153
  const el = config.elevenlabs;
124
154
  if (!el?.apiKey) throw new Error("ElevenLabs API key not available");
@@ -199,9 +229,9 @@ class TtsStreamBridge {
199
229
  );
200
230
  }
201
231
  }
202
- // ── MP3 → PCM decode ──────────────────────────────────────────────────
203
- /** Decode MP3 audio to raw s16le PCM using an FFmpeg subprocess. */
204
- decodeMp3ToPcm(mp3) {
232
+ // ── Encoded audio → PCM decode ─────────────────────────────────────────
233
+ /** Decode provider audio (WAV, MP3, etc.) to raw s16le PCM using FFmpeg. */
234
+ decodeAudioToPcm(audio) {
205
235
  return new Promise((resolve, reject) => {
206
236
  const proc = spawn(
207
237
  "ffmpeg",
@@ -228,7 +258,7 @@ class TtsStreamBridge {
228
258
  }
229
259
  });
230
260
  proc.on("error", reject);
231
- proc.stdin?.write(mp3);
261
+ proc.stdin?.write(audio);
232
262
  proc.stdin?.end();
233
263
  });
234
264
  }
@@ -240,18 +270,75 @@ function fetchWithTimeout(url, init, timeoutMs) {
240
270
  () => clearTimeout(timer)
241
271
  );
242
272
  }
273
+ async function useLocalInferenceTts(runtime, text) {
274
+ let lastError;
275
+ for (const provider of LOCAL_TTS_PROVIDER_IDS) {
276
+ try {
277
+ return await runtime.useModel(
278
+ ModelType.TEXT_TO_SPEECH,
279
+ { text },
280
+ provider
281
+ );
282
+ } catch (err) {
283
+ lastError = err;
284
+ if (!isMissingProviderError(err)) {
285
+ throw err;
286
+ }
287
+ }
288
+ }
289
+ if (lastError instanceof Error) {
290
+ throw lastError;
291
+ }
292
+ throw new Error("No local-inference TEXT_TO_SPEECH provider is registered");
293
+ }
294
+ function isMissingProviderError(error) {
295
+ return error instanceof Error && /No handler found for delegate type: TEXT_TO_SPEECH/.test(error.message);
296
+ }
297
+ function sanitizeLocalInferenceSpeechText(input) {
298
+ let text = input.normalize("NFKC");
299
+ text = text.replace(/<think\b[^>]*>[\s\S]*?(?:<\/think>|$)/gi, " ");
300
+ text = text.replace(
301
+ /<(analysis|reasoning|tool_calls?|tools?)\b[^>]*>[\s\S]*?(?:<\/\1>|$)/gi,
302
+ " "
303
+ );
304
+ text = text.replace(/```[\s\S]*?```/g, " ");
305
+ text = text.replace(/`([^`]+)`/g, "$1");
306
+ text = text.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
307
+ text = text.replace(/<[^>\n]+>/g, " ");
308
+ text = text.replace(/\bhttps?:\/\/\S+/gi, " ");
309
+ return text.replace(/\s+/g, " ").trim();
310
+ }
243
311
  function isRedactedSecret(val) {
244
312
  return /^\*+$/.test(val) || val === "REDACTED" || val === "[REDACTED]";
245
313
  }
246
- function resolveTtsConfig(ttsConfig) {
314
+ function hasLocalInferenceTtsHandler(runtime) {
315
+ const registrations = runtime?.models;
316
+ if (registrations instanceof Map) {
317
+ const handlers = registrations.get(ModelType.TEXT_TO_SPEECH);
318
+ return Array.isArray(handlers) && handlers.some((entry) => {
319
+ const provider = entry.provider;
320
+ return typeof provider === "string" && LOCAL_TTS_PROVIDER_IDS.includes(
321
+ provider
322
+ );
323
+ });
324
+ }
325
+ return false;
326
+ }
327
+ function resolveTtsConfig(ttsConfig, runtime) {
247
328
  if (!ttsConfig) return null;
248
329
  const preferredProvider = ttsConfig.provider || "elevenlabs";
249
330
  const providers = [preferredProvider];
331
+ if (!providers.includes("local-inference")) providers.push("local-inference");
250
332
  if (!providers.includes("elevenlabs")) providers.push("elevenlabs");
251
333
  if (!providers.includes("openai")) providers.push("openai");
252
- if (!providers.includes("edge")) providers.push("edge");
253
334
  for (const provider of providers) {
254
335
  switch (provider) {
336
+ case "local-inference": {
337
+ if (preferredProvider === "local-inference" || ttsConfig.provider === "local-inference") {
338
+ return hasLocalInferenceTtsHandler(runtime) ? { provider: "local-inference", runtime } : null;
339
+ }
340
+ break;
341
+ }
255
342
  case "elevenlabs": {
256
343
  const el = ttsConfig.elevenlabs;
257
344
  const apiKey = resolveKey(el?.apiKey, "ELEVENLABS_API_KEY");
@@ -284,12 +371,15 @@ function resolveTtsConfig(ttsConfig) {
284
371
  break;
285
372
  }
286
373
  case "edge": {
287
- return {
288
- provider: "edge",
289
- edge: {
290
- voice: ttsConfig.edge?.voice || "en-US-AriaNeural"
291
- }
292
- };
374
+ if (preferredProvider === "edge") {
375
+ return {
376
+ provider: "edge",
377
+ edge: {
378
+ voice: ttsConfig.edge?.voice || "en-US-AriaNeural"
379
+ }
380
+ };
381
+ }
382
+ break;
293
383
  }
294
384
  }
295
385
  }
@@ -310,11 +400,11 @@ function resolveKey(configKey, envVar) {
310
400
  }
311
401
  return null;
312
402
  }
313
- function getTtsProviderStatus(ttsConfig) {
314
- const resolved = resolveTtsConfig(ttsConfig);
403
+ function getTtsProviderStatus(ttsConfig, runtime) {
404
+ const resolved = resolveTtsConfig(ttsConfig, runtime);
315
405
  return {
316
406
  configuredProvider: ttsConfig?.provider || null,
317
- hasApiKey: resolved ? resolved.provider !== "edge" : false,
407
+ hasApiKey: resolved ? resolved.provider !== "edge" && resolved.provider !== "local-inference" : false,
318
408
  resolvedProvider: resolved?.provider || null
319
409
  };
320
410
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/tts-stream-bridge.ts"],"sourcesContent":["/**\n * TTS Stream Bridge — generates TTS audio server-side and pipes PCM data\n * into FFmpeg's audio track for RTMP streaming.\n *\n * Uses pipe:3 (a 4th stdio fd) as the audio input to FFmpeg. Writes PCM\n * silence when idle and decoded TTS audio when speaking. This avoids FIFO\n * complexity and works cross-platform.\n *\n * @module services/tts-stream-bridge\n */\n\nimport { spawn } from \"node:child_process\";\nimport type { Writable } from \"node:stream\";\nimport { logger, sanitizeSpeechText } from \"@elizaos/core\";\n\nexport type TtsProvider = \"elevenlabs\" | \"openai\" | \"edge\" | (string & {});\n\nexport type TtsConfig = {\n enabled?: boolean;\n provider?: TtsProvider;\n elevenlabs?: {\n apiKey?: string;\n voiceId?: string;\n modelId?: string;\n voiceSettings?: Record<string, unknown>;\n };\n openai?: {\n apiKey?: string;\n model?: string;\n voice?: string;\n };\n edge?: {\n voice?: string;\n };\n};\n\nconst TAG = \"[TtsStreamBridge]\";\n\n// PCM format: signed 16-bit little-endian, 24 kHz, mono\nconst SAMPLE_RATE = 24000;\nconst CHANNELS = 1;\nconst BYTES_PER_SAMPLE = 2;\nconst CHUNK_MS = 50;\n/** Bytes per tick: 24000 * 2 * 1 * 50/1000 = 2400 */\nconst CHUNK_BYTES =\n (SAMPLE_RATE * BYTES_PER_SAMPLE * CHANNELS * CHUNK_MS) / 1000;\n\nconst ELEVENLABS_TIMEOUT_MS = 20_000;\nconst OPENAI_TIMEOUT_MS = 20_000;\n\n/** Resolved TTS configuration for a speak() call. */\nexport interface ResolvedTtsConfig {\n provider: TtsProvider;\n elevenlabs?: {\n apiKey: string;\n voiceId: string;\n modelId: string;\n voiceSettings?: Record<string, unknown>;\n };\n openai?: {\n apiKey: string;\n model: string;\n voice: string;\n };\n edge?: {\n voice: string;\n };\n}\n\n/** Public interface for the TTS stream bridge. */\nexport interface ITtsStreamBridge {\n attach(stream: Writable): void;\n detach(): void;\n isAttached(): boolean;\n isSpeaking(): boolean;\n speak(text: string, config: ResolvedTtsConfig): Promise<boolean>;\n}\n\nclass TtsStreamBridge implements ITtsStreamBridge {\n private writeStream: Writable | null = null;\n private tickTimer: ReturnType<typeof setInterval> | null = null;\n private pcmQueue: Buffer[] = [];\n private _speaking = false;\n private silenceChunk: Buffer;\n\n constructor() {\n // Pre-allocate a silence chunk (all zeros = silence in s16le)\n this.silenceChunk = Buffer.alloc(CHUNK_BYTES, 0);\n }\n\n /** Attach the bridge to an FFmpeg stdio pipe (pipe:3). */\n attach(stream: Writable): void {\n this.detach();\n this.writeStream = stream;\n stream.on(\"error\", (err) => {\n logger.warn(`${TAG} Write stream error: ${err.message}`);\n });\n // Start the tick loop — writes PCM chunks every CHUNK_MS\n this.tickTimer = setInterval(() => this.tick(), CHUNK_MS);\n logger.info(`${TAG} Attached to FFmpeg audio pipe`);\n }\n\n /** Detach from FFmpeg — stops tick loop and clears queue. */\n detach(): void {\n if (this.tickTimer) {\n clearInterval(this.tickTimer);\n this.tickTimer = null;\n }\n this.writeStream = null;\n this.pcmQueue = [];\n this._speaking = false;\n }\n\n /** Whether the bridge is currently attached to an FFmpeg process. */\n isAttached(): boolean {\n return this.writeStream !== null;\n }\n\n /** Whether TTS audio is currently being played. */\n isSpeaking(): boolean {\n return this._speaking;\n }\n\n /**\n * Generate TTS for the given text and queue PCM audio for playback.\n * Non-blocking — queues audio and returns immediately after generation.\n */\n async speak(text: string, config: ResolvedTtsConfig): Promise<boolean> {\n if (!this.writeStream) {\n logger.warn(`${TAG} Cannot speak — not attached to FFmpeg`);\n return false;\n }\n\n const speakableText = sanitizeSpeechText(text);\n if (!speakableText) return false;\n\n try {\n logger.info(\n `${TAG} Generating TTS (${config.provider}, ${speakableText.length} chars)`,\n );\n const mp3 = await this.generateTts(speakableText, config);\n if (!mp3 || mp3.length === 0) {\n logger.warn(`${TAG} TTS returned empty audio`);\n return false;\n }\n\n const pcm = await this.decodeMp3ToPcm(mp3);\n if (!pcm || pcm.length === 0) {\n logger.warn(`${TAG} PCM decode returned empty buffer`);\n return false;\n }\n\n // Split PCM into CHUNK_BYTES-sized buffers for smooth playback\n const chunks: Buffer[] = [];\n for (let i = 0; i < pcm.length; i += CHUNK_BYTES) {\n const end = Math.min(i + CHUNK_BYTES, pcm.length);\n const chunk = Buffer.alloc(CHUNK_BYTES, 0);\n pcm.copy(chunk, 0, i, end);\n chunks.push(chunk);\n }\n\n this.pcmQueue.push(...chunks);\n this._speaking = true;\n logger.info(\n `${TAG} Queued ${chunks.length} PCM chunks (${(pcm.length / SAMPLE_RATE / BYTES_PER_SAMPLE).toFixed(1)}s)`,\n );\n return true;\n } catch (err) {\n logger.error(`${TAG} TTS generation failed: ${String(err)}`);\n return false;\n }\n }\n\n /** Called every CHUNK_MS — writes next PCM chunk (TTS or silence) to FFmpeg. */\n private tick(): void {\n if (!this.writeStream) return;\n\n let chunk: Buffer;\n if (this.pcmQueue.length > 0) {\n chunk = this.pcmQueue.shift() as Buffer;\n if (this.pcmQueue.length === 0) {\n this._speaking = false;\n logger.info(`${TAG} Finished speaking`);\n }\n } else {\n chunk = this.silenceChunk;\n }\n\n try {\n this.writeStream.write(chunk);\n } catch {\n // Stream may have been closed — detach will handle cleanup\n }\n }\n\n // ── TTS generation ─────────────────────────────────────────────────────\n\n private async generateTts(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n switch (config.provider) {\n case \"elevenlabs\":\n return this.generateElevenlabs(text, config);\n case \"openai\":\n return this.generateOpenai(text, config);\n case \"edge\":\n return this.generateEdge(text, config);\n default:\n throw new Error(`Unknown TTS provider: ${config.provider}`);\n }\n }\n\n private async generateElevenlabs(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n const el = config.elevenlabs;\n if (!el?.apiKey) throw new Error(\"ElevenLabs API key not available\");\n\n const voiceId = el.voiceId || \"EXAVITQu4vr4xnSDxMaL\";\n const modelId = el.modelId || \"eleven_flash_v2_5\";\n const url = `https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(voiceId)}/stream?output_format=mp3_22050_32`;\n\n const payload: Record<string, unknown> = {\n text,\n model_id: modelId,\n };\n if (el.voiceSettings && Object.keys(el.voiceSettings).length > 0) {\n payload.voice_settings = el.voiceSettings;\n }\n\n const resp = await fetchWithTimeout(\n url,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": el.apiKey,\n \"Content-Type\": \"application/json\",\n Accept: \"audio/mpeg\",\n },\n body: JSON.stringify(payload),\n },\n ELEVENLABS_TIMEOUT_MS,\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`ElevenLabs ${resp.status}: ${body.slice(0, 200)}`);\n }\n\n return Buffer.from(await resp.arrayBuffer());\n }\n\n private async generateOpenai(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n const oai = config.openai;\n if (!oai?.apiKey) throw new Error(\"OpenAI API key not available\");\n\n const model = oai.model || \"tts-1\";\n const voice = oai.voice || \"alloy\";\n\n const resp = await fetchWithTimeout(\n \"https://api.openai.com/v1/audio/speech\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${oai.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n input: text,\n voice,\n response_format: \"mp3\",\n }),\n },\n OPENAI_TIMEOUT_MS,\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`OpenAI TTS ${resp.status}: ${body.slice(0, 200)}`);\n }\n\n return Buffer.from(await resp.arrayBuffer());\n }\n\n private async generateEdge(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n // Edge TTS requires node-edge-tts package — optional dependency\n try {\n // Use a variable so Vite's static analysis doesn't try to resolve this optional dep\n const edgeTtsModule = \"node-edge-tts\";\n const { MsEdgeTTS } = await import(edgeTtsModule);\n const tts = new MsEdgeTTS();\n const voice = config.edge?.voice || \"en-US-AriaNeural\";\n await tts.setMetadata(voice, \"audio-24khz-48kbitrate-mono-mp3\");\n const readable = tts.toStream(text);\n\n return new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n readable.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n readable.on(\"end\", () => resolve(Buffer.concat(chunks)));\n readable.on(\"error\", reject);\n });\n } catch (err) {\n throw new Error(\n `Edge TTS failed (node-edge-tts may not be installed): ${String(err)}`,\n );\n }\n }\n\n // ── MP3 → PCM decode ──────────────────────────────────────────────────\n\n /** Decode MP3 audio to raw s16le PCM using an FFmpeg subprocess. */\n private decodeMp3ToPcm(mp3: Buffer): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const proc = spawn(\n \"ffmpeg\",\n [\n \"-i\",\n \"pipe:0\",\n \"-f\",\n \"s16le\",\n \"-ar\",\n String(SAMPLE_RATE),\n \"-ac\",\n String(CHANNELS),\n \"pipe:1\",\n ],\n { stdio: [\"pipe\", \"pipe\", \"ignore\"] },\n );\n\n const chunks: Buffer[] = [];\n proc.stdout?.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve(Buffer.concat(chunks));\n } else {\n reject(new Error(`FFmpeg decode exited with code ${code}`));\n }\n });\n proc.on(\"error\", reject);\n proc.stdin?.write(mp3);\n proc.stdin?.end();\n });\n }\n}\n\n// ── Helper ────────────────────────────────────────────────────────────────\n\nfunction fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\n// ── Config resolution ─────────────────────────────────────────────────────\n\n/** Helper to check if a string looks like a redacted secret placeholder. */\nfunction isRedactedSecret(val: string): boolean {\n return /^\\*+$/.test(val) || val === \"REDACTED\" || val === \"[REDACTED]\";\n}\n\n/**\n * Resolve TTS configuration from eliza config, finding the best available\n * provider with valid API keys.\n */\nexport function resolveTtsConfig(\n ttsConfig: TtsConfig | undefined,\n): ResolvedTtsConfig | null {\n if (!ttsConfig) return null;\n\n const preferredProvider = ttsConfig.provider || \"elevenlabs\";\n\n // Try providers in preference order: configured → elevenlabs → openai → edge\n const providers: TtsProvider[] = [preferredProvider];\n if (!providers.includes(\"elevenlabs\")) providers.push(\"elevenlabs\");\n if (!providers.includes(\"openai\")) providers.push(\"openai\");\n if (!providers.includes(\"edge\")) providers.push(\"edge\");\n\n for (const provider of providers) {\n switch (provider) {\n case \"elevenlabs\": {\n const el = ttsConfig.elevenlabs;\n const apiKey = resolveKey(el?.apiKey, \"ELEVENLABS_API_KEY\");\n if (apiKey) {\n return {\n provider: \"elevenlabs\",\n elevenlabs: {\n apiKey,\n voiceId: el?.voiceId || \"EXAVITQu4vr4xnSDxMaL\",\n modelId: el?.modelId || \"eleven_flash_v2_5\",\n voiceSettings: el?.voiceSettings\n ? { ...el.voiceSettings }\n : undefined,\n },\n };\n }\n break;\n }\n case \"openai\": {\n const oai = ttsConfig.openai;\n const apiKey = resolveKey(oai?.apiKey, \"OPENAI_API_KEY\");\n if (apiKey) {\n return {\n provider: \"openai\",\n openai: {\n apiKey,\n model: oai?.model || \"tts-1\",\n voice: oai?.voice || \"alloy\",\n },\n };\n }\n break;\n }\n case \"edge\": {\n // Edge TTS always works (no API key needed)\n return {\n provider: \"edge\",\n edge: {\n voice: ttsConfig.edge?.voice || \"en-US-AriaNeural\",\n },\n };\n }\n }\n }\n\n return null;\n}\n\nfunction resolveKey(\n configKey: string | undefined,\n envVar: string,\n): string | null {\n const ck = configKey?.trim();\n if (ck && !isRedactedSecret(ck)) return ck;\n const ev = process.env[envVar]?.trim();\n if (ev && !isRedactedSecret(ev)) return ev;\n\n const explicitCloudTts = process.env.ELIZAOS_CLOUD_USE_TTS === \"true\";\n const legacyCloudTts =\n process.env.ELIZAOS_CLOUD_USE_TTS === undefined &&\n process.env.ELIZAOS_CLOUD_ENABLED === \"true\" &&\n process.env.ELIZA_CLOUD_TTS_DISABLED !== \"true\";\n if (explicitCloudTts || legacyCloudTts) {\n const cloudKey = process.env.ELIZAOS_CLOUD_API_KEY?.trim();\n if (cloudKey && !isRedactedSecret(cloudKey)) {\n return cloudKey;\n }\n }\n\n return null;\n}\n\n/**\n * Get a summary of available TTS providers and their status.\n */\nexport function getTtsProviderStatus(ttsConfig: TtsConfig | undefined): {\n configuredProvider: string | null;\n hasApiKey: boolean;\n resolvedProvider: string | null;\n} {\n const resolved = resolveTtsConfig(ttsConfig);\n return {\n configuredProvider: ttsConfig?.provider || null,\n hasApiKey: resolved ? resolved.provider !== \"edge\" : false,\n resolvedProvider: resolved?.provider || null,\n };\n}\n\n// Module singleton\nexport const ttsStreamBridge = new TtsStreamBridge();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/services/tts-stream-bridge.ts"],"sourcesContent":["/**\n * TTS Stream Bridge — generates TTS audio server-side and pipes PCM data\n * into FFmpeg's audio track for RTMP streaming.\n *\n * Uses pipe:3 (a 4th stdio fd) as the audio input to FFmpeg. Writes PCM\n * silence when idle and decoded TTS audio when speaking. This avoids FIFO\n * complexity and works cross-platform.\n *\n * @module services/tts-stream-bridge\n */\n\nimport { spawn } from \"node:child_process\";\nimport type { Writable } from \"node:stream\";\nimport {\n type IAgentRuntime,\n logger,\n ModelType,\n sanitizeSpeechText,\n} from \"@elizaos/core\";\n\nexport type TtsProvider =\n | \"local-inference\"\n | \"elevenlabs\"\n | \"openai\"\n | \"edge\"\n | (string & {});\n\nexport type TtsConfig = {\n enabled?: boolean;\n provider?: TtsProvider;\n elevenlabs?: {\n apiKey?: string;\n voiceId?: string;\n modelId?: string;\n voiceSettings?: Record<string, unknown>;\n };\n openai?: {\n apiKey?: string;\n model?: string;\n voice?: string;\n };\n edge?: {\n voice?: string;\n };\n};\n\nconst TAG = \"[TtsStreamBridge]\";\n\n// PCM format: signed 16-bit little-endian, 24 kHz, mono\nconst SAMPLE_RATE = 24000;\nconst CHANNELS = 1;\nconst BYTES_PER_SAMPLE = 2;\nconst CHUNK_MS = 50;\n/** Bytes per tick: 24000 * 2 * 1 * 50/1000 = 2400 */\nconst CHUNK_BYTES =\n (SAMPLE_RATE * BYTES_PER_SAMPLE * CHANNELS * CHUNK_MS) / 1000;\n\nconst ELEVENLABS_TIMEOUT_MS = 20_000;\nconst OPENAI_TIMEOUT_MS = 20_000;\nconst LOCAL_TTS_PROVIDER_IDS = [\n \"eliza-local-inference\",\n \"capacitor-llama\",\n \"eliza-device-bridge\",\n \"eliza-aosp-llama\",\n] as const;\n\n/** Resolved TTS configuration for a speak() call. */\nexport interface ResolvedTtsConfig {\n provider: TtsProvider;\n runtime?: IAgentRuntime;\n elevenlabs?: {\n apiKey: string;\n voiceId: string;\n modelId: string;\n voiceSettings?: Record<string, unknown>;\n };\n openai?: {\n apiKey: string;\n model: string;\n voice: string;\n };\n edge?: {\n voice: string;\n };\n}\n\n/** Public interface for the TTS stream bridge. */\nexport interface ITtsStreamBridge {\n attach(stream: Writable): void;\n detach(): void;\n isAttached(): boolean;\n isSpeaking(): boolean;\n speak(text: string, config: ResolvedTtsConfig): Promise<boolean>;\n}\n\nclass TtsStreamBridge implements ITtsStreamBridge {\n private writeStream: Writable | null = null;\n private tickTimer: ReturnType<typeof setInterval> | null = null;\n private pcmQueue: Buffer[] = [];\n private _speaking = false;\n private silenceChunk: Buffer;\n\n constructor() {\n // Pre-allocate a silence chunk (all zeros = silence in s16le)\n this.silenceChunk = Buffer.alloc(CHUNK_BYTES, 0);\n }\n\n /** Attach the bridge to an FFmpeg stdio pipe (pipe:3). */\n attach(stream: Writable): void {\n this.detach();\n this.writeStream = stream;\n stream.on(\"error\", (err) => {\n logger.warn(`${TAG} Write stream error: ${err.message}`);\n });\n // Start the tick loop — writes PCM chunks every CHUNK_MS\n this.tickTimer = setInterval(() => this.tick(), CHUNK_MS);\n logger.info(`${TAG} Attached to FFmpeg audio pipe`);\n }\n\n /** Detach from FFmpeg — stops tick loop and clears queue. */\n detach(): void {\n if (this.tickTimer) {\n clearInterval(this.tickTimer);\n this.tickTimer = null;\n }\n this.writeStream = null;\n this.pcmQueue = [];\n this._speaking = false;\n }\n\n /** Whether the bridge is currently attached to an FFmpeg process. */\n isAttached(): boolean {\n return this.writeStream !== null;\n }\n\n /** Whether TTS audio is currently being played. */\n isSpeaking(): boolean {\n return this._speaking;\n }\n\n /**\n * Generate TTS for the given text and queue PCM audio for playback.\n * Non-blocking — queues audio and returns immediately after generation.\n */\n async speak(text: string, config: ResolvedTtsConfig): Promise<boolean> {\n if (!this.writeStream) {\n logger.warn(`${TAG} Cannot speak — not attached to FFmpeg`);\n return false;\n }\n\n const speakableText =\n config.provider === \"local-inference\"\n ? sanitizeLocalInferenceSpeechText(text)\n : sanitizeSpeechText(text);\n if (!speakableText) return false;\n\n try {\n logger.info(\n `${TAG} Generating TTS (${config.provider}, ${speakableText.length} chars)`,\n );\n const audio = await this.generateTts(speakableText, config);\n if (!audio || audio.length === 0) {\n logger.warn(`${TAG} TTS returned empty audio`);\n return false;\n }\n\n const pcm = await this.decodeAudioToPcm(audio);\n if (!pcm || pcm.length === 0) {\n logger.warn(`${TAG} PCM decode returned empty buffer`);\n return false;\n }\n\n // Split PCM into CHUNK_BYTES-sized buffers for smooth playback\n const chunks: Buffer[] = [];\n for (let i = 0; i < pcm.length; i += CHUNK_BYTES) {\n const end = Math.min(i + CHUNK_BYTES, pcm.length);\n const chunk = Buffer.alloc(CHUNK_BYTES, 0);\n pcm.copy(chunk, 0, i, end);\n chunks.push(chunk);\n }\n\n this.pcmQueue.push(...chunks);\n this._speaking = true;\n logger.info(\n `${TAG} Queued ${chunks.length} PCM chunks (${(pcm.length / SAMPLE_RATE / BYTES_PER_SAMPLE).toFixed(1)}s)`,\n );\n return true;\n } catch (err) {\n logger.error(`${TAG} TTS generation failed: ${String(err)}`);\n return false;\n }\n }\n\n /** Called every CHUNK_MS — writes next PCM chunk (TTS or silence) to FFmpeg. */\n private tick(): void {\n if (!this.writeStream) return;\n\n let chunk: Buffer;\n if (this.pcmQueue.length > 0) {\n chunk = this.pcmQueue.shift() as Buffer;\n if (this.pcmQueue.length === 0) {\n this._speaking = false;\n logger.info(`${TAG} Finished speaking`);\n }\n } else {\n chunk = this.silenceChunk;\n }\n\n try {\n this.writeStream.write(chunk);\n } catch {\n // Stream may have been closed — detach will handle cleanup\n }\n }\n\n // ── TTS generation ─────────────────────────────────────────────────────\n\n private async generateTts(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n switch (config.provider) {\n case \"elevenlabs\":\n return this.generateElevenlabs(text, config);\n case \"openai\":\n return this.generateOpenai(text, config);\n case \"edge\":\n return this.generateEdge(text, config);\n case \"local-inference\":\n return this.generateLocalInference(text, config);\n default:\n throw new Error(`Unknown TTS provider: ${config.provider}`);\n }\n }\n\n private async generateLocalInference(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n const runtime = config.runtime;\n if (!runtime) {\n throw new Error(\n \"Local inference TEXT_TO_SPEECH handler is not available\",\n );\n }\n const audio = await useLocalInferenceTts(runtime, text);\n if (audio instanceof Uint8Array) {\n return Buffer.from(audio.buffer, audio.byteOffset, audio.byteLength);\n }\n if (audio instanceof ArrayBuffer) {\n return Buffer.from(audio);\n }\n throw new Error(\n \"Local inference TEXT_TO_SPEECH returned invalid audio bytes\",\n );\n }\n\n private async generateElevenlabs(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n const el = config.elevenlabs;\n if (!el?.apiKey) throw new Error(\"ElevenLabs API key not available\");\n\n const voiceId = el.voiceId || \"EXAVITQu4vr4xnSDxMaL\";\n const modelId = el.modelId || \"eleven_flash_v2_5\";\n const url = `https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(voiceId)}/stream?output_format=mp3_22050_32`;\n\n const payload: Record<string, unknown> = {\n text,\n model_id: modelId,\n };\n if (el.voiceSettings && Object.keys(el.voiceSettings).length > 0) {\n payload.voice_settings = el.voiceSettings;\n }\n\n const resp = await fetchWithTimeout(\n url,\n {\n method: \"POST\",\n headers: {\n \"xi-api-key\": el.apiKey,\n \"Content-Type\": \"application/json\",\n Accept: \"audio/mpeg\",\n },\n body: JSON.stringify(payload),\n },\n ELEVENLABS_TIMEOUT_MS,\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`ElevenLabs ${resp.status}: ${body.slice(0, 200)}`);\n }\n\n return Buffer.from(await resp.arrayBuffer());\n }\n\n private async generateOpenai(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n const oai = config.openai;\n if (!oai?.apiKey) throw new Error(\"OpenAI API key not available\");\n\n const model = oai.model || \"tts-1\";\n const voice = oai.voice || \"alloy\";\n\n const resp = await fetchWithTimeout(\n \"https://api.openai.com/v1/audio/speech\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${oai.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n input: text,\n voice,\n response_format: \"mp3\",\n }),\n },\n OPENAI_TIMEOUT_MS,\n );\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n throw new Error(`OpenAI TTS ${resp.status}: ${body.slice(0, 200)}`);\n }\n\n return Buffer.from(await resp.arrayBuffer());\n }\n\n private async generateEdge(\n text: string,\n config: ResolvedTtsConfig,\n ): Promise<Buffer> {\n // Edge TTS requires node-edge-tts package — optional dependency\n try {\n // Use a variable so Vite's static analysis doesn't try to resolve this optional dep\n const edgeTtsModule = \"node-edge-tts\";\n const { MsEdgeTTS } = await import(edgeTtsModule);\n const tts = new MsEdgeTTS();\n const voice = config.edge?.voice || \"en-US-AriaNeural\";\n await tts.setMetadata(voice, \"audio-24khz-48kbitrate-mono-mp3\");\n const readable = tts.toStream(text);\n\n return new Promise<Buffer>((resolve, reject) => {\n const chunks: Buffer[] = [];\n readable.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n readable.on(\"end\", () => resolve(Buffer.concat(chunks)));\n readable.on(\"error\", reject);\n });\n } catch (err) {\n throw new Error(\n `Edge TTS failed (node-edge-tts may not be installed): ${String(err)}`,\n );\n }\n }\n\n // ── Encoded audio → PCM decode ─────────────────────────────────────────\n\n /** Decode provider audio (WAV, MP3, etc.) to raw s16le PCM using FFmpeg. */\n private decodeAudioToPcm(audio: Buffer): Promise<Buffer> {\n return new Promise<Buffer>((resolve, reject) => {\n const proc = spawn(\n \"ffmpeg\",\n [\n \"-i\",\n \"pipe:0\",\n \"-f\",\n \"s16le\",\n \"-ar\",\n String(SAMPLE_RATE),\n \"-ac\",\n String(CHANNELS),\n \"pipe:1\",\n ],\n { stdio: [\"pipe\", \"pipe\", \"ignore\"] },\n );\n\n const chunks: Buffer[] = [];\n proc.stdout?.on(\"data\", (chunk: Buffer) => chunks.push(chunk));\n proc.on(\"close\", (code) => {\n if (code === 0) {\n resolve(Buffer.concat(chunks));\n } else {\n reject(new Error(`FFmpeg decode exited with code ${code}`));\n }\n });\n proc.on(\"error\", reject);\n proc.stdin?.write(audio);\n proc.stdin?.end();\n });\n }\n}\n\n// ── Helper ────────────────────────────────────────────────────────────────\n\nfunction fetchWithTimeout(\n url: string,\n init: RequestInit,\n timeoutMs: number,\n): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n return fetch(url, { ...init, signal: controller.signal }).finally(() =>\n clearTimeout(timer),\n );\n}\n\nasync function useLocalInferenceTts(\n runtime: IAgentRuntime,\n text: string,\n): Promise<Buffer | Uint8Array | ArrayBuffer> {\n let lastError: unknown;\n for (const provider of LOCAL_TTS_PROVIDER_IDS) {\n try {\n return await runtime.useModel(\n ModelType.TEXT_TO_SPEECH,\n { text },\n provider,\n );\n } catch (err) {\n lastError = err;\n if (!isMissingProviderError(err)) {\n throw err;\n }\n }\n }\n if (lastError instanceof Error) {\n throw lastError;\n }\n throw new Error(\"No local-inference TEXT_TO_SPEECH provider is registered\");\n}\n\nfunction isMissingProviderError(error: unknown): boolean {\n return (\n error instanceof Error &&\n /No handler found for delegate type: TEXT_TO_SPEECH/.test(error.message)\n );\n}\n\nfunction sanitizeLocalInferenceSpeechText(input: string): string {\n let text = input.normalize(\"NFKC\");\n text = text.replace(/<think\\b[^>]*>[\\s\\S]*?(?:<\\/think>|$)/gi, \" \");\n text = text.replace(\n /<(analysis|reasoning|tool_calls?|tools?)\\b[^>]*>[\\s\\S]*?(?:<\\/\\1>|$)/gi,\n \" \",\n );\n text = text.replace(/```[\\s\\S]*?```/g, \" \");\n text = text.replace(/`([^`]+)`/g, \"$1\");\n text = text.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\");\n text = text.replace(/<[^>\\n]+>/g, \" \");\n text = text.replace(/\\bhttps?:\\/\\/\\S+/gi, \" \");\n return text.replace(/\\s+/g, \" \").trim();\n}\n\n// ── Config resolution ─────────────────────────────────────────────────────\n\n/** Helper to check if a string looks like a redacted secret token. */\nfunction isRedactedSecret(val: string): boolean {\n return /^\\*+$/.test(val) || val === \"REDACTED\" || val === \"[REDACTED]\";\n}\n\nfunction hasLocalInferenceTtsHandler(\n runtime: IAgentRuntime | undefined,\n): boolean {\n const registrations = (runtime as { models?: unknown } | undefined)?.models;\n if (registrations instanceof Map) {\n const handlers = registrations.get(ModelType.TEXT_TO_SPEECH);\n return (\n Array.isArray(handlers) &&\n handlers.some((entry) => {\n const provider = (entry as { provider?: unknown }).provider;\n return (\n typeof provider === \"string\" &&\n LOCAL_TTS_PROVIDER_IDS.includes(\n provider as (typeof LOCAL_TTS_PROVIDER_IDS)[number],\n )\n );\n })\n );\n }\n return false;\n}\n\n/**\n * Resolve TTS configuration from eliza config, finding the best available\n * provider with valid API keys.\n */\nexport function resolveTtsConfig(\n ttsConfig: TtsConfig | undefined,\n runtime?: IAgentRuntime,\n): ResolvedTtsConfig | null {\n if (!ttsConfig) return null;\n\n const preferredProvider = ttsConfig.provider || \"elevenlabs\";\n\n // Try configured/cloud providers in preference order. Edge TTS is only\n // selected when explicitly configured; it is not an implicit local default.\n const providers: TtsProvider[] = [preferredProvider];\n if (!providers.includes(\"local-inference\")) providers.push(\"local-inference\");\n if (!providers.includes(\"elevenlabs\")) providers.push(\"elevenlabs\");\n if (!providers.includes(\"openai\")) providers.push(\"openai\");\n\n for (const provider of providers) {\n switch (provider) {\n case \"local-inference\": {\n if (\n preferredProvider === \"local-inference\" ||\n ttsConfig.provider === \"local-inference\"\n ) {\n return hasLocalInferenceTtsHandler(runtime)\n ? { provider: \"local-inference\", runtime }\n : null;\n }\n break;\n }\n case \"elevenlabs\": {\n const el = ttsConfig.elevenlabs;\n const apiKey = resolveKey(el?.apiKey, \"ELEVENLABS_API_KEY\");\n if (apiKey) {\n return {\n provider: \"elevenlabs\",\n elevenlabs: {\n apiKey,\n voiceId: el?.voiceId || \"EXAVITQu4vr4xnSDxMaL\",\n modelId: el?.modelId || \"eleven_flash_v2_5\",\n voiceSettings: el?.voiceSettings\n ? { ...el.voiceSettings }\n : undefined,\n },\n };\n }\n break;\n }\n case \"openai\": {\n const oai = ttsConfig.openai;\n const apiKey = resolveKey(oai?.apiKey, \"OPENAI_API_KEY\");\n if (apiKey) {\n return {\n provider: \"openai\",\n openai: {\n apiKey,\n model: oai?.model || \"tts-1\",\n voice: oai?.voice || \"alloy\",\n },\n };\n }\n break;\n }\n case \"edge\": {\n if (preferredProvider === \"edge\") {\n return {\n provider: \"edge\",\n edge: {\n voice: ttsConfig.edge?.voice || \"en-US-AriaNeural\",\n },\n };\n }\n break;\n }\n }\n }\n\n return null;\n}\n\nfunction resolveKey(\n configKey: string | undefined,\n envVar: string,\n): string | null {\n const ck = configKey?.trim();\n if (ck && !isRedactedSecret(ck)) return ck;\n const ev = process.env[envVar]?.trim();\n if (ev && !isRedactedSecret(ev)) return ev;\n\n const explicitCloudTts = process.env.ELIZAOS_CLOUD_USE_TTS === \"true\";\n const legacyCloudTts =\n process.env.ELIZAOS_CLOUD_USE_TTS === undefined &&\n process.env.ELIZAOS_CLOUD_ENABLED === \"true\" &&\n process.env.ELIZA_CLOUD_TTS_DISABLED !== \"true\";\n if (explicitCloudTts || legacyCloudTts) {\n const cloudKey = process.env.ELIZAOS_CLOUD_API_KEY?.trim();\n if (cloudKey && !isRedactedSecret(cloudKey)) {\n return cloudKey;\n }\n }\n\n return null;\n}\n\n/**\n * Get a summary of available TTS providers and their status.\n */\nexport function getTtsProviderStatus(\n ttsConfig: TtsConfig | undefined,\n runtime?: IAgentRuntime,\n): {\n configuredProvider: string | null;\n hasApiKey: boolean;\n resolvedProvider: string | null;\n} {\n const resolved = resolveTtsConfig(ttsConfig, runtime);\n return {\n configuredProvider: ttsConfig?.provider || null,\n hasApiKey: resolved\n ? resolved.provider !== \"edge\" && resolved.provider !== \"local-inference\"\n : false,\n resolvedProvider: resolved?.provider || null,\n };\n}\n\n// Module singleton\nexport const ttsStreamBridge = new TtsStreamBridge();\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elizaos/plugin-streaming",
3
- "description": "Unified RTMP streaming for elizaOS (Twitch, YouTube, X, pump.fun, custom and named ingest URLs)",
4
- "version": "2.0.0-beta.1",
3
+ "description": "RTMP streaming for elizaOS (Twitch, YouTube, X, pump.fun, custom and named ingest URLs)",
4
+ "version": "2.0.11-beta.7",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -13,35 +13,46 @@
13
13
  "./package.json": "./package.json",
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
16
+ "eliza-source": {
17
+ "types": "./src/index.ts",
18
+ "import": "./src/index.ts",
19
+ "default": "./src/index.ts"
20
+ },
16
21
  "import": "./dist/index.js",
17
22
  "default": "./dist/index.js"
23
+ },
24
+ "./*.css": "./dist/*.css",
25
+ "./*": {
26
+ "types": "./dist/*.d.ts",
27
+ "eliza-source": {
28
+ "types": "./src/*.ts",
29
+ "import": "./src/*.ts",
30
+ "default": "./src/*.ts"
31
+ },
32
+ "import": "./dist/*.js",
33
+ "default": "./dist/*.js"
18
34
  }
19
35
  },
20
36
  "files": [
21
37
  "dist"
22
38
  ],
23
39
  "dependencies": {
24
- "@elizaos/cloud-routing": "2.0.0-beta.1",
25
- "@elizaos/core": "2.0.0-beta.1",
26
- "@elizaos/plugin-browser": "2.0.0-beta.1"
40
+ "@elizaos/cloud-routing": "2.0.11-beta.7",
41
+ "@elizaos/core": "2.0.11-beta.7",
42
+ "@elizaos/plugin-browser": "2.0.11-beta.7"
27
43
  },
28
44
  "devDependencies": {
29
- "@eslint/js": "^10.0.0",
30
- "@typescript-eslint/eslint-plugin": "^8.22.0",
31
- "@typescript-eslint/parser": "^8.22.0",
32
- "eslint": "^10.0.0",
33
- "prettier": "^3.8.3",
34
45
  "tsup": "^8.5.1",
35
46
  "typescript": "^6.0.3",
36
47
  "vitest": "^4.0.0"
37
48
  },
38
49
  "scripts": {
39
- "build": "tsup",
50
+ "build": "tsup && tsc --declaration --emitDeclarationOnly --noEmit false",
40
51
  "dev": "tsup --watch",
41
52
  "test": "vitest run",
42
53
  "lint": "bunx @biomejs/biome check --write --unsafe src",
43
54
  "lint:check": "bunx @biomejs/biome check src",
44
- "typecheck": "tsc --noEmit",
55
+ "typecheck": "tsgo --noEmit",
45
56
  "clean": "rm -rf dist .turbo tsconfig.tsbuildinfo",
46
57
  "format": "bunx @biomejs/biome format --write .",
47
58
  "format:check": "bunx @biomejs/biome format ."
@@ -102,5 +113,6 @@
102
113
  "required": false
103
114
  }
104
115
  }
105
- }
116
+ },
117
+ "gitHead": "cdbc876f793d96073d7eb0d09715a031ce0cd32e"
106
118
  }