@mastra/voice-deepgram 0.0.0-storage-20250225005900 → 0.0.0-stream-vnext-usage-20250908171242

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/src/index.test.ts DELETED
@@ -1,134 +0,0 @@
1
- import { writeFileSync, mkdirSync, createReadStream } from 'fs';
2
- import path from 'path';
3
- import { PassThrough } from 'stream';
4
- import { describe, expect, it, beforeAll } from 'vitest';
5
-
6
- import { DeepgramVoice } from './index.js';
7
-
8
- describe('DeepgramVoice Integration Tests', () => {
9
- let voice: DeepgramVoice;
10
- const outputDir = path.join(process.cwd(), 'test-outputs');
11
-
12
- beforeAll(() => {
13
- try {
14
- mkdirSync(outputDir, { recursive: true });
15
- } catch (err) {
16
- console.log('Directory already exists: ', err);
17
- }
18
-
19
- voice = new DeepgramVoice({
20
- speechModel: {
21
- name: 'aura',
22
- },
23
- listeningModel: {
24
- name: 'whisper',
25
- },
26
- speaker: 'asteria-en',
27
- });
28
- });
29
-
30
- describe('getSpeakers', () => {
31
- it('should list available voices', async () => {
32
- const speakers = await voice.getSpeakers();
33
- const expectedVoiceIds = ['asteria-en', 'stella-en', 'luna-en'];
34
- expectedVoiceIds.forEach(voiceId => {
35
- expect(speakers.some(s => s.voiceId === voiceId)).toBe(true);
36
- });
37
- });
38
- });
39
-
40
- describe('speak', () => {
41
- it('should generate audio and save to file', async () => {
42
- const audioResult = await voice.speak('Hello World', {
43
- text: 'Hello World',
44
- });
45
-
46
- const chunks: Buffer[] = [];
47
- for await (const chunk of audioResult) {
48
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
49
- }
50
- const audioBuffer = Buffer.concat(chunks);
51
- const outputPath = path.join(outputDir, 'deepgram-speech-test.mp3');
52
- writeFileSync(outputPath, audioBuffer);
53
- expect(audioBuffer.length).toBeGreaterThan(0);
54
- }, 10000);
55
-
56
- it('should work with different parameters', async () => {
57
- const audioResult = await voice.speak('Hello World', {
58
- text: 'Test with parameters',
59
- speaker: 'luna-en',
60
- });
61
-
62
- const chunks: Buffer[] = [];
63
- for await (const chunk of audioResult) {
64
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
65
- }
66
- const audioBuffer = Buffer.concat(chunks);
67
- const outputPath = path.join(outputDir, 'deepgram-speech-test-params.mp3');
68
- writeFileSync(outputPath, audioBuffer);
69
- expect(audioBuffer.length).toBeGreaterThan(0);
70
- }, 10000);
71
- });
72
-
73
- // Error cases
74
- describe('error handling', () => {
75
- it('should handle invalid voice names', async () => {
76
- await expect(voice.speak('Test', { speaker: 'invalid_voice' })).rejects.toThrow();
77
- });
78
-
79
- it('should handle empty text', async () => {
80
- await expect(voice.speak('', { speaker: 'asteria-en' })).rejects.toThrow('Input text is empty');
81
- });
82
-
83
- it('should handle whitespace-only text', async () => {
84
- await expect(voice.speak(' \n\t ', { speaker: 'asteria-en' })).rejects.toThrow('Input text is empty');
85
- });
86
- });
87
-
88
- describe('listen', () => {
89
- it('should transcribe audio buffer', async () => {
90
- // First generate some audio to transcribe
91
- const audioResult = await voice.speak('This is a test of transcription');
92
-
93
- // Collect audio chunks
94
- const chunks: Buffer[] = [];
95
- for await (const chunk of audioResult) {
96
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
97
- }
98
- const audioBuffer = Buffer.concat(chunks);
99
-
100
- // Create stream from the buffer
101
- const stream = new PassThrough();
102
- stream.end(audioBuffer);
103
- const result = await voice.listen(stream);
104
-
105
- expect(typeof result).toBe('string');
106
- expect(result.toLowerCase()).toContain('test');
107
- expect(result.toLowerCase()).toContain('transcription');
108
- }, 15000);
109
-
110
- it('should transcribe audio from fixture file', async () => {
111
- const fixturePath = path.join(process.cwd(), '__fixtures__', 'voice-test.m4a');
112
- const audioStream = createReadStream(fixturePath);
113
-
114
- console.log('listening to audio stream');
115
- const text = await voice.listen(audioStream, {
116
- filetype: 'm4a',
117
- });
118
- console.log('text', text);
119
-
120
- expect(text).toBeTruthy();
121
- console.log(text);
122
- expect(typeof text).toBe('string');
123
- expect(text.length).toBeGreaterThan(0);
124
- }, 15000);
125
-
126
- it('should handle invalid audio', async () => {
127
- const invalidAudio = Buffer.from('not valid audio');
128
- const stream = new PassThrough();
129
- stream.end(invalidAudio);
130
-
131
- await expect(voice.listen(stream)).rejects.toThrow();
132
- });
133
- });
134
- });
package/src/index.ts DELETED
@@ -1,190 +0,0 @@
1
- import { createClient } from '@deepgram/sdk';
2
- import { MastraVoice } from '@mastra/core/voice';
3
- import { PassThrough } from 'stream';
4
-
5
- import { DEEPGRAM_VOICES } from './voices';
6
- import type { DeepgramVoiceId, DeepgramModel } from './voices';
7
-
8
- interface DeepgramVoiceConfig {
9
- name?: DeepgramModel;
10
- apiKey?: string;
11
- properties?: Record<string, any>;
12
- language?: string;
13
- }
14
-
15
- export class DeepgramVoice extends MastraVoice {
16
- private speechClient?: ReturnType<typeof createClient>;
17
- private listeningClient?: ReturnType<typeof createClient>;
18
-
19
- constructor({
20
- speechModel,
21
- listeningModel,
22
- speaker,
23
- }: { speechModel?: DeepgramVoiceConfig; listeningModel?: DeepgramVoiceConfig; speaker?: DeepgramVoiceId } = {}) {
24
- const defaultApiKey = process.env.DEEPGRAM_API_KEY;
25
-
26
- const defaultSpeechModel = {
27
- name: 'aura',
28
- apiKey: defaultApiKey,
29
- };
30
-
31
- const defaultListeningModel = {
32
- name: 'nova',
33
- apiKey: defaultApiKey,
34
- };
35
-
36
- super({
37
- speechModel: {
38
- name: speechModel?.name ?? defaultSpeechModel.name,
39
- apiKey: speechModel?.apiKey ?? defaultSpeechModel.apiKey,
40
- },
41
- listeningModel: {
42
- name: listeningModel?.name ?? defaultListeningModel.name,
43
- apiKey: listeningModel?.apiKey ?? defaultListeningModel.apiKey,
44
- },
45
- speaker,
46
- });
47
-
48
- const speechApiKey = speechModel?.apiKey || defaultApiKey;
49
- const listeningApiKey = listeningModel?.apiKey || defaultApiKey;
50
- console.log('speechApiKey', speechApiKey);
51
- console.log('listeningApiKey', listeningApiKey);
52
- if (!speechApiKey && !listeningApiKey) {
53
- throw new Error('At least one of DEEPGRAM_API_KEY, speechModel.apiKey, or listeningModel.apiKey must be set');
54
- }
55
-
56
- if (speechApiKey) {
57
- this.speechClient = createClient(speechApiKey);
58
- }
59
- if (listeningApiKey) {
60
- this.listeningClient = createClient(listeningApiKey);
61
- }
62
-
63
- this.speaker = speaker || 'asteria-en';
64
- }
65
-
66
- async getSpeakers() {
67
- return this.traced(async () => {
68
- return DEEPGRAM_VOICES.map(voice => ({
69
- voiceId: voice,
70
- }));
71
- }, 'voice.deepgram.getSpeakers')();
72
- }
73
-
74
- async speak(
75
- input: string | NodeJS.ReadableStream,
76
- options?: {
77
- speaker?: string;
78
- [key: string]: any;
79
- },
80
- ): Promise<NodeJS.ReadableStream> {
81
- if (!this.speechClient) {
82
- throw new Error('Deepgram speech client not configured');
83
- }
84
-
85
- let text: string;
86
- if (typeof input !== 'string') {
87
- const chunks: Buffer[] = [];
88
- for await (const chunk of input) {
89
- chunks.push(Buffer.from(chunk));
90
- }
91
- text = Buffer.concat(chunks).toString('utf-8');
92
- } else {
93
- text = input;
94
- }
95
-
96
- if (text.trim().length === 0) {
97
- throw new Error('Input text is empty');
98
- }
99
-
100
- return this.traced(async () => {
101
- if (!this.speechClient) {
102
- throw new Error('No speech client configured');
103
- }
104
-
105
- let model;
106
- if (options?.speaker) {
107
- model = this.speechModel?.name + '-' + options.speaker;
108
- } else if (this.speaker) {
109
- model = this.speechModel?.name + '-' + this.speaker;
110
- }
111
-
112
- const speakClient = this.speechClient.speak;
113
- const response = await speakClient.request(
114
- { text },
115
- {
116
- model,
117
- ...options,
118
- },
119
- );
120
-
121
- const webStream = await response.getStream();
122
- if (!webStream) {
123
- throw new Error('No stream returned from Deepgram');
124
- }
125
-
126
- const reader = webStream.getReader();
127
- const nodeStream = new PassThrough();
128
-
129
- // Add error handling for the stream processing
130
- (async () => {
131
- try {
132
- while (true) {
133
- const { done, value } = await reader.read();
134
- if (done) {
135
- nodeStream.end();
136
- break;
137
- }
138
- nodeStream.write(value);
139
- }
140
- } catch (error) {
141
- nodeStream.destroy(error as Error);
142
- }
143
- })().catch(error => {
144
- nodeStream.destroy(error as Error);
145
- });
146
-
147
- return nodeStream;
148
- }, 'voice.deepgram.speak')();
149
- }
150
-
151
- async listen(
152
- audioStream: NodeJS.ReadableStream,
153
- options?: {
154
- [key: string]: any;
155
- },
156
- ): Promise<string> {
157
- if (!this.listeningClient) {
158
- throw new Error('Deepgram listening client not configured');
159
- }
160
-
161
- const chunks: Buffer[] = [];
162
- for await (const chunk of audioStream) {
163
- chunks.push(Buffer.from(chunk));
164
- }
165
- const buffer = Buffer.concat(chunks);
166
-
167
- return this.traced(async () => {
168
- if (!this.listeningClient) {
169
- throw new Error('No listening client configured');
170
- }
171
- const { result, error } = await this.listeningClient.listen.prerecorded.transcribeFile(buffer, {
172
- model: this.listeningModel?.name,
173
- ...options,
174
- });
175
-
176
- if (error) {
177
- throw error;
178
- }
179
-
180
- const transcript = result.results?.channels?.[0]?.alternatives?.[0]?.transcript;
181
- if (!transcript) {
182
- throw new Error('No transcript found in Deepgram response');
183
- }
184
-
185
- return transcript;
186
- }, 'voice.deepgram.listen')();
187
- }
188
- }
189
-
190
- export type { DeepgramVoiceConfig, DeepgramVoiceId, DeepgramModel };
package/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.node.json",
3
- "include": ["src/**/*"],
4
- "exclude": ["node_modules", "**/*.test.ts"]
5
- }
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineConfig } from 'vitest/config';
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: 'node',
7
- include: ['src/**/*.test.ts'],
8
- },
9
- });