(base) asvinir@Asvinis-MacBook-Air ai_be % npm run tsc
> ai_be@1.0.0 tsc
> tsc -b && node dist/server.js
Server running on port 3000
Voice stream generation error: ElevenLabsError: Status code: 400
Body: {
  "readableStream": {},
  "reader": {},
  "events": {
    "data": [],
    "end": [],
    "error": [],
    "readable": [],
    "close": [],
    "pause": [],
    "resume": []
  },
  "paused": false,
  "resumeCallback": null,
  "encoding": null
}
    at TextToSpeech.<anonymous> (/Users/asvinir/Desktop/ai_be/node_modules/elevenlabs/api/resources/textToSpeech/client/Client.js:238:31)
    at Generator.next (<anonymous>)
    at fulfilled (/Users/asvinir/Desktop/ai_be/node_modules/elevenlabs/api/resources/textToSpeech/client/Client.js:31:58)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  statusCode: 400,
  body: Node18UniversalStreamWrapper {
    readableStream: ReadableStream { locked: true, state: 'readable', supportsBYOB: true },
    reader: ReadableStreamDefaultReader {
      stream: ReadableStream { locked: true, state: 'readable', supportsBYOB: true },
      readRequests: 0,
      close: Promise { <pending> }
    },
    events: {
      data: [],
      end: [],
      error: [],
      readable: [],
      close: [],
      pause: [],
      resume: []
    },
    paused: false,
    resumeCallback: null,
    encoding: null
  }
}
"""
"""
import express from 'express';
import AIVoiceStreamService from '../openaiElevenLabs/AIVoiceStreamService';
import { ChatMessage } from '../openaiElevenLabs/types';

const router = express.Router();
const aiVoiceService = new AIVoiceStreamService();

router.post('/stream', async (req: any, res: any) => {
  try {
    const { messages }: { messages: ChatMessage[] } = req.body;

    if (!messages || !Array.isArray(messages)) {
      return res.status(400).json({ error: 'Invalid messages format' });
    }

    // Directly stream voice to response
    await aiVoiceService.generateVoiceStream(messages, res);
  } catch (error) {
    console.error('Streaming error:', error);
    if (!res.headersSent) {
      res.status(500).json({ 
        error: error instanceof Error ? error.message : 'Unknown error' 
      });
    }
  }
});

export default router;
"""
"""
import * as dotenv from 'dotenv';
dotenv.config();
import OpenAI from 'openai';
import { Response } from 'express';
import { ChatMessage, AIVoiceStreamOptions } from './types';
import { ElevenLabsClient } from "elevenlabs";

class AIVoiceStreamService {
  private openai: OpenAI;
  private client: ElevenLabsClient;

  constructor() {
    // Validate API keys on initialization
    if (!process.env.OPENAI_API_KEY) {
      throw new Error('Missing OpenAI API Key');
    }
    if (!process.env.ELEVENLABS_API_KEY) {
      throw new Error('Missing ElevenLabs API Key');
    }

    this.openai = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
    this.client = new ElevenLabsClient({
      apiKey: process.env.ELEVENLABS_API_KEY,
    });
  }

  /**
   * Generate AI response and directly stream audio to response
   * @param messages Array of chat messages
   * @param res Express response object
   * @param options Configuration options for AI and TTS
   */
  async generateVoiceStream(
    messages: ChatMessage[], 
    res: Response,
    options: AIVoiceStreamOptions = {}
  ): Promise<void> {
    // Default options with robust fallbacks
    const {
      openaiModel = 'gpt-4o-mini',
      elevenLabsVoiceId = process.env.ELEVENLABS_VOICE_ID || '',
      stability = 0.75,
      similarityBoost = 0.85
    } = options;

    // Validate inputs
    if (!elevenLabsVoiceId) {
      throw new Error('No ElevenLabs Voice ID provided');
    }

    try {
      // Generate text response from OpenAI
      const textStream = await this.openai.beta.chat.completions.stream({
        model: openaiModel,
        messages: messages,
        stream: true,
      });

      // Collect full text response
      let fullContent = '';
      for await (const chunk of textStream) {
        const delta = chunk.choices[0]?.delta?.content || '';
        fullContent += delta;
      }

      // Set headers for audio streaming
      res.setHeader('Content-Type', 'audio/mpeg');
      res.setHeader('Transfer-Encoding', 'chunked');

      // Directly stream audio to response
      const audioStream = await this.client.textToSpeech.convertAsStream(elevenLabsVoiceId, {
        text: fullContent,
        model_id: "eleven_monolingual_v1",
        voice_settings: {
          stability,
          similarity_boost: similarityBoost,
        },
      });

      // Pipe the audio stream directly to the response
      for await (const chunk of audioStream) {
        res.write(chunk);
      }
      
      // End the response
      res.end();
    } catch (error: any) {
      // Error handling
      console.error('Voice stream generation error:', error);
      if (!res.headersSent) {
        res.status(500).json({ 
          error: `Failed to generate voice stream: ${error.message}` 
        });
      }
    }
  }
}

export default AIVoiceStreamService;