@openai/agents-extensions 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OpenAI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # OpenAI Agents SDK Extensions
2
+
3
+ This package contains a collection of extension features for the OpenAI Agents SDK and is intended to be used alongside it.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @openai/agents @openai/agents-extensions
9
+ ```
10
+
11
+ ## License
12
+
13
+ MIT
@@ -0,0 +1,46 @@
1
+ import { OpenAIRealtimeWebSocket, OpenAIRealtimeWebSocketOptions, RealtimeTransportLayerConnectOptions, TransportLayerAudio, RealtimeSessionConfig } from '@openai/agents/realtime';
2
+ import type { WebSocket } from 'ws';
3
+ /**
4
+ * The options for the Twilio Realtime Transport Layer.
5
+ */
6
+ export type TwilioRealtimeTransportLayerOptions = OpenAIRealtimeWebSocketOptions & {
7
+ /**
8
+ * The websocket that is receiving messages from Twilio's Media Streams API. Typically the
9
+ * connection gets passed into your request handler when running your WebSocket server.
10
+ */
11
+ twilioWebSocket: WebSocket;
12
+ };
13
+ /**
14
+ * An adapter to connect a websocket that is receiving messages from Twilio's Media Streams API to
15
+ * the OpenAI Realtime API via WebSocket.
16
+ *
17
+ * It automatically handles setting the right audio format for the input and output audio, passing
18
+ * the data along and handling the timing for interruptions using Twilio's `mark` events.
19
+ *
20
+ * It does require you to run your own WebSocket server that is receiving connection requests from
21
+ * Twilio.
22
+ *
23
+ * It will emit all Twilio received messages as `twilio_message` type messages on the `*` handler.
24
+ * If you are using a `RealtimeSession` you can listen to the `transport_event`.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const transport = new TwilioRealtimeTransportLayer({
29
+ * twilioWebSocket: twilioWebSocket,
30
+ * });
31
+ *
32
+ * transport.on('*', (event) => {
33
+ * if (event.type === 'twilio_message') {
34
+ * console.log('Twilio message:', event.data);
35
+ * }
36
+ * });
37
+ * ```
38
+ */
39
+ export declare class TwilioRealtimeTransportLayer extends OpenAIRealtimeWebSocket {
40
+ #private;
41
+ constructor(options: TwilioRealtimeTransportLayerOptions);
42
+ _setInputAndOutputAudioFormat(partialConfig?: Partial<RealtimeSessionConfig>): Partial<RealtimeSessionConfig>;
43
+ connect(options: RealtimeTransportLayerConnectOptions): Promise<void>;
44
+ _interrupt(_elapsedTime: number): void;
45
+ protected _onAudio(audioEvent: TransportLayerAudio): void;
46
+ }
@@ -0,0 +1,165 @@
1
+ import { OpenAIRealtimeWebSocket, utils, } from '@openai/agents/realtime';
2
+ import { getLogger } from '@openai/agents';
3
+ /**
4
+ * An adapter to connect a websocket that is receiving messages from Twilio's Media Streams API to
5
+ * the OpenAI Realtime API via WebSocket.
6
+ *
7
+ * It automatically handles setting the right audio format for the input and output audio, passing
8
+ * the data along and handling the timing for interruptions using Twilio's `mark` events.
9
+ *
10
+ * It does require you to run your own WebSocket server that is receiving connection requests from
11
+ * Twilio.
12
+ *
13
+ * It will emit all Twilio received messages as `twilio_message` type messages on the `*` handler.
14
+ * If you are using a `RealtimeSession` you can listen to the `transport_event`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const transport = new TwilioRealtimeTransportLayer({
19
+ * twilioWebSocket: twilioWebSocket,
20
+ * });
21
+ *
22
+ * transport.on('*', (event) => {
23
+ * if (event.type === 'twilio_message') {
24
+ * console.log('Twilio message:', event.data);
25
+ * }
26
+ * });
27
+ * ```
28
+ */
29
+ export class TwilioRealtimeTransportLayer extends OpenAIRealtimeWebSocket {
30
+ #twilioWebSocket;
31
+ #streamSid = null;
32
+ #audioChunkCount = 0;
33
+ #lastPlayedChunkCount = 0;
34
+ #previousItemId = null;
35
+ #logger = getLogger('openai-agents:extensions:twilio');
36
+ constructor(options) {
37
+ super(options);
38
+ this.#twilioWebSocket = options.twilioWebSocket;
39
+ }
40
+ _setInputAndOutputAudioFormat(partialConfig) {
41
+ let newConfig = {};
42
+ if (!partialConfig) {
43
+ newConfig.inputAudioFormat = 'g711_ulaw';
44
+ newConfig.outputAudioFormat = 'g711_ulaw';
45
+ }
46
+ else {
47
+ newConfig = {
48
+ ...partialConfig,
49
+ inputAudioFormat: partialConfig.inputAudioFormat ?? 'g711_ulaw',
50
+ outputAudioFormat: partialConfig.outputAudioFormat ?? 'g711_ulaw',
51
+ };
52
+ }
53
+ return newConfig;
54
+ }
55
+ async connect(options) {
56
+ options.initialSessionConfig = this._setInputAndOutputAudioFormat(options.initialSessionConfig);
57
+ // listen to Twilio messages as quickly as possible
58
+ this.#twilioWebSocket.on('message', (message) => {
59
+ try {
60
+ const data = JSON.parse(message.toString());
61
+ if (this.#logger.dontLogModelData) {
62
+ this.#logger.debug('Twilio message:', data.event);
63
+ }
64
+ else {
65
+ this.#logger.debug('Twilio message:', data);
66
+ }
67
+ this.emit('*', {
68
+ type: 'twilio_message',
69
+ message: data,
70
+ });
71
+ switch (data.event) {
72
+ case 'media':
73
+ if (this.status === 'connected') {
74
+ this.sendAudio(utils.base64ToArrayBuffer(data.media.payload));
75
+ }
76
+ break;
77
+ case 'mark':
78
+ if (!data.mark.name.startsWith('done:') &&
79
+ data.mark.name.includes(':')) {
80
+ // keeping track of what the last chunk was that the user heard fully
81
+ const count = Number(data.mark.name.split(':')[1]);
82
+ if (Number.isFinite(count)) {
83
+ this.#lastPlayedChunkCount = count;
84
+ }
85
+ else {
86
+ this.#logger.warn('Invalid mark name received:', data.mark.name);
87
+ }
88
+ }
89
+ else if (data.mark.name.startsWith('done:')) {
90
+ this.#lastPlayedChunkCount = 0;
91
+ }
92
+ break;
93
+ case 'start':
94
+ this.#streamSid = data.start.streamSid;
95
+ break;
96
+ default:
97
+ break;
98
+ }
99
+ }
100
+ catch (error) {
101
+ this.#logger.error('Error parsing message:', error, 'Message:', message);
102
+ this.emit('error', {
103
+ type: 'error',
104
+ error,
105
+ });
106
+ }
107
+ });
108
+ this.#twilioWebSocket.on('close', () => {
109
+ if (this.status !== 'disconnected') {
110
+ this.close();
111
+ }
112
+ });
113
+ this.#twilioWebSocket.on('error', (error) => {
114
+ this.emit('error', {
115
+ type: 'error',
116
+ error,
117
+ });
118
+ this.close();
119
+ });
120
+ this.on('audio_done', () => {
121
+ this.#twilioWebSocket.send(JSON.stringify({
122
+ event: 'mark',
123
+ mark: {
124
+ name: `done:${this.currentItemId}`,
125
+ },
126
+ streamSid: this.#streamSid,
127
+ }));
128
+ });
129
+ await super.connect(options);
130
+ }
131
+ _interrupt(_elapsedTime) {
132
+ const elapsedTime = this.#lastPlayedChunkCount + 50; /* 50ms buffer */
133
+ this.#logger.debug(`Interruption detected, clearing Twilio audio and truncating OpenAI audio after ${elapsedTime}ms`);
134
+ this.#twilioWebSocket.send(JSON.stringify({
135
+ event: 'clear',
136
+ streamSid: this.#streamSid,
137
+ }));
138
+ super._interrupt(elapsedTime);
139
+ }
140
+ _onAudio(audioEvent) {
141
+ this.#logger.debug(`Sending audio to Twilio ${audioEvent.responseId}: (${audioEvent.data.byteLength} bytes)`);
142
+ const audioDelta = {
143
+ event: 'media',
144
+ streamSid: this.#streamSid,
145
+ media: {
146
+ payload: utils.arrayBufferToBase64(audioEvent.data),
147
+ },
148
+ };
149
+ if (this.#previousItemId !== this.currentItemId && this.currentItemId) {
150
+ this.#previousItemId = this.currentItemId;
151
+ this.#audioChunkCount = 0;
152
+ }
153
+ this.#audioChunkCount += audioEvent.data.byteLength / 8;
154
+ this.#twilioWebSocket.send(JSON.stringify(audioDelta));
155
+ this.#twilioWebSocket.send(JSON.stringify({
156
+ event: 'mark',
157
+ streamSid: this.#streamSid,
158
+ mark: {
159
+ name: `${this.currentItemId}:${this.#audioChunkCount}`,
160
+ },
161
+ }));
162
+ this.emit('audio', audioEvent);
163
+ }
164
+ }
165
+ //# sourceMappingURL=TwilioRealtimeTransport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwilioRealtimeTransport.js","sourceRoot":"","sources":["../src/TwilioRealtimeTransport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,165 @@
1
+ import { OpenAIRealtimeWebSocket, utils, } from '@openai/agents/realtime';
2
+ import { getLogger } from '@openai/agents';
3
+ /**
4
+ * An adapter to connect a websocket that is receiving messages from Twilio's Media Streams API to
5
+ * the OpenAI Realtime API via WebSocket.
6
+ *
7
+ * It automatically handles setting the right audio format for the input and output audio, passing
8
+ * the data along and handling the timing for interruptions using Twilio's `mark` events.
9
+ *
10
+ * It does require you to run your own WebSocket server that is receiving connection requests from
11
+ * Twilio.
12
+ *
13
+ * It will emit all Twilio received messages as `twilio_message` type messages on the `*` handler.
14
+ * If you are using a `RealtimeSession` you can listen to the `transport_event`.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * const transport = new TwilioRealtimeTransportLayer({
19
+ * twilioWebSocket: twilioWebSocket,
20
+ * });
21
+ *
22
+ * transport.on('*', (event) => {
23
+ * if (event.type === 'twilio_message') {
24
+ * console.log('Twilio message:', event.data);
25
+ * }
26
+ * });
27
+ * ```
28
+ */
29
+ export class TwilioRealtimeTransportLayer extends OpenAIRealtimeWebSocket {
30
+ #twilioWebSocket;
31
+ #streamSid = null;
32
+ #audioChunkCount = 0;
33
+ #lastPlayedChunkCount = 0;
34
+ #previousItemId = null;
35
+ #logger = getLogger('openai-agents:extensions:twilio');
36
+ constructor(options) {
37
+ super(options);
38
+ this.#twilioWebSocket = options.twilioWebSocket;
39
+ }
40
+ _setInputAndOutputAudioFormat(partialConfig) {
41
+ let newConfig = {};
42
+ if (!partialConfig) {
43
+ newConfig.inputAudioFormat = 'g711_ulaw';
44
+ newConfig.outputAudioFormat = 'g711_ulaw';
45
+ }
46
+ else {
47
+ newConfig = {
48
+ ...partialConfig,
49
+ inputAudioFormat: partialConfig.inputAudioFormat ?? 'g711_ulaw',
50
+ outputAudioFormat: partialConfig.outputAudioFormat ?? 'g711_ulaw',
51
+ };
52
+ }
53
+ return newConfig;
54
+ }
55
+ async connect(options) {
56
+ options.initialSessionConfig = this._setInputAndOutputAudioFormat(options.initialSessionConfig);
57
+ // listen to Twilio messages as quickly as possible
58
+ this.#twilioWebSocket.on('message', (message) => {
59
+ try {
60
+ const data = JSON.parse(message.toString());
61
+ if (this.#logger.dontLogModelData) {
62
+ this.#logger.debug('Twilio message:', data.event);
63
+ }
64
+ else {
65
+ this.#logger.debug('Twilio message:', data);
66
+ }
67
+ this.emit('*', {
68
+ type: 'twilio_message',
69
+ message: data,
70
+ });
71
+ switch (data.event) {
72
+ case 'media':
73
+ if (this.status === 'connected') {
74
+ this.sendAudio(utils.base64ToArrayBuffer(data.media.payload));
75
+ }
76
+ break;
77
+ case 'mark':
78
+ if (!data.mark.name.startsWith('done:') &&
79
+ data.mark.name.includes(':')) {
80
+ // keeping track of what the last chunk was that the user heard fully
81
+ const count = Number(data.mark.name.split(':')[1]);
82
+ if (Number.isFinite(count)) {
83
+ this.#lastPlayedChunkCount = count;
84
+ }
85
+ else {
86
+ this.#logger.warn('Invalid mark name received:', data.mark.name);
87
+ }
88
+ }
89
+ else if (data.mark.name.startsWith('done:')) {
90
+ this.#lastPlayedChunkCount = 0;
91
+ }
92
+ break;
93
+ case 'start':
94
+ this.#streamSid = data.start.streamSid;
95
+ break;
96
+ default:
97
+ break;
98
+ }
99
+ }
100
+ catch (error) {
101
+ this.#logger.error('Error parsing message:', error, 'Message:', message);
102
+ this.emit('error', {
103
+ type: 'error',
104
+ error,
105
+ });
106
+ }
107
+ });
108
+ this.#twilioWebSocket.on('close', () => {
109
+ if (this.status !== 'disconnected') {
110
+ this.close();
111
+ }
112
+ });
113
+ this.#twilioWebSocket.on('error', (error) => {
114
+ this.emit('error', {
115
+ type: 'error',
116
+ error,
117
+ });
118
+ this.close();
119
+ });
120
+ this.on('audio_done', () => {
121
+ this.#twilioWebSocket.send(JSON.stringify({
122
+ event: 'mark',
123
+ mark: {
124
+ name: `done:${this.currentItemId}`,
125
+ },
126
+ streamSid: this.#streamSid,
127
+ }));
128
+ });
129
+ await super.connect(options);
130
+ }
131
+ _interrupt(_elapsedTime) {
132
+ const elapsedTime = this.#lastPlayedChunkCount + 50; /* 50ms buffer */
133
+ this.#logger.debug(`Interruption detected, clearing Twilio audio and truncating OpenAI audio after ${elapsedTime}ms`);
134
+ this.#twilioWebSocket.send(JSON.stringify({
135
+ event: 'clear',
136
+ streamSid: this.#streamSid,
137
+ }));
138
+ super._interrupt(elapsedTime);
139
+ }
140
+ _onAudio(audioEvent) {
141
+ this.#logger.debug(`Sending audio to Twilio ${audioEvent.responseId}: (${audioEvent.data.byteLength} bytes)`);
142
+ const audioDelta = {
143
+ event: 'media',
144
+ streamSid: this.#streamSid,
145
+ media: {
146
+ payload: utils.arrayBufferToBase64(audioEvent.data),
147
+ },
148
+ };
149
+ if (this.#previousItemId !== this.currentItemId && this.currentItemId) {
150
+ this.#previousItemId = this.currentItemId;
151
+ this.#audioChunkCount = 0;
152
+ }
153
+ this.#audioChunkCount += audioEvent.data.byteLength / 8;
154
+ this.#twilioWebSocket.send(JSON.stringify(audioDelta));
155
+ this.#twilioWebSocket.send(JSON.stringify({
156
+ event: 'mark',
157
+ streamSid: this.#streamSid,
158
+ mark: {
159
+ name: `${this.currentItemId}:${this.#audioChunkCount}`,
160
+ },
161
+ }));
162
+ this.emit('audio', audioEvent);
163
+ }
164
+ }
165
+ //# sourceMappingURL=TwilioRealtimeTransport.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TwilioRealtimeTransport.mjs","sourceRoot":"","sources":["../src/TwilioRealtimeTransport.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { LanguageModelV1 } from '@ai-sdk/provider';
2
+ import { Model, ModelRequest, ResponseStreamEvent, Usage } from '@openai/agents';
3
+ /**
4
+ * Wraps a model from the AI SDK that adheres to the LanguageModelV1 spec to be used used as a model
5
+ * in the OpenAI Agents SDK to use other models.
6
+ *
7
+ * While you can use this with the OpenAI models, it is recommended to use the default OpenAI model
8
+ * provider instead.
9
+ *
10
+ * If tracing is enabled, the model will send generation spans to your traces processor.
11
+ *
12
+ * ```ts
13
+ * import { aisdk } from '@openai/agents-extensions';
14
+ * import { openai } from '@ai-sdk/openai';
15
+ *
16
+ * const model = aisdk(openai('gpt-4o'));
17
+ *
18
+ * const agent = new Agent({
19
+ * name: 'My Agent',
20
+ * model
21
+ * });
22
+ * ```
23
+ *
24
+ * @param model - The Vercel AI SDK model to wrap.
25
+ * @returns The wrapped model.
26
+ */
27
+ export declare class AiSdkModel implements Model {
28
+ #private;
29
+ constructor(model: LanguageModelV1);
30
+ getResponse(request: ModelRequest): Promise<{
31
+ responseId: string;
32
+ usage: Usage;
33
+ output: import("@openai/agents").AgentOutputItem[];
34
+ }>;
35
+ getStreamedResponse(request: ModelRequest): AsyncIterable<ResponseStreamEvent>;
36
+ }
37
+ /**
38
+ * Wraps a model from the AI SDK that adheres to the LanguageModelV1 spec to be used used as a model
39
+ * in the OpenAI Agents SDK to use other models.
40
+ *
41
+ * While you can use this with the OpenAI models, it is recommended to use the default OpenAI model
42
+ * provider instead.
43
+ *
44
+ * If tracing is enabled, the model will send generation spans to your traces processor.
45
+ *
46
+ * ```ts
47
+ * import { aisdk } from '@openai/agents-extensions';
48
+ * import { openai } from '@ai-sdk/openai';
49
+ *
50
+ * const model = aisdk(openai('gpt-4o'));
51
+ *
52
+ * const agent = new Agent({
53
+ * name: 'My Agent',
54
+ * model
55
+ * });
56
+ * ```
57
+ *
58
+ * @param model - The Vercel AI SDK model to wrap.
59
+ * @returns The wrapped model.
60
+ */
61
+ export declare function aisdk(model: LanguageModelV1): AiSdkModel;