@mastra/voice-openai-realtime 0.1.4 → 0.2.0-alpha.10
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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +87 -0
- package/README.md +1 -0
- package/dist/_tsup-dts-rollup.d.cts +3 -1
- package/dist/_tsup-dts-rollup.d.ts +3 -1
- package/dist/index.cjs +47 -25
- package/dist/index.js +47 -25
- package/package.json +2 -2
- package/src/index.ts +46 -19
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/voice-openai-realtime@0.
|
|
2
|
+
> @mastra/voice-openai-realtime@0.2.0-alpha.10 build /home/runner/work/mastra/mastra/voice/openai-realtime-api
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 8690ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/voice/openai-realtime-api/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/voice/openai-realtime-api/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 9650ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
21
|
-
[32mCJS[39m ⚡️ Build success in
|
|
22
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
23
|
-
[32mESM[39m ⚡️ Build success in
|
|
20
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m19.12 KB[39m
|
|
21
|
+
[32mCJS[39m ⚡️ Build success in 683ms
|
|
22
|
+
[32mESM[39m [1mdist/index.js [22m[32m19.06 KB[39m
|
|
23
|
+
[32mESM[39m ⚡️ Build success in 708ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# @mastra/voice-openai-realtime
|
|
2
2
|
|
|
3
|
+
## 0.2.0-alpha.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [000a6d4]
|
|
8
|
+
- Updated dependencies [ed2f549]
|
|
9
|
+
- Updated dependencies [c0f22b4]
|
|
10
|
+
- Updated dependencies [0a033fa]
|
|
11
|
+
- Updated dependencies [9c26508]
|
|
12
|
+
- Updated dependencies [0f4eae3]
|
|
13
|
+
- Updated dependencies [16a8648]
|
|
14
|
+
- @mastra/core@0.9.0-alpha.8
|
|
15
|
+
|
|
16
|
+
## 0.2.0-alpha.9
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [71d9444]
|
|
21
|
+
- @mastra/core@0.9.0-alpha.7
|
|
22
|
+
|
|
23
|
+
## 0.2.0-alpha.8
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- Updated dependencies [157c741]
|
|
28
|
+
- @mastra/core@0.9.0-alpha.6
|
|
29
|
+
|
|
30
|
+
## 0.2.0-alpha.7
|
|
31
|
+
|
|
32
|
+
### Patch Changes
|
|
33
|
+
|
|
34
|
+
- Updated dependencies [08bb78e]
|
|
35
|
+
- @mastra/core@0.9.0-alpha.5
|
|
36
|
+
|
|
37
|
+
## 0.2.0-alpha.6
|
|
38
|
+
|
|
39
|
+
### Patch Changes
|
|
40
|
+
|
|
41
|
+
- Updated dependencies [7e92011]
|
|
42
|
+
- @mastra/core@0.9.0-alpha.4
|
|
43
|
+
|
|
44
|
+
## 0.2.0-alpha.5
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- 6cf1417: Voice reference and tool-call-start
|
|
49
|
+
- Updated dependencies [fe3ae4d]
|
|
50
|
+
- @mastra/core@0.9.0-alpha.3
|
|
51
|
+
|
|
52
|
+
## 0.2.0-alpha.4
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- 1ef0f1f: Disconnect
|
|
57
|
+
|
|
58
|
+
## 0.2.0-alpha.3
|
|
59
|
+
|
|
60
|
+
### Minor Changes
|
|
61
|
+
|
|
62
|
+
- 9aaa64b: Don't connect the ws connection until connect is called
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- Updated dependencies [9ee4293]
|
|
67
|
+
- @mastra/core@0.8.4-alpha.2
|
|
68
|
+
|
|
69
|
+
## 0.2.0-alpha.2
|
|
70
|
+
|
|
71
|
+
### Minor Changes
|
|
72
|
+
|
|
73
|
+
- 185f8e9: Emit add tools and silence warnings
|
|
74
|
+
|
|
75
|
+
## 0.1.5-alpha.1
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- Updated dependencies [8a8a73b]
|
|
80
|
+
- Updated dependencies [6f92295]
|
|
81
|
+
- @mastra/core@0.8.4-alpha.1
|
|
82
|
+
|
|
83
|
+
## 0.1.5-alpha.0
|
|
84
|
+
|
|
85
|
+
### Patch Changes
|
|
86
|
+
|
|
87
|
+
- Updated dependencies [03f3cd0]
|
|
88
|
+
- @mastra/core@0.8.4-alpha.0
|
|
89
|
+
|
|
3
90
|
## 0.1.4
|
|
4
91
|
|
|
5
92
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ OPENAI_API_KEY=your_api_key
|
|
|
20
20
|
|
|
21
21
|
```typescript
|
|
22
22
|
import { OpenAIRealtimeVoice } from '@mastra/voice-openai-realtime';
|
|
23
|
+
import { getMicrophoneStream } from '@mastra/node-audio';
|
|
23
24
|
|
|
24
25
|
// Create a voice instance with default configuration
|
|
25
26
|
const voice = new OpenAIRealtimeVoice();
|
|
@@ -43,7 +43,8 @@ export declare type OpenAIExecuteFunction = (args: any) => Promise<any>;
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
export declare class OpenAIRealtimeVoice extends MastraVoice {
|
|
46
|
-
private
|
|
46
|
+
private options;
|
|
47
|
+
private ws?;
|
|
47
48
|
private state;
|
|
48
49
|
private client;
|
|
49
50
|
private events;
|
|
@@ -220,6 +221,7 @@ export declare class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
220
221
|
* ```
|
|
221
222
|
*/
|
|
222
223
|
connect(): Promise<void>;
|
|
224
|
+
disconnect(): void;
|
|
223
225
|
/**
|
|
224
226
|
* Streams audio data in real-time to the OpenAI service.
|
|
225
227
|
* Useful for continuous audio streaming scenarios like live microphone input.
|
|
@@ -43,7 +43,8 @@ export declare type OpenAIExecuteFunction = (args: any) => Promise<any>;
|
|
|
43
43
|
* ```
|
|
44
44
|
*/
|
|
45
45
|
export declare class OpenAIRealtimeVoice extends MastraVoice {
|
|
46
|
-
private
|
|
46
|
+
private options;
|
|
47
|
+
private ws?;
|
|
47
48
|
private state;
|
|
48
49
|
private client;
|
|
49
50
|
private events;
|
|
@@ -220,6 +221,7 @@ export declare class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
220
221
|
* ```
|
|
221
222
|
*/
|
|
222
223
|
connect(): Promise<void>;
|
|
224
|
+
disconnect(): void;
|
|
223
225
|
/**
|
|
224
226
|
* Streams audio data in real-time to the OpenAI service.
|
|
225
227
|
* Useful for continuous audio streaming scenarios like live microphone input.
|
package/dist/index.cjs
CHANGED
|
@@ -76,15 +76,6 @@ var DEFAULT_URL = "wss://api.openai.com/v1/realtime";
|
|
|
76
76
|
var DEFAULT_MODEL = "gpt-4o-mini-realtime-preview-2024-12-17";
|
|
77
77
|
var VOICES = ["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"];
|
|
78
78
|
var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
79
|
-
ws;
|
|
80
|
-
state;
|
|
81
|
-
client;
|
|
82
|
-
events;
|
|
83
|
-
instructions;
|
|
84
|
-
tools;
|
|
85
|
-
debug;
|
|
86
|
-
queue = [];
|
|
87
|
-
transcriber;
|
|
88
79
|
/**
|
|
89
80
|
* Creates a new instance of OpenAIRealtimeVoice.
|
|
90
81
|
*
|
|
@@ -108,22 +99,23 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
108
99
|
*/
|
|
109
100
|
constructor(options = {}) {
|
|
110
101
|
super();
|
|
111
|
-
|
|
112
|
-
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
|
|
113
|
-
this.ws = new ws.WebSocket(url, void 0, {
|
|
114
|
-
headers: {
|
|
115
|
-
Authorization: "Bearer " + apiKey,
|
|
116
|
-
"OpenAI-Beta": "realtime=v1"
|
|
117
|
-
}
|
|
118
|
-
});
|
|
102
|
+
this.options = options;
|
|
119
103
|
this.client = new events.EventEmitter();
|
|
120
104
|
this.state = "close";
|
|
121
105
|
this.events = {};
|
|
122
106
|
this.speaker = options.speaker || DEFAULT_VOICE;
|
|
123
107
|
this.transcriber = options.transcriber || DEFAULT_TRANSCRIBER;
|
|
124
108
|
this.debug = options.debug || false;
|
|
125
|
-
this.setupEventListeners();
|
|
126
109
|
}
|
|
110
|
+
ws;
|
|
111
|
+
state;
|
|
112
|
+
client;
|
|
113
|
+
events;
|
|
114
|
+
instructions;
|
|
115
|
+
tools;
|
|
116
|
+
debug;
|
|
117
|
+
queue = [];
|
|
118
|
+
transcriber;
|
|
127
119
|
/**
|
|
128
120
|
* Returns a list of available voice speakers.
|
|
129
121
|
*
|
|
@@ -300,7 +292,7 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
300
292
|
}
|
|
301
293
|
waitForOpen() {
|
|
302
294
|
return new Promise((resolve) => {
|
|
303
|
-
this.ws
|
|
295
|
+
this.ws?.on("open", resolve);
|
|
304
296
|
});
|
|
305
297
|
}
|
|
306
298
|
waitForSessionCreated() {
|
|
@@ -321,8 +313,16 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
321
313
|
* ```
|
|
322
314
|
*/
|
|
323
315
|
async connect() {
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
const url = `${this.options.url || DEFAULT_URL}?model=${this.options.model || DEFAULT_MODEL}`;
|
|
317
|
+
const apiKey = this.options.apiKey || process.env.OPENAI_API_KEY;
|
|
318
|
+
this.ws = new ws.WebSocket(url, void 0, {
|
|
319
|
+
headers: {
|
|
320
|
+
Authorization: "Bearer " + apiKey,
|
|
321
|
+
"OpenAI-Beta": "realtime=v1"
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
this.setupEventListeners();
|
|
325
|
+
await Promise.all([this.waitForOpen(), this.waitForSessionCreated()]);
|
|
326
326
|
const openaiTools = transformTools(this.tools);
|
|
327
327
|
this.updateConfig({
|
|
328
328
|
instructions: this.instructions,
|
|
@@ -334,6 +334,10 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
334
334
|
});
|
|
335
335
|
this.state = "open";
|
|
336
336
|
}
|
|
337
|
+
disconnect() {
|
|
338
|
+
this.state = "close";
|
|
339
|
+
this.ws?.close();
|
|
340
|
+
}
|
|
337
341
|
/**
|
|
338
342
|
* Streams audio data in real-time to the OpenAI service.
|
|
339
343
|
* Useful for continuous audio streaming scenarios like live microphone input.
|
|
@@ -466,6 +470,9 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
466
470
|
}
|
|
467
471
|
setupEventListeners() {
|
|
468
472
|
const speakerStreams = /* @__PURE__ */ new Map();
|
|
473
|
+
if (!this.ws) {
|
|
474
|
+
throw new Error("WebSocket not initialized");
|
|
475
|
+
}
|
|
469
476
|
this.ws.on("message", (message) => {
|
|
470
477
|
const data = JSON.parse(message.toString());
|
|
471
478
|
this.client.emit(data.type, data);
|
|
@@ -478,7 +485,7 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
478
485
|
this.emit("session.created", ev);
|
|
479
486
|
const queue = this.queue.splice(0, this.queue.length);
|
|
480
487
|
for (const ev2 of queue) {
|
|
481
|
-
this.ws
|
|
488
|
+
this.ws?.send(JSON.stringify(ev2));
|
|
482
489
|
}
|
|
483
490
|
});
|
|
484
491
|
this.client.on("session.updated", (ev) => {
|
|
@@ -541,13 +548,28 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
541
548
|
console.warn(`Tool "${output.name}" not found`);
|
|
542
549
|
return;
|
|
543
550
|
}
|
|
551
|
+
if (tool?.execute) {
|
|
552
|
+
this.emit("tool-call-start", {
|
|
553
|
+
toolCallId: output.call_id,
|
|
554
|
+
toolName: output.name,
|
|
555
|
+
toolDescription: tool.description,
|
|
556
|
+
args: context
|
|
557
|
+
});
|
|
558
|
+
}
|
|
544
559
|
const result = await tool?.execute?.(
|
|
545
560
|
{ context },
|
|
546
561
|
{
|
|
547
|
-
toolCallId:
|
|
562
|
+
toolCallId: output.call_id,
|
|
548
563
|
messages: []
|
|
549
564
|
}
|
|
550
565
|
);
|
|
566
|
+
this.emit("tool-call-result", {
|
|
567
|
+
toolCallId: output.call_id,
|
|
568
|
+
toolName: output.name,
|
|
569
|
+
toolDescription: tool.description,
|
|
570
|
+
args: context,
|
|
571
|
+
result
|
|
572
|
+
});
|
|
551
573
|
this.sendEvent("conversation.item.create", {
|
|
552
574
|
item: {
|
|
553
575
|
type: "function_call_output",
|
|
@@ -583,10 +605,10 @@ var OpenAIRealtimeVoice = class extends voice.MastraVoice {
|
|
|
583
605
|
return btoa(binary);
|
|
584
606
|
}
|
|
585
607
|
sendEvent(type, data) {
|
|
586
|
-
if (this.ws.readyState !== this.ws.OPEN) {
|
|
608
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
587
609
|
this.queue.push({ type, ...data });
|
|
588
610
|
} else {
|
|
589
|
-
this.ws
|
|
611
|
+
this.ws?.send(
|
|
590
612
|
JSON.stringify({
|
|
591
613
|
type,
|
|
592
614
|
...data
|
package/dist/index.js
CHANGED
|
@@ -74,15 +74,6 @@ var DEFAULT_URL = "wss://api.openai.com/v1/realtime";
|
|
|
74
74
|
var DEFAULT_MODEL = "gpt-4o-mini-realtime-preview-2024-12-17";
|
|
75
75
|
var VOICES = ["alloy", "ash", "ballad", "coral", "echo", "sage", "shimmer", "verse"];
|
|
76
76
|
var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
77
|
-
ws;
|
|
78
|
-
state;
|
|
79
|
-
client;
|
|
80
|
-
events;
|
|
81
|
-
instructions;
|
|
82
|
-
tools;
|
|
83
|
-
debug;
|
|
84
|
-
queue = [];
|
|
85
|
-
transcriber;
|
|
86
77
|
/**
|
|
87
78
|
* Creates a new instance of OpenAIRealtimeVoice.
|
|
88
79
|
*
|
|
@@ -106,22 +97,23 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
106
97
|
*/
|
|
107
98
|
constructor(options = {}) {
|
|
108
99
|
super();
|
|
109
|
-
|
|
110
|
-
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
|
|
111
|
-
this.ws = new WebSocket(url, void 0, {
|
|
112
|
-
headers: {
|
|
113
|
-
Authorization: "Bearer " + apiKey,
|
|
114
|
-
"OpenAI-Beta": "realtime=v1"
|
|
115
|
-
}
|
|
116
|
-
});
|
|
100
|
+
this.options = options;
|
|
117
101
|
this.client = new EventEmitter();
|
|
118
102
|
this.state = "close";
|
|
119
103
|
this.events = {};
|
|
120
104
|
this.speaker = options.speaker || DEFAULT_VOICE;
|
|
121
105
|
this.transcriber = options.transcriber || DEFAULT_TRANSCRIBER;
|
|
122
106
|
this.debug = options.debug || false;
|
|
123
|
-
this.setupEventListeners();
|
|
124
107
|
}
|
|
108
|
+
ws;
|
|
109
|
+
state;
|
|
110
|
+
client;
|
|
111
|
+
events;
|
|
112
|
+
instructions;
|
|
113
|
+
tools;
|
|
114
|
+
debug;
|
|
115
|
+
queue = [];
|
|
116
|
+
transcriber;
|
|
125
117
|
/**
|
|
126
118
|
* Returns a list of available voice speakers.
|
|
127
119
|
*
|
|
@@ -298,7 +290,7 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
298
290
|
}
|
|
299
291
|
waitForOpen() {
|
|
300
292
|
return new Promise((resolve) => {
|
|
301
|
-
this.ws
|
|
293
|
+
this.ws?.on("open", resolve);
|
|
302
294
|
});
|
|
303
295
|
}
|
|
304
296
|
waitForSessionCreated() {
|
|
@@ -319,8 +311,16 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
319
311
|
* ```
|
|
320
312
|
*/
|
|
321
313
|
async connect() {
|
|
322
|
-
|
|
323
|
-
|
|
314
|
+
const url = `${this.options.url || DEFAULT_URL}?model=${this.options.model || DEFAULT_MODEL}`;
|
|
315
|
+
const apiKey = this.options.apiKey || process.env.OPENAI_API_KEY;
|
|
316
|
+
this.ws = new WebSocket(url, void 0, {
|
|
317
|
+
headers: {
|
|
318
|
+
Authorization: "Bearer " + apiKey,
|
|
319
|
+
"OpenAI-Beta": "realtime=v1"
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
this.setupEventListeners();
|
|
323
|
+
await Promise.all([this.waitForOpen(), this.waitForSessionCreated()]);
|
|
324
324
|
const openaiTools = transformTools(this.tools);
|
|
325
325
|
this.updateConfig({
|
|
326
326
|
instructions: this.instructions,
|
|
@@ -332,6 +332,10 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
332
332
|
});
|
|
333
333
|
this.state = "open";
|
|
334
334
|
}
|
|
335
|
+
disconnect() {
|
|
336
|
+
this.state = "close";
|
|
337
|
+
this.ws?.close();
|
|
338
|
+
}
|
|
335
339
|
/**
|
|
336
340
|
* Streams audio data in real-time to the OpenAI service.
|
|
337
341
|
* Useful for continuous audio streaming scenarios like live microphone input.
|
|
@@ -464,6 +468,9 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
464
468
|
}
|
|
465
469
|
setupEventListeners() {
|
|
466
470
|
const speakerStreams = /* @__PURE__ */ new Map();
|
|
471
|
+
if (!this.ws) {
|
|
472
|
+
throw new Error("WebSocket not initialized");
|
|
473
|
+
}
|
|
467
474
|
this.ws.on("message", (message) => {
|
|
468
475
|
const data = JSON.parse(message.toString());
|
|
469
476
|
this.client.emit(data.type, data);
|
|
@@ -476,7 +483,7 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
476
483
|
this.emit("session.created", ev);
|
|
477
484
|
const queue = this.queue.splice(0, this.queue.length);
|
|
478
485
|
for (const ev2 of queue) {
|
|
479
|
-
this.ws
|
|
486
|
+
this.ws?.send(JSON.stringify(ev2));
|
|
480
487
|
}
|
|
481
488
|
});
|
|
482
489
|
this.client.on("session.updated", (ev) => {
|
|
@@ -539,13 +546,28 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
539
546
|
console.warn(`Tool "${output.name}" not found`);
|
|
540
547
|
return;
|
|
541
548
|
}
|
|
549
|
+
if (tool?.execute) {
|
|
550
|
+
this.emit("tool-call-start", {
|
|
551
|
+
toolCallId: output.call_id,
|
|
552
|
+
toolName: output.name,
|
|
553
|
+
toolDescription: tool.description,
|
|
554
|
+
args: context
|
|
555
|
+
});
|
|
556
|
+
}
|
|
542
557
|
const result = await tool?.execute?.(
|
|
543
558
|
{ context },
|
|
544
559
|
{
|
|
545
|
-
toolCallId:
|
|
560
|
+
toolCallId: output.call_id,
|
|
546
561
|
messages: []
|
|
547
562
|
}
|
|
548
563
|
);
|
|
564
|
+
this.emit("tool-call-result", {
|
|
565
|
+
toolCallId: output.call_id,
|
|
566
|
+
toolName: output.name,
|
|
567
|
+
toolDescription: tool.description,
|
|
568
|
+
args: context,
|
|
569
|
+
result
|
|
570
|
+
});
|
|
549
571
|
this.sendEvent("conversation.item.create", {
|
|
550
572
|
item: {
|
|
551
573
|
type: "function_call_output",
|
|
@@ -581,10 +603,10 @@ var OpenAIRealtimeVoice = class extends MastraVoice {
|
|
|
581
603
|
return btoa(binary);
|
|
582
604
|
}
|
|
583
605
|
sendEvent(type, data) {
|
|
584
|
-
if (this.ws.readyState !== this.ws.OPEN) {
|
|
606
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
585
607
|
this.queue.push({ type, ...data });
|
|
586
608
|
} else {
|
|
587
|
-
this.ws
|
|
609
|
+
this.ws?.send(
|
|
588
610
|
JSON.stringify({
|
|
589
611
|
type,
|
|
590
612
|
...data
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/voice-openai-realtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-alpha.10",
|
|
4
4
|
"description": "Mastra OpenAI Realtime API integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"openai-realtime-api": "^1.0.7",
|
|
24
24
|
"ws": "^8.18.1",
|
|
25
25
|
"zod-to-json-schema": "^3.24.5",
|
|
26
|
-
"@mastra/core": "^0.8
|
|
26
|
+
"@mastra/core": "^0.9.0-alpha.8"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@microsoft/api-extractor": "^7.52.2",
|
package/src/index.ts
CHANGED
|
@@ -106,7 +106,7 @@ type RealtimeClientServerEventMap = {
|
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
108
|
export class OpenAIRealtimeVoice extends MastraVoice {
|
|
109
|
-
private ws
|
|
109
|
+
private ws?: WebSocket;
|
|
110
110
|
private state: 'close' | 'open';
|
|
111
111
|
private client: EventEmitter<RealtimeClientServerEventMap>;
|
|
112
112
|
private events: EventMap;
|
|
@@ -138,7 +138,7 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
138
138
|
* ```
|
|
139
139
|
*/
|
|
140
140
|
constructor(
|
|
141
|
-
options: {
|
|
141
|
+
private options: {
|
|
142
142
|
model?: string;
|
|
143
143
|
url?: string;
|
|
144
144
|
apiKey?: string;
|
|
@@ -149,22 +149,12 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
149
149
|
) {
|
|
150
150
|
super();
|
|
151
151
|
|
|
152
|
-
const url = `${options.url || DEFAULT_URL}?model=${options.model || DEFAULT_MODEL}`;
|
|
153
|
-
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
|
|
154
|
-
this.ws = new WebSocket(url, undefined, {
|
|
155
|
-
headers: {
|
|
156
|
-
Authorization: 'Bearer ' + apiKey,
|
|
157
|
-
'OpenAI-Beta': 'realtime=v1',
|
|
158
|
-
},
|
|
159
|
-
});
|
|
160
|
-
|
|
161
152
|
this.client = new EventEmitter();
|
|
162
153
|
this.state = 'close';
|
|
163
154
|
this.events = {} as EventMap;
|
|
164
155
|
this.speaker = options.speaker || DEFAULT_VOICE;
|
|
165
156
|
this.transcriber = options.transcriber || DEFAULT_TRANSCRIBER;
|
|
166
157
|
this.debug = options.debug || false;
|
|
167
|
-
this.setupEventListeners();
|
|
168
158
|
}
|
|
169
159
|
|
|
170
160
|
/**
|
|
@@ -355,7 +345,7 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
355
345
|
|
|
356
346
|
waitForOpen() {
|
|
357
347
|
return new Promise(resolve => {
|
|
358
|
-
this.ws
|
|
348
|
+
this.ws?.on('open', resolve);
|
|
359
349
|
});
|
|
360
350
|
}
|
|
361
351
|
|
|
@@ -378,8 +368,17 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
378
368
|
* ```
|
|
379
369
|
*/
|
|
380
370
|
async connect() {
|
|
381
|
-
|
|
382
|
-
|
|
371
|
+
const url = `${this.options.url || DEFAULT_URL}?model=${this.options.model || DEFAULT_MODEL}`;
|
|
372
|
+
const apiKey = this.options.apiKey || process.env.OPENAI_API_KEY;
|
|
373
|
+
this.ws = new WebSocket(url, undefined, {
|
|
374
|
+
headers: {
|
|
375
|
+
Authorization: 'Bearer ' + apiKey,
|
|
376
|
+
'OpenAI-Beta': 'realtime=v1',
|
|
377
|
+
},
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
this.setupEventListeners();
|
|
381
|
+
await Promise.all([this.waitForOpen(), this.waitForSessionCreated()]);
|
|
383
382
|
|
|
384
383
|
const openaiTools = transformTools(this.tools);
|
|
385
384
|
this.updateConfig({
|
|
@@ -393,6 +392,11 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
393
392
|
this.state = 'open';
|
|
394
393
|
}
|
|
395
394
|
|
|
395
|
+
disconnect() {
|
|
396
|
+
this.state = 'close';
|
|
397
|
+
this.ws?.close();
|
|
398
|
+
}
|
|
399
|
+
|
|
396
400
|
/**
|
|
397
401
|
* Streams audio data in real-time to the OpenAI service.
|
|
398
402
|
* Useful for continuous audio streaming scenarios like live microphone input.
|
|
@@ -534,6 +538,10 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
534
538
|
private setupEventListeners(): void {
|
|
535
539
|
const speakerStreams = new Map<string, StreamWithId>();
|
|
536
540
|
|
|
541
|
+
if (!this.ws) {
|
|
542
|
+
throw new Error('WebSocket not initialized');
|
|
543
|
+
}
|
|
544
|
+
|
|
537
545
|
this.ws.on('message', message => {
|
|
538
546
|
const data = JSON.parse(message.toString());
|
|
539
547
|
this.client.emit(data.type, data);
|
|
@@ -549,7 +557,7 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
549
557
|
|
|
550
558
|
const queue = this.queue.splice(0, this.queue.length);
|
|
551
559
|
for (const ev of queue) {
|
|
552
|
-
this.ws
|
|
560
|
+
this.ws?.send(JSON.stringify(ev));
|
|
553
561
|
}
|
|
554
562
|
});
|
|
555
563
|
this.client.on('session.updated', ev => {
|
|
@@ -619,13 +627,32 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
619
627
|
console.warn(`Tool "${output.name}" not found`);
|
|
620
628
|
return;
|
|
621
629
|
}
|
|
630
|
+
|
|
631
|
+
if (tool?.execute) {
|
|
632
|
+
this.emit('tool-call-start', {
|
|
633
|
+
toolCallId: output.call_id,
|
|
634
|
+
toolName: output.name,
|
|
635
|
+
toolDescription: tool.description,
|
|
636
|
+
args: context,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
622
640
|
const result = await tool?.execute?.(
|
|
623
641
|
{ context },
|
|
624
642
|
{
|
|
625
|
-
toolCallId:
|
|
643
|
+
toolCallId: output.call_id,
|
|
626
644
|
messages: [],
|
|
627
645
|
},
|
|
628
646
|
);
|
|
647
|
+
|
|
648
|
+
this.emit('tool-call-result', {
|
|
649
|
+
toolCallId: output.call_id,
|
|
650
|
+
toolName: output.name,
|
|
651
|
+
toolDescription: tool.description,
|
|
652
|
+
args: context,
|
|
653
|
+
result,
|
|
654
|
+
});
|
|
655
|
+
|
|
629
656
|
this.sendEvent('conversation.item.create', {
|
|
630
657
|
item: {
|
|
631
658
|
type: 'function_call_output',
|
|
@@ -663,10 +690,10 @@ export class OpenAIRealtimeVoice extends MastraVoice {
|
|
|
663
690
|
}
|
|
664
691
|
|
|
665
692
|
private sendEvent(type: string, data: any) {
|
|
666
|
-
if (this.ws.readyState !== this.ws.OPEN) {
|
|
693
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
667
694
|
this.queue.push({ type: type, ...data });
|
|
668
695
|
} else {
|
|
669
|
-
this.ws
|
|
696
|
+
this.ws?.send(
|
|
670
697
|
JSON.stringify({
|
|
671
698
|
type: type,
|
|
672
699
|
...data,
|