@pipedog/openai 0.0.0 → 0.3.0
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 +25 -0
- package/NOTICE +9 -0
- package/dist/_constants.d.ts +4 -0
- package/dist/_constants.js +9 -0
- package/dist/base_llm.d.ts +272 -0
- package/dist/base_llm.js +619 -0
- package/dist/image.d.ts +116 -0
- package/dist/image.js +179 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +12 -0
- package/dist/live/events.d.ts +768 -0
- package/dist/live/events.js +1020 -0
- package/dist/live/index.d.ts +2 -0
- package/dist/live/index.js +8 -0
- package/dist/live/llm.d.ts +566 -0
- package/dist/live/llm.js +1652 -0
- package/dist/llm.d.ts +30 -0
- package/dist/llm.js +50 -0
- package/dist/open_ai_live_adapter.d.ts +94 -0
- package/dist/open_ai_live_adapter.js +192 -0
- package/dist/open_ai_realtime_adapter.d.ts +95 -0
- package/dist/open_ai_realtime_adapter.js +221 -0
- package/dist/open_ai_responses_adapter.d.ts +95 -0
- package/dist/open_ai_responses_adapter.js +358 -0
- package/dist/realtime/events.d.ts +1401 -0
- package/dist/realtime/events.js +1639 -0
- package/dist/realtime/index.d.ts +2 -0
- package/dist/realtime/index.js +8 -0
- package/dist/realtime/llm.d.ts +457 -0
- package/dist/realtime/llm.js +1550 -0
- package/dist/responses/index.d.ts +2 -0
- package/dist/responses/index.js +8 -0
- package/dist/responses/llm.d.ts +448 -0
- package/dist/responses/llm.js +1517 -0
- package/dist/stt.d.ts +409 -0
- package/dist/stt.js +741 -0
- package/dist/tts.d.ts +86 -0
- package/dist/tts.js +238 -0
- package/package.json +49 -3
- package/README.md +0 -5
package/dist/tts.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type Frame, type StartFrame } from "@pipedog/core/frames/frames";
|
|
2
|
+
import { TTSSettings, type TTSSettingsOptions } from "@pipedog/core/services/settings";
|
|
3
|
+
import { TTSService, type TTSServiceOptions } from "@pipedog/core/services/tts_service";
|
|
4
|
+
import { type NotGiven } from "@pipedog/core/utils/types";
|
|
5
|
+
import OpenAI from "openai";
|
|
6
|
+
export type ValidVoice = "alloy" | "ash" | "ballad" | "cedar" | "coral" | "echo" | "fable" | "marin" | "nova" | "onyx" | "sage" | "shimmer" | "verse";
|
|
7
|
+
export declare const VALID_VOICES: Readonly<Record<string, ValidVoice>>;
|
|
8
|
+
export interface OpenAITTSSettingsOptions extends TTSSettingsOptions {
|
|
9
|
+
/** Instructions to guide voice synthesis behavior. */
|
|
10
|
+
instructions?: string | null | NotGiven;
|
|
11
|
+
/** Voice speed control (0.25 to 4.0, default 1.0). */
|
|
12
|
+
speed?: number | null | NotGiven;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Settings for OpenAITTSService.
|
|
16
|
+
*
|
|
17
|
+
* - `instructions`: instructions to guide voice synthesis behavior.
|
|
18
|
+
* - `speed`: voice speed control (0.25 to 4.0, default 1.0).
|
|
19
|
+
*/
|
|
20
|
+
export declare class OpenAITTSSettings extends TTSSettings {
|
|
21
|
+
instructions: string | null | NotGiven;
|
|
22
|
+
speed: number | null | NotGiven;
|
|
23
|
+
constructor(options?: OpenAITTSSettingsOptions);
|
|
24
|
+
}
|
|
25
|
+
export interface OpenAITTSServiceOptions extends TTSServiceOptions {
|
|
26
|
+
/** OpenAI API key for authentication. If null, uses environment variable. */
|
|
27
|
+
apiKey?: string | null;
|
|
28
|
+
/** Custom base URL for OpenAI API. If null, uses default. */
|
|
29
|
+
baseUrl?: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Custom `fetch` for API requests, e.g. one with a longer request timeout.
|
|
32
|
+
* Defaults to null, which lets the SDK use its own.
|
|
33
|
+
*
|
|
34
|
+
* DIVERGENCE: upstream takes an httpx client here.
|
|
35
|
+
*/
|
|
36
|
+
httpClient?: typeof fetch | null;
|
|
37
|
+
/** Runtime-updatable settings. */
|
|
38
|
+
settings?: OpenAITTSSettings | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* OpenAI Text-to-Speech service that generates audio from text.
|
|
42
|
+
*
|
|
43
|
+
* This service uses the OpenAI TTS API to generate PCM-encoded audio at
|
|
44
|
+
* 24kHz. Supports multiple voice models and configurable parameters for
|
|
45
|
+
* high-quality speech synthesis with streaming audio output.
|
|
46
|
+
*/
|
|
47
|
+
export declare class OpenAITTSService extends TTSService {
|
|
48
|
+
static Settings: typeof OpenAITTSSettings;
|
|
49
|
+
protected _settings: OpenAITTSSettings;
|
|
50
|
+
protected _client: OpenAI;
|
|
51
|
+
/**
|
|
52
|
+
* Initialize OpenAI TTS service.
|
|
53
|
+
*
|
|
54
|
+
* @param options.apiKey OpenAI API key for authentication. If null, uses
|
|
55
|
+
* environment variable.
|
|
56
|
+
* @param options.baseUrl Custom base URL for OpenAI API. If null, uses default.
|
|
57
|
+
* @param options.httpClient Custom `fetch` for API requests.
|
|
58
|
+
* @param options.sampleRate Output audio sample rate in Hz. If null, uses
|
|
59
|
+
* OpenAI's default 24kHz.
|
|
60
|
+
* @param options.settings Runtime-updatable settings. Its given fields
|
|
61
|
+
* override the defaults (model "gpt-4o-mini-tts", voice "alloy").
|
|
62
|
+
* Other options are passed to TTSService.
|
|
63
|
+
*/
|
|
64
|
+
constructor(options?: OpenAITTSServiceOptions);
|
|
65
|
+
/**
|
|
66
|
+
* Check if this service can generate processing metrics.
|
|
67
|
+
*
|
|
68
|
+
* @returns True, as OpenAI TTS service supports metrics generation.
|
|
69
|
+
*/
|
|
70
|
+
canGenerateMetrics(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Start the OpenAI TTS service.
|
|
73
|
+
*
|
|
74
|
+
* @param frame The start frame containing initialization parameters.
|
|
75
|
+
*/
|
|
76
|
+
start(frame: StartFrame): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Generate speech from text using OpenAI's TTS API.
|
|
79
|
+
*
|
|
80
|
+
* @param text The text to synthesize into speech.
|
|
81
|
+
* @param contextId The context ID for tracking audio frames.
|
|
82
|
+
* @returns Audio frames containing the synthesized speech data.
|
|
83
|
+
*/
|
|
84
|
+
runTts(text: string, contextId: string): AsyncGenerator<Frame, void>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=tts.d.ts.map
|
package/dist/tts.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/services/openai/tts.py
|
|
6
|
+
/**
|
|
7
|
+
* OpenAI text-to-speech service implementation.
|
|
8
|
+
*
|
|
9
|
+
* This module provides integration with OpenAI's text-to-speech API for
|
|
10
|
+
* generating high-quality synthetic speech from text input.
|
|
11
|
+
*
|
|
12
|
+
* Porting notes:
|
|
13
|
+
* - The client is `OpenAI` from the `openai` JS SDK (fetch based). The
|
|
14
|
+
* streamed body is read from the `Response` the SDK returns, re-chunked to
|
|
15
|
+
* `chunkSize` bytes as `iter_bytes(chunk_size)` does.
|
|
16
|
+
* - `httpClient` is the SDK's `fetch` option (upstream: an httpx client).
|
|
17
|
+
* - Deprecated APIs are not ported: `OpenAITTSService.InputParams` and the
|
|
18
|
+
* `voice`, `model`, `instructions`, `speed` and `params` constructor
|
|
19
|
+
* options.
|
|
20
|
+
*/
|
|
21
|
+
import { abortable, currentSignal, rethrowIfCancelled } from "@pipedog/asyncio";
|
|
22
|
+
import { ErrorFrame, TTSAudioRawFrame, } from "@pipedog/core/frames/frames";
|
|
23
|
+
import { TTSSettings } from "@pipedog/core/services/settings";
|
|
24
|
+
import { TTSService } from "@pipedog/core/services/tts_service";
|
|
25
|
+
import { logger } from "@pipedog/core/utils/logger";
|
|
26
|
+
import { installTracedTts } from "@pipedog/core/utils/tracing/service_decorators";
|
|
27
|
+
import { assertGiven, NOT_GIVEN } from "@pipedog/core/utils/types";
|
|
28
|
+
import OpenAI, { BadRequestError } from "openai";
|
|
29
|
+
import { OPENAI_SAMPLE_RATE } from "./_constants.js";
|
|
30
|
+
export const VALID_VOICES = {
|
|
31
|
+
alloy: "alloy",
|
|
32
|
+
ash: "ash",
|
|
33
|
+
ballad: "ballad",
|
|
34
|
+
cedar: "cedar",
|
|
35
|
+
coral: "coral",
|
|
36
|
+
echo: "echo",
|
|
37
|
+
fable: "fable",
|
|
38
|
+
marin: "marin",
|
|
39
|
+
nova: "nova",
|
|
40
|
+
onyx: "onyx",
|
|
41
|
+
sage: "sage",
|
|
42
|
+
shimmer: "shimmer",
|
|
43
|
+
verse: "verse",
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Settings for OpenAITTSService.
|
|
47
|
+
*
|
|
48
|
+
* - `instructions`: instructions to guide voice synthesis behavior.
|
|
49
|
+
* - `speed`: voice speed control (0.25 to 4.0, default 1.0).
|
|
50
|
+
*/
|
|
51
|
+
export class OpenAITTSSettings extends TTSSettings {
|
|
52
|
+
instructions = NOT_GIVEN;
|
|
53
|
+
speed = NOT_GIVEN;
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
super(options);
|
|
56
|
+
this._assignOptions(options);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** Yield the bytes of a body stream in chunks of exactly `chunkSize` (the last may be shorter). */
|
|
60
|
+
async function* iterBytes(body, chunkSize) {
|
|
61
|
+
if (!body)
|
|
62
|
+
return;
|
|
63
|
+
const reader = body.getReader();
|
|
64
|
+
let buffer = new Uint8Array(0);
|
|
65
|
+
try {
|
|
66
|
+
while (true) {
|
|
67
|
+
const { done, value } = await abortable(reader.read());
|
|
68
|
+
if (done)
|
|
69
|
+
break;
|
|
70
|
+
if (!value || value.length === 0)
|
|
71
|
+
continue;
|
|
72
|
+
const merged = new Uint8Array(buffer.length + value.length);
|
|
73
|
+
merged.set(buffer);
|
|
74
|
+
merged.set(value, buffer.length);
|
|
75
|
+
buffer = merged;
|
|
76
|
+
while (buffer.length >= chunkSize) {
|
|
77
|
+
yield buffer.slice(0, chunkSize);
|
|
78
|
+
buffer = buffer.slice(chunkSize);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (buffer.length > 0) {
|
|
82
|
+
yield buffer;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
try {
|
|
87
|
+
await reader.cancel();
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
rethrowIfCancelled(e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* OpenAI Text-to-Speech service that generates audio from text.
|
|
96
|
+
*
|
|
97
|
+
* This service uses the OpenAI TTS API to generate PCM-encoded audio at
|
|
98
|
+
* 24kHz. Supports multiple voice models and configurable parameters for
|
|
99
|
+
* high-quality speech synthesis with streaming audio output.
|
|
100
|
+
*/
|
|
101
|
+
export class OpenAITTSService extends TTSService {
|
|
102
|
+
static Settings = OpenAITTSSettings;
|
|
103
|
+
static {
|
|
104
|
+
installTracedTts(OpenAITTSService.prototype, "runTts");
|
|
105
|
+
}
|
|
106
|
+
_client;
|
|
107
|
+
/**
|
|
108
|
+
* Initialize OpenAI TTS service.
|
|
109
|
+
*
|
|
110
|
+
* @param options.apiKey OpenAI API key for authentication. If null, uses
|
|
111
|
+
* environment variable.
|
|
112
|
+
* @param options.baseUrl Custom base URL for OpenAI API. If null, uses default.
|
|
113
|
+
* @param options.httpClient Custom `fetch` for API requests.
|
|
114
|
+
* @param options.sampleRate Output audio sample rate in Hz. If null, uses
|
|
115
|
+
* OpenAI's default 24kHz.
|
|
116
|
+
* @param options.settings Runtime-updatable settings. Its given fields
|
|
117
|
+
* override the defaults (model "gpt-4o-mini-tts", voice "alloy").
|
|
118
|
+
* Other options are passed to TTSService.
|
|
119
|
+
*/
|
|
120
|
+
constructor(options = {}) {
|
|
121
|
+
const sampleRate = options.sampleRate ?? null;
|
|
122
|
+
if (sampleRate && sampleRate !== OPENAI_SAMPLE_RATE) {
|
|
123
|
+
logger.warning(`OpenAI TTS only supports ${OPENAI_SAMPLE_RATE}Hz sample rate. ` +
|
|
124
|
+
`Current rate of ${sampleRate}Hz may cause issues.`);
|
|
125
|
+
}
|
|
126
|
+
// 1. Initialize default_settings with hardcoded defaults
|
|
127
|
+
const defaultSettings = new new.target.Settings({
|
|
128
|
+
model: "gpt-4o-mini-tts",
|
|
129
|
+
voice: "alloy",
|
|
130
|
+
language: null,
|
|
131
|
+
instructions: null,
|
|
132
|
+
speed: null,
|
|
133
|
+
});
|
|
134
|
+
// 2. Apply settings delta (canonical API, always wins)
|
|
135
|
+
if (options.settings !== undefined && options.settings !== null) {
|
|
136
|
+
defaultSettings.applyUpdate(options.settings);
|
|
137
|
+
}
|
|
138
|
+
super({
|
|
139
|
+
...options,
|
|
140
|
+
sampleRate,
|
|
141
|
+
pushStartFrame: true,
|
|
142
|
+
pushStopFrames: true,
|
|
143
|
+
settings: defaultSettings,
|
|
144
|
+
});
|
|
145
|
+
this._client = new OpenAI({
|
|
146
|
+
apiKey: options.apiKey ?? undefined,
|
|
147
|
+
baseURL: options.baseUrl ?? undefined,
|
|
148
|
+
...(options.httpClient ? { fetch: options.httpClient } : {}),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if this service can generate processing metrics.
|
|
153
|
+
*
|
|
154
|
+
* @returns True, as OpenAI TTS service supports metrics generation.
|
|
155
|
+
*/
|
|
156
|
+
canGenerateMetrics() {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Start the OpenAI TTS service.
|
|
161
|
+
*
|
|
162
|
+
* @param frame The start frame containing initialization parameters.
|
|
163
|
+
*/
|
|
164
|
+
async start(frame) {
|
|
165
|
+
await super.start(frame);
|
|
166
|
+
if (this.sampleRate !== OPENAI_SAMPLE_RATE) {
|
|
167
|
+
logger.warning(`OpenAI TTS requires ${OPENAI_SAMPLE_RATE}Hz sample rate. ` +
|
|
168
|
+
`Current rate of ${this.sampleRate}Hz may cause issues.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Generate speech from text using OpenAI's TTS API.
|
|
173
|
+
*
|
|
174
|
+
* @param text The text to synthesize into speech.
|
|
175
|
+
* @param contextId The context ID for tracking audio frames.
|
|
176
|
+
* @returns Audio frames containing the synthesized speech data.
|
|
177
|
+
*/
|
|
178
|
+
async *runTts(text, contextId) {
|
|
179
|
+
const voice = assertGiven(this._settings.voice);
|
|
180
|
+
if (voice === null) {
|
|
181
|
+
yield new ErrorFrame({ error: "OpenAI TTS voice must be specified" });
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!Object.hasOwn(VALID_VOICES, voice)) {
|
|
185
|
+
yield new ErrorFrame({
|
|
186
|
+
error: `OpenAI TTS voice '${voice}' is not supported ` +
|
|
187
|
+
`(must be one of: ${Object.keys(VALID_VOICES).sort().join(", ")})`,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
// Setup API parameters
|
|
193
|
+
const createParams = {
|
|
194
|
+
input: text,
|
|
195
|
+
model: this._settings.model,
|
|
196
|
+
voice: VALID_VOICES[voice],
|
|
197
|
+
response_format: "pcm",
|
|
198
|
+
};
|
|
199
|
+
if (this._settings.instructions) {
|
|
200
|
+
createParams.instructions = this._settings.instructions;
|
|
201
|
+
}
|
|
202
|
+
if (this._settings.speed) {
|
|
203
|
+
createParams.speed = this._settings.speed;
|
|
204
|
+
}
|
|
205
|
+
const r = await abortable(this._client.audio.speech.create(createParams, { signal: currentSignal() }));
|
|
206
|
+
if (r.status !== 200) {
|
|
207
|
+
const error = await abortable(r.text());
|
|
208
|
+
logger.error(`${this} error getting audio (status: ${r.status}, error: ${error})`);
|
|
209
|
+
yield new ErrorFrame({
|
|
210
|
+
error: `Error getting audio (status: ${r.status}, error: ${error})`,
|
|
211
|
+
});
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
await this.startTtsUsageMetrics(text);
|
|
215
|
+
const CHUNK_SIZE = this.chunkSize;
|
|
216
|
+
for await (const chunk of iterBytes(r.body, CHUNK_SIZE)) {
|
|
217
|
+
if (chunk.length > 0) {
|
|
218
|
+
await this.stopTtfbMetrics();
|
|
219
|
+
const frame = new TTSAudioRawFrame({
|
|
220
|
+
audio: chunk,
|
|
221
|
+
sampleRate: this.sampleRate,
|
|
222
|
+
numChannels: 1,
|
|
223
|
+
contextId,
|
|
224
|
+
});
|
|
225
|
+
yield frame;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
if (e instanceof BadRequestError) {
|
|
231
|
+
yield new ErrorFrame({ error: `Unknown error occurred: ${e.message}` });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
throw e;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=tts.js.map
|
package/package.json
CHANGED
|
@@ -1,12 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pipedog/openai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "OpenAI services (LLM, Responses, Realtime, Live, STT, TTS, image) for pipedog.",
|
|
5
5
|
"license": "BSD-2-Clause",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/polvallverdu/pipedog.git",
|
|
9
9
|
"directory": "packages/openai"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://github.com/polvallverdu/pipedog/tree/main/packages/openai#readme"
|
|
11
|
+
"homepage": "https://github.com/polvallverdu/pipedog/tree/main/packages/openai#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/polvallverdu/pipedog/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": false,
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
"./*": {
|
|
23
|
+
"types": "./dist/*.d.ts",
|
|
24
|
+
"default": "./dist/*.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"NOTICE"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"test": "bun test"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@pipedog/asyncio": "0.3.0",
|
|
37
|
+
"@pipedog/core": "0.3.0",
|
|
38
|
+
"@pipedog/whisper": "0.3.0",
|
|
39
|
+
"fast-png": "^8.0.0",
|
|
40
|
+
"openai": "^6.49.0"
|
|
41
|
+
},
|
|
42
|
+
"pipecat": {
|
|
43
|
+
"runtimes": [
|
|
44
|
+
"bun",
|
|
45
|
+
"node",
|
|
46
|
+
"deno",
|
|
47
|
+
"workerd"
|
|
48
|
+
],
|
|
49
|
+
"upstream": [
|
|
50
|
+
"src/pipecat/services/openai",
|
|
51
|
+
"src/pipecat/adapters/services/open_ai_live_adapter.py",
|
|
52
|
+
"src/pipecat/adapters/services/open_ai_realtime_adapter.py",
|
|
53
|
+
"src/pipecat/adapters/services/open_ai_responses_adapter.py"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"main": "./dist/index.js",
|
|
57
|
+
"types": "./dist/index.d.ts"
|
|
12
58
|
}
|