@breeze.blue/sdk 0.4.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,10 +1,67 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ - Fixed `textToSpeech.realtime.connect(...)` so a failed WebSocket handshake
6
+ rejects instead of hanging: added a handshake timeout (connect `timeout`,
7
+ falling back to the client `timeout`), rejected clean closes that arrive
8
+ before the socket opens, and closed the socket on every failure path.
9
+ - Breaking (compile time): the client constructor accepts a missing API key,
10
+ so `BreezeBlueClient.apiKey` is now typed `string | undefined` instead of
11
+ `string`. The constructor no longer throws on a missing key; the first REST
12
+ call throws `BreezeBlueConfigurationError` instead, so browser clients can
13
+ use `textToSpeech.realtime.connect({ clientSecret })` without a key. Code
14
+ that read `client.apiKey` as a `string` must narrow it first, and code that
15
+ relied on the constructor throwing must handle the error at the first
16
+ request.
17
+ - Bounded the realtime receive buffer at 16 MiB. If messages are not consumed,
18
+ the SDK closes the connection and the message iterator rejects with a
19
+ `BreezeBlueRealtimeError`.
20
+ - Added `BreezeBlueRealtimeError` for realtime connection failures the SDK
21
+ detects (invalid server frames, receive-buffer overflow). It carries the
22
+ same fields as the Python SDK's `RealtimeError` — `code`, `meta`,
23
+ `closeCode`, `reconnect`, and `recoverable` (`true` only when just the
24
+ active turn was cancelled and the connection stays usable; SDK-detected
25
+ connection failures are never recoverable).
26
+ - Fixed invalid JSON text frames on a realtime connection: instead of the
27
+ `JSON.parse` exception escaping from the WebSocket `onmessage` callback
28
+ (leaving the connection neither failed nor finished), the SDK now fails the
29
+ connection and the message iterator rejects with a
30
+ `BreezeBlueRealtimeError`, matching the Python SDK.
31
+ - Added `connection.ping()` for realtime keepalive; the server answers with a
32
+ `pong` event.
33
+ - Guarded `connection.close()` so it no longer throws when the underlying
34
+ WebSocket is not open.
35
+ - Breaking (compile time): `RealtimeTextToSpeechEvent` changed from an open
36
+ record (`{ type: string; [key: string]: unknown }`) to a discriminated
37
+ union of per-event interfaces with camelCase fields (`turnId`,
38
+ `historyItemId`, `ttfaMs`, ...). Code that indexed arbitrary keys on an
39
+ event must narrow on `event.type` first (or cast). The union is
40
+ non-exhaustive by design: unknown future event types are still delivered
41
+ at runtime, so do not rely on an exhaustive `switch` — ignore event types
42
+ you do not recognize.
43
+ - Exported the `RealtimeTextToSpeechConnection` class from the root
44
+ entrypoint.
45
+ - `realtime.connect` now warns when session parameters (`modelId`,
46
+ `voiceSettings`, ...) are passed together with `clientSecret` or
47
+ `websocketUrl`, which ignore them.
48
+ - Clarified the missing-WebSocket error and README: realtime needs a global
49
+ WebSocket (browsers, edge runtimes, Node 22+) or a `webSocket` constructor
50
+ passed to the client.
51
+ - Reworked the README realtime examples into a consume-and-close loop and
52
+ added keepalive, pre-created session, and browser `clientSecret` handoff
53
+ examples.
54
+
3
55
  ## 0.4.1
4
56
 
5
57
  - Renamed the instruction-only text-to-speech helper to
6
58
  `client.textToSpeech.enhance(...)` and aligned it with
7
59
  `/v1/text-to-speech/enhance`.
60
+ - Documented the streaming text-to-speech default as `pcm`, added `wav`
61
+ streaming examples, and taught the Node `stream(...)` helper to pass raw PCM
62
+ playback options to mpv.
63
+ - Added realtime conversation text-to-speech helpers under
64
+ `client.textToSpeech.realtime` for fixed-PCM WebSocket sessions.
8
65
 
9
66
  ## 0.4.0
10
67
 
package/README.md CHANGED
@@ -30,6 +30,10 @@ export BREEZE_API_KEY=brz_...
30
30
  The SDK sends the key with the `xi-api-key` header. It also sends
31
31
  `x-breeze-sdk` for server-side observability.
32
32
 
33
+ An API key is required for every REST method. The one exception is
34
+ `textToSpeech.realtime.connect(...)` with a `clientSecret`, which lets a
35
+ browser client run without a key (see the realtime section below).
36
+
33
37
  ## Quickstart
34
38
 
35
39
  ```ts
@@ -100,6 +104,102 @@ if (status.status === "ready") {
100
104
  If the job is still active, `downloadAudio(...)` rejects with
101
105
  `BreezeBlueGenerationNotReadyError`; read `error.retryAfter` before retrying.
102
106
 
107
+ Use realtime text-to-speech when one WebSocket connection should handle multiple
108
+ conversation turns. Realtime audio is fixed to raw `pcm_s16le`, 24000 Hz, mono,
109
+ 16-bit frames.
110
+
111
+ Realtime needs a `WebSocket` implementation. Browsers, edge runtimes, and
112
+ Node 22+ provide a global one; on Node 20, pass a constructor (for example the
113
+ `ws` package's `WebSocket`) via `new BreezeBlueClient({ webSocket })`.
114
+
115
+ ```ts
116
+ const connection = await client.textToSpeech.realtime.connect("voc_...", {
117
+ modelId: "bluebell-v1",
118
+ });
119
+
120
+ connection.startTurn("turn_1");
121
+ connection.appendText("Hello from Breeze.");
122
+ connection.flush();
123
+ connection.endTurn();
124
+
125
+ const pcmChunks: Uint8Array[] = [];
126
+ for await (const message of connection) {
127
+ if (message.type === "audio") {
128
+ pcmChunks.push(message.audio);
129
+ continue;
130
+ }
131
+ if (message.type === "error") {
132
+ throw new Error(`Realtime TTS failed: ${message.code}: ${message.message}`);
133
+ }
134
+ if (message.type === "turn.done") {
135
+ break; // all audio for this turn has been delivered
136
+ }
137
+ }
138
+ connection.close();
139
+ ```
140
+
141
+ Always consume the connection (or its `audio()` / `events()` iterators) while a
142
+ turn is active, and call `close()` when you are done. When the SDK detects a
143
+ connection failure — an invalid server frame, or more than 16 MiB of messages
144
+ piling up unconsumed — it closes the connection and the iterator rejects with
145
+ a `BreezeBlueRealtimeError`.
146
+
147
+ Events are a typed discriminated union (`session.ready`, `turn.started`,
148
+ `audio.started`, `turn.done`, `turn.cancelled`, `usage.committed`,
149
+ `session.closed`, `error`, `pong`), with camelCase fields such as `turnId`,
150
+ `historyItemId`, and `ttfaMs`. Treat the union as non-exhaustive: the server
151
+ may add event types, and the SDK delivers unknown JSON events unchanged —
152
+ ignore event types you do not recognize instead of switching exhaustively.
153
+
154
+ For connections that sit idle between turns, send a keepalive ping inside the
155
+ session's `inactivityTimeoutSeconds` window; the server answers with a `pong`
156
+ event:
157
+
158
+ ```ts
159
+ const keepalive = setInterval(() => connection.ping(), 30_000);
160
+ // ... run turns ...
161
+ clearInterval(keepalive);
162
+ connection.close();
163
+ ```
164
+
165
+ To cut time to first audio, create the session ahead of time (for example while
166
+ your app is still preparing the turn) and connect with its `clientSecret` when
167
+ the first text is ready — only the WebSocket handshake remains:
168
+
169
+ ```ts
170
+ const session = await client.textToSpeech.realtime.createSession("voc_...", {
171
+ modelId: "bluebell-v1",
172
+ });
173
+
174
+ // Later, when the first text is ready:
175
+ const connection = await client.textToSpeech.realtime.connect("voc_...", {
176
+ clientSecret: session.clientSecret,
177
+ });
178
+ ```
179
+
180
+ The same `clientSecret` handoff lets a browser connect without ever seeing your
181
+ API key: create the session on your server, hand `session.clientSecret` to the
182
+ page, and build a key-less client there:
183
+
184
+ ```ts
185
+ // Browser — no API key required for clientSecret connections.
186
+ const browserClient = new BreezeBlueClient();
187
+ const connection = await browserClient.textToSpeech.realtime.connect("voc_...", {
188
+ clientSecret, // received from your server
189
+ });
190
+ ```
191
+
192
+ Session parameters (`modelId`, `languageCode`, `instructions`, `voiceSettings`,
193
+ `inactivityTimeoutSeconds`, `enableLogging`) are fixed when the session is
194
+ created. `connect` ignores them when `clientSecret` or `websocketUrl` is
195
+ provided and logs a warning.
196
+
197
+ If a realtime WebSocket is interrupted by a network change, service deployment,
198
+ or upstream realtime worker restart, handle `error.meta.reconnect === true` or a
199
+ `session.closed` event with `reconnect === true` by creating a new connection and
200
+ starting a new turn from your own conversation state. Active turns are not
201
+ resumed in place.
202
+
103
203
  The API uses the default text-to-speech model when `modelId` is omitted. If
104
204
  you need to select a model explicitly, call `client.models.list()` and pass one
105
205
  of the returned `modelId` values.
@@ -130,6 +230,10 @@ await save(audio, "x.mp3");
130
230
  await stream(audioStream); // mpv
131
231
  ```
132
232
 
233
+ Streaming text-to-speech defaults to `pcm` to reduce time to first audio. Pass
234
+ `{ outputFormat: "wav" }` or `{ outputFormat: "mp3" }` when you need that wire
235
+ format explicitly.
236
+
133
237
  ## Voices
134
238
 
135
239
  Browse existing voices and inspect a single voice:
package/dist/audio.d.ts CHANGED
@@ -10,6 +10,12 @@ export interface StreamPlaybackCommand {
10
10
  command: string;
11
11
  args: string[];
12
12
  }
13
+ export interface StreamPlaybackCommandOptions {
14
+ contentType?: string | null;
15
+ sampleRate?: number | null;
16
+ channels?: number | null;
17
+ sampleFormat?: string | null;
18
+ }
13
19
  export interface AudioResponseOptions {
14
20
  filename?: string | null;
15
21
  }
@@ -45,5 +51,5 @@ export declare function selectAudioPlaybackCommand(params: {
45
51
  availability: CommandAvailability;
46
52
  platform: string;
47
53
  }): PlaybackCommand;
48
- export declare function selectStreamPlaybackCommand(): StreamPlaybackCommand;
54
+ export declare function selectStreamPlaybackCommand(options?: StreamPlaybackCommandOptions): StreamPlaybackCommand;
49
55
  export declare function extensionForContentType(contentType: string): string;
package/dist/audio.js CHANGED
@@ -55,8 +55,17 @@ export function selectAudioPlaybackCommand(params) {
55
55
  }
56
56
  throw new Error("No supported audio player found. Install ffplay, or use afplay on macOS.");
57
57
  }
58
- export function selectStreamPlaybackCommand() {
59
- return { command: "mpv", args: ["--no-terminal", "--really-quiet", "-"] };
58
+ export function selectStreamPlaybackCommand(options = {}) {
59
+ const args = ["--no-terminal", "--really-quiet"];
60
+ if (isPcmContentType(options.contentType)) {
61
+ args.push("--demuxer=rawaudio", `--demuxer-rawaudio-rate=${options.sampleRate ?? 24000}`, `--demuxer-rawaudio-channels=${options.channels ?? 1}`, `--demuxer-rawaudio-format=${options.sampleFormat ?? "s16le"}`);
62
+ }
63
+ args.push("-");
64
+ return { command: "mpv", args };
65
+ }
66
+ function isPcmContentType(contentType) {
67
+ const mediaType = (contentType ?? "").split(";", 1)[0].trim().toLowerCase();
68
+ return mediaType === "audio/pcm" || mediaType === "audio/x-pcm" || mediaType.endsWith("/pcm");
60
69
  }
61
70
  function nodeHelperError(action) {
62
71
  return new Error(`Use @breeze.blue/sdk/node to ${action} in Node.js runtimes.`);
package/dist/client.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { AudioResponse } from "./audio.js";
2
- import type { AudioRequestOptions, AsyncTextToSpeechJob, Balance, BreezeBlueClientOptions, GenericStatus, GenerationJob, HistoryItem, HistoryList, HistoryListParams, Model, RequestOptions, SaveVoiceRequest, StreamTextToSpeechOptions, TextToSpeechRequest, TtsEnhanceRequest, TtsEnhanceResponse, Usage, UsageParams, Voice, VoiceClonePreview, VoiceClonePreviewRequest, VoiceDesignRequest, VoiceDesignResponse, VoiceEditRequest, VoiceList, VoiceSearchParams, VoiceSettings } from "./types.js";
2
+ import type { AudioRequestOptions, BreezeBlueWebSocket, AsyncTextToSpeechJob, Balance, BreezeBlueClientOptions, GenericStatus, GenerationJob, HistoryItem, HistoryList, HistoryListParams, Model, RequestOptions, RealtimeTextToSpeechConnectOptions, RealtimeTextToSpeechEvent, RealtimeTextToSpeechMessage, RealtimeTextToSpeechSession, RealtimeTextToSpeechSessionRequest, SavedVoice, SaveVoiceRequest, StreamTextToSpeechOptions, TextToSpeechRequest, TtsEnhanceRequest, TtsEnhanceResponse, Usage, UsageParams, Voice, VoiceClonePreview, VoiceClonePreviewRequest, VoiceDesignRequest, VoiceDesignResponse, VoiceEditRequest, VoiceList, VoiceSearchParams, VoiceSettings } from "./types.js";
3
3
  export declare class BreezeBlueClient {
4
- readonly apiKey: string;
4
+ readonly apiKey: string | undefined;
5
5
  readonly baseUrl: string;
6
6
  readonly fetch: typeof fetch;
7
+ readonly webSocket: BreezeBlueClientOptions["webSocket"];
7
8
  readonly headers: HeadersInit | undefined;
8
9
  readonly timeout: number | undefined;
9
10
  readonly textToSpeech: TextToSpeechResource;
@@ -20,12 +21,48 @@ export declare class BreezeBlueClient {
20
21
  }
21
22
  declare class TextToSpeechResource {
22
23
  private readonly client;
24
+ readonly realtime: RealtimeTextToSpeechResource;
23
25
  constructor(client: BreezeBlueClient);
24
26
  convert(voiceId: string, request: TextToSpeechRequest, options?: AudioRequestOptions): Promise<AudioResponse>;
25
27
  createJob(voiceId: string, request: TextToSpeechRequest, options?: AudioRequestOptions): Promise<AsyncTextToSpeechJob>;
26
28
  stream(voiceId: string, request: TextToSpeechRequest, options?: StreamTextToSpeechOptions): Promise<AudioResponse>;
27
29
  enhance(request: TtsEnhanceRequest, options?: RequestOptions): Promise<TtsEnhanceResponse>;
28
30
  }
31
+ declare class RealtimeTextToSpeechResource {
32
+ private readonly client;
33
+ constructor(client: BreezeBlueClient);
34
+ createSession(voiceId: string, request?: RealtimeTextToSpeechSessionRequest, options?: RequestOptions): Promise<RealtimeTextToSpeechSession>;
35
+ connect(voiceId: string, options?: RealtimeTextToSpeechConnectOptions): Promise<RealtimeTextToSpeechConnection>;
36
+ }
37
+ export declare class RealtimeTextToSpeechConnection implements AsyncIterable<RealtimeTextToSpeechMessage> {
38
+ private readonly socket;
39
+ private readonly queue;
40
+ private readonly waiters;
41
+ private closed;
42
+ private queuedBytes;
43
+ private failure;
44
+ private constructor();
45
+ static open(socket: BreezeBlueWebSocket, options?: {
46
+ timeout?: number;
47
+ }): Promise<RealtimeTextToSpeechConnection>;
48
+ startTurn(turnId?: string): void;
49
+ appendText(text: string): void;
50
+ flush(): void;
51
+ endTurn(): void;
52
+ cancelTurn(): void;
53
+ /** Send a keepalive ping. The server answers with a `pong` event. */
54
+ ping(): void;
55
+ close(): void;
56
+ events(): AsyncIterable<RealtimeTextToSpeechEvent>;
57
+ audio(): AsyncIterable<Uint8Array>;
58
+ [Symbol.asyncIterator](): AsyncIterator<RealtimeTextToSpeechMessage>;
59
+ private sendJson;
60
+ private handleMessage;
61
+ private handleClose;
62
+ private push;
63
+ private fail;
64
+ private finish;
65
+ }
29
66
  declare class GenerationJobsResource {
30
67
  private readonly client;
31
68
  constructor(client: BreezeBlueClient);
@@ -65,7 +102,7 @@ declare class VoicesResource {
65
102
  /** Stream the audio of a clone or design preview before saving. */
66
103
  streamPreview(generatedVoiceId: string, options?: AudioRequestOptions): Promise<AudioResponse>;
67
104
  /** Persist a clone or design preview as a real voice asset. */
68
- savePreview(request: SaveVoiceRequest, options?: RequestOptions): Promise<Voice>;
105
+ savePreview(request: SaveVoiceRequest, options?: RequestOptions): Promise<SavedVoice>;
69
106
  }
70
107
  declare class HistoryResource {
71
108
  private readonly client;
package/dist/client.js CHANGED
@@ -1,13 +1,17 @@
1
1
  import { AudioResponse } from "./audio.js";
2
- import { BreezeBlueConfigurationError, errorFromResponse } from "./errors.js";
2
+ import { BreezeBlueConfigurationError, BreezeBlueRealtimeError, errorFromResponse } from "./errors.js";
3
3
  import { camelizeKeys, snakeizeKeys } from "./_transform.js";
4
4
  import { SDK_NAME, SDK_VERSION } from "./version.js";
5
5
  const DEFAULT_BASE_URL = "https://api.breeze.blue";
6
6
  const DEFAULT_TIMEOUT_MS = 60_000;
7
+ const DEFAULT_REALTIME_HANDSHAKE_TIMEOUT_MS = 30_000;
8
+ const REALTIME_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
9
+ const WEBSOCKET_OPEN = 1;
7
10
  export class BreezeBlueClient {
8
11
  apiKey;
9
12
  baseUrl;
10
13
  fetch;
14
+ webSocket;
11
15
  headers;
12
16
  timeout;
13
17
  textToSpeech;
@@ -19,13 +23,12 @@ export class BreezeBlueClient {
19
23
  constructor(options = {}) {
20
24
  const envApiKey = typeof process !== "undefined" ? process.env.BREEZE_API_KEY : undefined;
21
25
  const envBaseUrl = typeof process !== "undefined" ? process.env.BREEZE_BASE_URL : undefined;
22
- const apiKey = options.apiKey ?? envApiKey;
23
- if (!apiKey) {
24
- throw new BreezeBlueConfigurationError("Missing Breeze API key. Pass apiKey or set BREEZE_API_KEY.");
25
- }
26
- this.apiKey = apiKey;
26
+ // An API key is only required for REST calls; realtime.connect with a
27
+ // clientSecret works without one, so missing keys are reported lazily.
28
+ this.apiKey = options.apiKey ?? envApiKey;
27
29
  this.baseUrl = normalizeBaseUrl(options.baseUrl ?? envBaseUrl ?? DEFAULT_BASE_URL);
28
30
  this.fetch = options.fetch ?? globalThis.fetch;
31
+ this.webSocket = options.webSocket ?? readGlobalWebSocket();
29
32
  this.headers = options.headers;
30
33
  this.timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
31
34
  this.textToSpeech = new TextToSpeechResource(this);
@@ -48,6 +51,10 @@ export class BreezeBlueClient {
48
51
  return new AudioResponse(response);
49
52
  }
50
53
  async request(method, path, params, body, options) {
54
+ if (!this.apiKey) {
55
+ throw new BreezeBlueConfigurationError("Missing Breeze API key. Pass apiKey or set BREEZE_API_KEY. " +
56
+ "(Only textToSpeech.realtime.connect with a clientSecret works without one.)");
57
+ }
51
58
  const signal = buildAbortSignal(options?.signal, options?.timeout ?? this.timeout);
52
59
  const wireParams = params ? snakeizeKeys(params) : undefined;
53
60
  const wireBody = transformRequestBody(body);
@@ -69,7 +76,9 @@ export class BreezeBlueClient {
69
76
  }
70
77
  buildHeaders(body, requestHeaders) {
71
78
  const headers = new Headers(this.headers);
72
- headers.set("xi-api-key", this.apiKey);
79
+ if (this.apiKey) {
80
+ headers.set("xi-api-key", this.apiKey);
81
+ }
73
82
  headers.set("x-breeze-sdk", `${SDK_NAME}/${SDK_VERSION}`);
74
83
  mergeHeaders(headers, requestHeaders);
75
84
  if (body !== undefined && !(body instanceof FormData) && !headers.has("content-type")) {
@@ -80,8 +89,10 @@ export class BreezeBlueClient {
80
89
  }
81
90
  class TextToSpeechResource {
82
91
  client;
92
+ realtime;
83
93
  constructor(client) {
84
94
  this.client = client;
95
+ this.realtime = new RealtimeTextToSpeechResource(client);
85
96
  }
86
97
  convert(voiceId, request, options = {}) {
87
98
  return this.client.requestAudio("POST", `/v1/text-to-speech/${encodeURIComponent(voiceId)}`, { outputFormat: options.outputFormat }, request, options);
@@ -100,6 +111,233 @@ class TextToSpeechResource {
100
111
  return this.client.requestJson("POST", "/v1/text-to-speech/enhance", undefined, request, options);
101
112
  }
102
113
  }
114
+ class RealtimeTextToSpeechResource {
115
+ client;
116
+ constructor(client) {
117
+ this.client = client;
118
+ }
119
+ createSession(voiceId, request = {}, options) {
120
+ return this.client.requestJson("POST", `/v1/text-to-speech/${encodeURIComponent(voiceId)}/realtime-sessions`, undefined, request, options);
121
+ }
122
+ async connect(voiceId, options = {}) {
123
+ if ((options.clientSecret !== undefined || options.websocketUrl !== undefined) && hasRealtimeSessionParams(options)) {
124
+ console.warn("@breeze.blue/sdk: realtime.connect ignores modelId, languageCode, instructions, voiceSettings, " +
125
+ "inactivityTimeoutSeconds, and enableLogging when clientSecret or websocketUrl is provided. " +
126
+ "Configure these when creating the realtime session instead.");
127
+ }
128
+ const websocketUrl = options.websocketUrl ??
129
+ (options.clientSecret
130
+ ? buildUrl(this.client.baseUrl.replace(/^http/i, "ws"), `/v1/text-to-speech/${encodeURIComponent(voiceId)}/stream-input`, { client_secret: options.clientSecret })
131
+ : (await this.createSession(voiceId, {
132
+ modelId: options.modelId,
133
+ languageCode: options.languageCode,
134
+ instructions: options.instructions,
135
+ voiceSettings: options.voiceSettings,
136
+ inactivityTimeoutSeconds: options.inactivityTimeoutSeconds,
137
+ enableLogging: options.enableLogging,
138
+ })).websocketUrl);
139
+ const WebSocketImpl = requireWebSocket(options.webSocket ?? this.client.webSocket);
140
+ const socket = new WebSocketImpl(websocketUrl);
141
+ return RealtimeTextToSpeechConnection.open(socket, {
142
+ timeout: options.timeout ?? this.client.timeout ?? DEFAULT_REALTIME_HANDSHAKE_TIMEOUT_MS,
143
+ });
144
+ }
145
+ }
146
+ function hasRealtimeSessionParams(options) {
147
+ return (options.modelId !== undefined ||
148
+ options.languageCode !== undefined ||
149
+ options.instructions !== undefined ||
150
+ options.voiceSettings !== undefined ||
151
+ options.inactivityTimeoutSeconds !== undefined ||
152
+ options.enableLogging !== undefined);
153
+ }
154
+ export class RealtimeTextToSpeechConnection {
155
+ socket;
156
+ queue = [];
157
+ waiters = [];
158
+ closed = false;
159
+ queuedBytes = 0;
160
+ failure;
161
+ constructor(socket) {
162
+ this.socket = socket;
163
+ }
164
+ static async open(socket, options = {}) {
165
+ const connection = new RealtimeTextToSpeechConnection(socket);
166
+ const timeoutMs = options.timeout ?? DEFAULT_REALTIME_HANDSHAKE_TIMEOUT_MS;
167
+ await new Promise((resolve, reject) => {
168
+ let settled = false;
169
+ let timer;
170
+ const succeed = () => {
171
+ if (settled)
172
+ return;
173
+ settled = true;
174
+ if (timer !== undefined)
175
+ clearTimeout(timer);
176
+ resolve();
177
+ };
178
+ const fail = (error) => {
179
+ if (settled)
180
+ return;
181
+ settled = true;
182
+ if (timer !== undefined)
183
+ clearTimeout(timer);
184
+ try {
185
+ socket.close(1000);
186
+ }
187
+ catch {
188
+ // The socket may already be closed; opening failed either way.
189
+ }
190
+ reject(error);
191
+ };
192
+ if (timeoutMs > 0) {
193
+ timer = setTimeout(() => fail(new Error(`Realtime TTS WebSocket did not open within ${timeoutMs}ms.`)), timeoutMs);
194
+ }
195
+ socket.binaryType = "arraybuffer";
196
+ socket.onopen = () => succeed();
197
+ socket.onerror = (event) => fail(new Error(`Realtime TTS WebSocket failed to open: ${String(event.type)}`));
198
+ socket.onclose = (event) => fail(new Error(`Realtime TTS WebSocket closed before opening: ${event.code} ${event.reason}`.trim()));
199
+ });
200
+ socket.onmessage = (event) => connection.handleMessage(event.data);
201
+ socket.onclose = (event) => connection.handleClose(event);
202
+ socket.onerror = () => connection.finish();
203
+ return connection;
204
+ }
205
+ startTurn(turnId) {
206
+ this.sendJson({ type: "turn.start", ...(turnId ? { turn_id: turnId } : {}) });
207
+ }
208
+ appendText(text) {
209
+ this.sendJson({ type: "text.append", text });
210
+ }
211
+ flush() {
212
+ this.sendJson({ type: "text.flush" });
213
+ }
214
+ endTurn() {
215
+ this.sendJson({ type: "turn.end" });
216
+ }
217
+ cancelTurn() {
218
+ this.sendJson({ type: "turn.cancel" });
219
+ }
220
+ /** Send a keepalive ping. The server answers with a `pong` event. */
221
+ ping() {
222
+ this.sendJson({ type: "ping" });
223
+ }
224
+ close() {
225
+ if (this.closed) {
226
+ return;
227
+ }
228
+ if (this.socket.readyState === WEBSOCKET_OPEN) {
229
+ this.sendJson({ type: "session.close" });
230
+ }
231
+ this.socket.close(1000);
232
+ this.finish();
233
+ }
234
+ async *events() {
235
+ for await (const message of this) {
236
+ if (message.type !== "audio") {
237
+ yield message;
238
+ }
239
+ }
240
+ }
241
+ async *audio() {
242
+ for await (const message of this) {
243
+ if (message.type === "audio") {
244
+ yield message.audio;
245
+ }
246
+ }
247
+ }
248
+ [Symbol.asyncIterator]() {
249
+ return {
250
+ next: () => {
251
+ const entry = this.queue.shift();
252
+ if (entry) {
253
+ this.queuedBytes -= entry.size;
254
+ return Promise.resolve({ value: entry.message, done: false });
255
+ }
256
+ if (this.failure) {
257
+ return Promise.reject(this.failure);
258
+ }
259
+ if (this.closed) {
260
+ return Promise.resolve({ value: undefined, done: true });
261
+ }
262
+ return new Promise((resolve, reject) => this.waiters.push({ resolve, reject }));
263
+ },
264
+ };
265
+ }
266
+ sendJson(payload) {
267
+ this.socket.send(JSON.stringify(payload));
268
+ }
269
+ handleMessage(data) {
270
+ if (typeof data === "string") {
271
+ let parsed;
272
+ try {
273
+ parsed = JSON.parse(data);
274
+ }
275
+ catch (cause) {
276
+ this.fail(new BreezeBlueRealtimeError("Realtime TTS returned an invalid JSON event.", { cause }));
277
+ return;
278
+ }
279
+ this.push(camelizeKeys(parsed), data.length);
280
+ return;
281
+ }
282
+ if (data instanceof ArrayBuffer) {
283
+ this.push({ type: "audio", audio: new Uint8Array(data) }, data.byteLength);
284
+ return;
285
+ }
286
+ if (ArrayBuffer.isView(data)) {
287
+ this.push({ type: "audio", audio: new Uint8Array(data.buffer, data.byteOffset, data.byteLength) }, data.byteLength);
288
+ }
289
+ }
290
+ handleClose(event) {
291
+ if (!this.closed && event.code !== 1000) {
292
+ this.push({
293
+ type: "session.closed",
294
+ code: event.code,
295
+ reason: event.reason,
296
+ reconnect: event.code === 1012,
297
+ }, 0);
298
+ }
299
+ this.finish();
300
+ }
301
+ push(message, size) {
302
+ if (this.closed) {
303
+ return;
304
+ }
305
+ const waiter = this.waiters.shift();
306
+ if (waiter) {
307
+ waiter.resolve({ value: message, done: false });
308
+ return;
309
+ }
310
+ this.queue.push({ message, size });
311
+ this.queuedBytes += size;
312
+ if (this.queuedBytes > REALTIME_MAX_BUFFERED_BYTES) {
313
+ this.fail(new BreezeBlueRealtimeError(`Realtime TTS connection buffered more than ${REALTIME_MAX_BUFFERED_BYTES} bytes without being ` +
314
+ "consumed, so the connection was closed. Iterate the connection (or its audio()/events() " +
315
+ "helpers) while the session is active."));
316
+ }
317
+ }
318
+ fail(error) {
319
+ if (this.closed) {
320
+ return;
321
+ }
322
+ this.closed = true;
323
+ this.failure = error;
324
+ this.queue.length = 0;
325
+ this.queuedBytes = 0;
326
+ this.socket.close(1000);
327
+ for (const waiter of this.waiters.splice(0)) {
328
+ waiter.reject(error);
329
+ }
330
+ }
331
+ finish() {
332
+ if (this.closed) {
333
+ return;
334
+ }
335
+ this.closed = true;
336
+ for (const waiter of this.waiters.splice(0)) {
337
+ waiter.resolve({ value: undefined, done: true });
338
+ }
339
+ }
340
+ }
103
341
  class GenerationJobsResource {
104
342
  client;
105
343
  constructor(client) {
@@ -175,6 +413,8 @@ class VoicesResource {
175
413
  voiceName: request.voiceName,
176
414
  voiceDescription: request.voiceDescription,
177
415
  labels: request.labels,
416
+ primaryCategoryCode: request.primaryCategoryCode,
417
+ visibility: request.visibility,
178
418
  playedNotSelectedVoiceIds: request.playedNotSelectedVoiceIds,
179
419
  }, options);
180
420
  }
@@ -218,6 +458,17 @@ class AccountResource {
218
458
  return this.client.requestJson("GET", "/v1/usage", params, undefined, options);
219
459
  }
220
460
  }
461
+ function readGlobalWebSocket() {
462
+ return globalThis.WebSocket;
463
+ }
464
+ function requireWebSocket(WebSocketImpl) {
465
+ if (!WebSocketImpl) {
466
+ throw new BreezeBlueConfigurationError("Missing WebSocket implementation. Realtime text-to-speech needs a global WebSocket " +
467
+ "(available in browsers, edge runtimes, and Node 22+) or a webSocket constructor passed to " +
468
+ "BreezeBlueClient (for example from the 'ws' package on Node 20).");
469
+ }
470
+ return WebSocketImpl;
471
+ }
221
472
  function normalizeBaseUrl(value) {
222
473
  return value.replace(/\/+$/, "");
223
474
  }
@@ -292,9 +543,11 @@ function clonePreviewForm(request) {
292
543
  }
293
544
  function voiceEditForm(request) {
294
545
  const form = new FormData();
295
- form.set("name", request.name);
546
+ appendOptional(form, "name", request.name);
296
547
  appendOptional(form, "description", request.description);
297
548
  appendOptional(form, "labels", request.labels ? JSON.stringify(request.labels) : undefined);
549
+ appendOptional(form, "primary_category_code", request.primaryCategoryCode);
550
+ appendOptional(form, "visibility", request.visibility);
298
551
  for (const file of normalizeFiles(request.file, request.files, { required: false })) {
299
552
  appendFile(form, "files", file);
300
553
  }
package/dist/errors.d.ts CHANGED
@@ -10,6 +10,32 @@ export declare class BreezeBlueError extends Error {
10
10
  }
11
11
  export declare class BreezeBlueConfigurationError extends BreezeBlueError {
12
12
  }
13
+ /**
14
+ * A realtime text-to-speech connection failure: the SDK failed the
15
+ * connection (invalid server frame, unconsumed-buffer overflow, ...) and the
16
+ * message iterator rejects with this error.
17
+ *
18
+ * `recoverable` mirrors the Python SDK's `RealtimeError.recoverable`: it is
19
+ * `true` only when the failure cancelled just the active turn and the
20
+ * connection stays usable. SDK-detected connection failures are never
21
+ * recoverable. `reconnect` tells you whether opening a new session is
22
+ * expected to succeed.
23
+ */
24
+ export declare class BreezeBlueRealtimeError extends BreezeBlueError {
25
+ readonly code: string | undefined;
26
+ readonly meta: Record<string, unknown> | null;
27
+ readonly closeCode: number | undefined;
28
+ readonly reconnect: boolean;
29
+ readonly recoverable: boolean;
30
+ constructor(message: string, params?: {
31
+ code?: string;
32
+ meta?: Record<string, unknown> | null;
33
+ closeCode?: number;
34
+ reconnect?: boolean;
35
+ recoverable?: boolean;
36
+ cause?: unknown;
37
+ });
38
+ }
13
39
  export declare class BreezeBlueAPIError extends BreezeBlueError {
14
40
  readonly status: number;
15
41
  readonly code: string;
package/dist/errors.js CHANGED
@@ -6,6 +6,32 @@ export class BreezeBlueError extends Error {
6
6
  }
7
7
  export class BreezeBlueConfigurationError extends BreezeBlueError {
8
8
  }
9
+ /**
10
+ * A realtime text-to-speech connection failure: the SDK failed the
11
+ * connection (invalid server frame, unconsumed-buffer overflow, ...) and the
12
+ * message iterator rejects with this error.
13
+ *
14
+ * `recoverable` mirrors the Python SDK's `RealtimeError.recoverable`: it is
15
+ * `true` only when the failure cancelled just the active turn and the
16
+ * connection stays usable. SDK-detected connection failures are never
17
+ * recoverable. `reconnect` tells you whether opening a new session is
18
+ * expected to succeed.
19
+ */
20
+ export class BreezeBlueRealtimeError extends BreezeBlueError {
21
+ code;
22
+ meta;
23
+ closeCode;
24
+ reconnect;
25
+ recoverable;
26
+ constructor(message, params = {}) {
27
+ super(message, params.cause === undefined ? undefined : { cause: params.cause });
28
+ this.code = params.code;
29
+ this.meta = params.meta ?? null;
30
+ this.closeCode = params.closeCode;
31
+ this.reconnect = params.reconnect ?? false;
32
+ this.recoverable = params.recoverable ?? false;
33
+ }
34
+ }
9
35
  export class BreezeBlueAPIError extends BreezeBlueError {
10
36
  status;
11
37
  code;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { AudioResponse } from "./audio.js";
2
- export { BreezeBlueAPIError, BreezeBlueAuthenticationError, BreezeBlueBadRequestError, BreezeBlueConfigurationError, BreezeBlueConflictError, BreezeBlueError, BreezeBlueForbiddenError, BreezeBlueGenerationNotReadyError, BreezeBlueInsufficientCreditsError, BreezeBlueNotFoundError, BreezeBlueRateLimitError, BreezeBlueServiceUnavailableError, BreezeBlueUpstreamError, BreezeBlueValidationError, } from "./errors.js";
3
- export { BreezeBlueClient } from "./client.js";
2
+ export { BreezeBlueAPIError, BreezeBlueAuthenticationError, BreezeBlueBadRequestError, BreezeBlueConfigurationError, BreezeBlueConflictError, BreezeBlueError, BreezeBlueForbiddenError, BreezeBlueGenerationNotReadyError, BreezeBlueInsufficientCreditsError, BreezeBlueNotFoundError, BreezeBlueRateLimitError, BreezeBlueRealtimeError, BreezeBlueServiceUnavailableError, BreezeBlueUpstreamError, BreezeBlueValidationError, } from "./errors.js";
3
+ export { BreezeBlueClient, RealtimeTextToSpeechConnection } from "./client.js";
4
4
  export { SDK_NAME, SDK_VERSION } from "./version.js";
5
5
  export type * from "./types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { AudioResponse } from "./audio.js";
2
- export { BreezeBlueAPIError, BreezeBlueAuthenticationError, BreezeBlueBadRequestError, BreezeBlueConfigurationError, BreezeBlueConflictError, BreezeBlueError, BreezeBlueForbiddenError, BreezeBlueGenerationNotReadyError, BreezeBlueInsufficientCreditsError, BreezeBlueNotFoundError, BreezeBlueRateLimitError, BreezeBlueServiceUnavailableError, BreezeBlueUpstreamError, BreezeBlueValidationError, } from "./errors.js";
3
- export { BreezeBlueClient } from "./client.js";
2
+ export { BreezeBlueAPIError, BreezeBlueAuthenticationError, BreezeBlueBadRequestError, BreezeBlueConfigurationError, BreezeBlueConflictError, BreezeBlueError, BreezeBlueForbiddenError, BreezeBlueGenerationNotReadyError, BreezeBlueInsufficientCreditsError, BreezeBlueNotFoundError, BreezeBlueRateLimitError, BreezeBlueRealtimeError, BreezeBlueServiceUnavailableError, BreezeBlueUpstreamError, BreezeBlueValidationError, } from "./errors.js";
3
+ export { BreezeBlueClient, RealtimeTextToSpeechConnection } from "./client.js";
4
4
  export { SDK_NAME, SDK_VERSION } from "./version.js";
package/dist/node.js CHANGED
@@ -29,7 +29,11 @@ export async function play(audio, options = {}) {
29
29
  await spawnAndPipeBytes(command.command, command.args, await audio.bytes());
30
30
  }
31
31
  export async function stream(audio, options = {}) {
32
- const selected = selectStreamPlaybackCommand();
32
+ const selected = selectStreamPlaybackCommand({
33
+ contentType: audio.contentType,
34
+ sampleRate: numberHeader(audio, "x-breeze-sample-rate"),
35
+ channels: numberHeader(audio, "x-breeze-channels"),
36
+ });
33
37
  const command = options.command ?? selected.command;
34
38
  const args = options.args ?? selected.args;
35
39
  await spawnAndPipeReadable(command, args, audio.response.body);
@@ -57,6 +61,10 @@ async function spawnAndPipeReadable(command, args, body) {
57
61
  Readable.fromWeb(body).pipe(child.stdin);
58
62
  await waitForChild(child);
59
63
  }
64
+ function numberHeader(audio, header) {
65
+ const value = Number.parseInt(audio.headers.get(header) ?? "", 10);
66
+ return Number.isFinite(value) && value > 0 ? value : null;
67
+ }
60
68
  async function spawnAndWait(command, args) {
61
69
  await waitForChild(spawn(command, args, { stdio: "inherit" }));
62
70
  }
package/dist/types.d.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  export type JsonObject = Record<string, unknown>;
2
2
  export type BreezeBlueFetch = typeof fetch;
3
3
  export interface BreezeBlueClientOptions {
4
+ /**
5
+ * API key for REST calls. Optional: browser and edge callers that only use
6
+ * `textToSpeech.realtime.connect` with a `clientSecret` do not need one.
7
+ */
4
8
  apiKey?: string;
5
9
  baseUrl?: string;
6
10
  fetch?: BreezeBlueFetch;
7
11
  headers?: HeadersInit;
8
12
  timeout?: number;
13
+ webSocket?: BreezeBlueWebSocketConstructor;
9
14
  }
10
15
  export interface RequestOptions {
11
16
  headers?: HeadersInit;
@@ -19,6 +24,129 @@ export interface StreamTextToSpeechOptions extends AudioRequestOptions {
19
24
  optimizeStreamingLatency?: number;
20
25
  enableLogging?: boolean;
21
26
  }
27
+ export type BreezeBlueWebSocketConstructor = new (url: string, protocols?: string | string[]) => BreezeBlueWebSocket;
28
+ export interface BreezeBlueWebSocket {
29
+ binaryType: string;
30
+ readyState: number;
31
+ onopen: ((event: Event) => void) | null;
32
+ onmessage: ((event: MessageEvent) => void) | null;
33
+ onerror: ((event: Event) => void) | null;
34
+ onclose: ((event: CloseEvent) => void) | null;
35
+ send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
36
+ close(code?: number, reason?: string): void;
37
+ }
38
+ export interface RealtimeTextToSpeechSessionRequest {
39
+ modelId?: string;
40
+ languageCode?: string;
41
+ instructions?: string;
42
+ voiceSettings?: {
43
+ guidanceScale?: number | null;
44
+ };
45
+ inactivityTimeoutSeconds?: number;
46
+ enableLogging?: boolean;
47
+ }
48
+ export interface RealtimeTextToSpeechAudioFormat {
49
+ codec: "pcm_s16le" | string;
50
+ sampleRate: number;
51
+ channels: number;
52
+ sampleWidthBits: number;
53
+ }
54
+ export interface RealtimeTextToSpeechSession {
55
+ clientSecret: string;
56
+ websocketUrl: string;
57
+ expiresAt: string;
58
+ audioFormat: RealtimeTextToSpeechAudioFormat;
59
+ }
60
+ export interface RealtimeTextToSpeechConnectOptions extends RealtimeTextToSpeechSessionRequest {
61
+ clientSecret?: string;
62
+ websocketUrl?: string;
63
+ webSocket?: BreezeBlueWebSocketConstructor;
64
+ /** Handshake timeout in milliseconds. Defaults to the client `timeout`. */
65
+ timeout?: number;
66
+ }
67
+ export interface RealtimeTextToSpeechSessionReadyEvent {
68
+ type: "session.ready";
69
+ sessionId: string;
70
+ audioFormat: RealtimeTextToSpeechAudioFormat;
71
+ maxAppendBytes: number;
72
+ maxTurnCharacters: number;
73
+ inactivityTimeoutSeconds: number;
74
+ maxSessionSeconds: number;
75
+ }
76
+ export interface RealtimeTextToSpeechPongEvent {
77
+ type: "pong";
78
+ }
79
+ export interface RealtimeTextToSpeechTurnStartedEvent {
80
+ type: "turn.started";
81
+ turnId: string;
82
+ historyItemId: string;
83
+ }
84
+ export interface RealtimeTextToSpeechAudioStartedEvent {
85
+ type: "audio.started";
86
+ turnId: string;
87
+ ttfaMs: number;
88
+ audioFormat: RealtimeTextToSpeechAudioFormat;
89
+ }
90
+ export interface RealtimeTextToSpeechTurnSummary {
91
+ turnId: string;
92
+ historyItemId: string;
93
+ status: string;
94
+ textCharacters: number;
95
+ audioBytes: number;
96
+ durationMs: number | null;
97
+ ttfaMs: number | null;
98
+ }
99
+ export interface RealtimeTextToSpeechTurnDoneEvent extends RealtimeTextToSpeechTurnSummary {
100
+ type: "turn.done";
101
+ }
102
+ export interface RealtimeTextToSpeechTurnCancelledEvent extends RealtimeTextToSpeechTurnSummary {
103
+ type: "turn.cancelled";
104
+ }
105
+ export interface RealtimeTextToSpeechUsageCommittedEvent {
106
+ type: "usage.committed";
107
+ turnId: string;
108
+ historyItemId: string;
109
+ textCharacters: number;
110
+ outputFormat: string;
111
+ ttfaMs: number | null;
112
+ }
113
+ /**
114
+ * Sent by the server after a clean `session.close` (with `sessionId`), or
115
+ * synthesized by the SDK when the WebSocket closes abnormally (with `code`,
116
+ * `reason`, and `reconnect`).
117
+ */
118
+ export interface RealtimeTextToSpeechSessionClosedEvent {
119
+ type: "session.closed";
120
+ sessionId?: string;
121
+ code?: number;
122
+ reason?: string;
123
+ reconnect?: boolean;
124
+ }
125
+ export interface RealtimeTextToSpeechErrorEvent {
126
+ type: "error";
127
+ code: string;
128
+ message: string;
129
+ meta?: {
130
+ reconnect?: boolean;
131
+ reason?: string;
132
+ turnId?: string;
133
+ [key: string]: unknown;
134
+ };
135
+ }
136
+ /**
137
+ * Server events currently sent on a realtime text-to-speech connection.
138
+ *
139
+ * Treat this union as non-exhaustive: the server may add new event types,
140
+ * and the SDK passes unknown JSON events through to the iterator unchanged
141
+ * (with camelCase keys). Do not rely on an exhaustive `switch` over `type`;
142
+ * ignore event types you do not recognize.
143
+ */
144
+ export type RealtimeTextToSpeechEvent = RealtimeTextToSpeechSessionReadyEvent | RealtimeTextToSpeechPongEvent | RealtimeTextToSpeechTurnStartedEvent | RealtimeTextToSpeechAudioStartedEvent | RealtimeTextToSpeechTurnDoneEvent | RealtimeTextToSpeechTurnCancelledEvent | RealtimeTextToSpeechUsageCommittedEvent | RealtimeTextToSpeechSessionClosedEvent | RealtimeTextToSpeechErrorEvent;
145
+ export interface RealtimeTextToSpeechAudioMessage {
146
+ type: "audio";
147
+ audio: Uint8Array;
148
+ }
149
+ export type RealtimeTextToSpeechMessage = RealtimeTextToSpeechEvent | RealtimeTextToSpeechAudioMessage;
22
150
  export interface VoiceSettings {
23
151
  stability?: number;
24
152
  similarityBoost?: number;
@@ -71,7 +199,7 @@ export interface GenerationJob {
71
199
  source: string;
72
200
  voiceId?: string | null;
73
201
  voiceName?: string | null;
74
- voiceCategory?: string | null;
202
+ origin?: string | null;
75
203
  modelId?: string | null;
76
204
  text?: string | null;
77
205
  contentType?: string | null;
@@ -100,15 +228,27 @@ export interface Model {
100
228
  export interface Voice {
101
229
  voiceId: string;
102
230
  name: string;
103
- category: string;
231
+ origin: string;
104
232
  voiceType: string;
105
233
  labels: Record<string, string>;
106
234
  previewUrl?: string | null;
107
235
  settings: Required<VoiceSettings>;
108
236
  description?: string | null;
109
237
  createdAtUnix?: number | null;
238
+ primaryCategoryCode?: string | null;
239
+ visibility?: string | null;
110
240
  [key: string]: unknown;
111
241
  }
242
+ export interface SavedVoice {
243
+ voiceId: string;
244
+ name: string;
245
+ origin: string;
246
+ previewUrl?: string | null;
247
+ description?: string | null;
248
+ createdAtUnix?: number | null;
249
+ primaryCategoryCode?: string | null;
250
+ visibility?: string | null;
251
+ }
112
252
  export interface VoiceList {
113
253
  voices: Voice[];
114
254
  hasMore: boolean;
@@ -119,7 +259,7 @@ export interface VoiceList {
119
259
  }
120
260
  export interface VoiceSearchParams {
121
261
  search?: string;
122
- category?: "premade" | "generated" | "cloned" | string;
262
+ origin?: "designed" | "cloned" | string;
123
263
  voiceType?: "all" | "default" | "personal" | string;
124
264
  sort?: "created_at_unix" | "name" | string;
125
265
  sortDirection?: "asc" | "desc";
@@ -151,9 +291,11 @@ export interface VoiceClonePreview {
151
291
  requiresVerification: boolean;
152
292
  }
153
293
  export interface VoiceEditRequest {
154
- name: string;
294
+ name?: string;
155
295
  description?: string;
156
296
  labels?: Record<string, string>;
297
+ primaryCategoryCode?: string;
298
+ visibility?: "private" | "public" | string;
157
299
  file?: UploadData | UploadFile;
158
300
  files?: Array<UploadData | UploadFile>;
159
301
  }
@@ -184,13 +326,15 @@ export interface SaveVoiceRequest {
184
326
  voiceName: string;
185
327
  voiceDescription?: string | null;
186
328
  labels?: Record<string, string>;
329
+ primaryCategoryCode?: string;
330
+ visibility?: "private" | "public" | string;
187
331
  playedNotSelectedVoiceIds?: string[];
188
332
  }
189
333
  export interface HistoryItem {
190
334
  historyItemId: string;
191
335
  voiceId?: string | null;
192
336
  voiceName?: string | null;
193
- voiceCategory?: string | null;
337
+ origin?: string | null;
194
338
  modelId?: string | null;
195
339
  text?: string | null;
196
340
  source: string;
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "0.4.1";
1
+ export declare const SDK_VERSION = "0.5.0";
2
2
  export declare const SDK_NAME = "@breeze.blue/sdk";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const SDK_VERSION = "0.4.1";
1
+ export const SDK_VERSION = "0.5.0";
2
2
  export const SDK_NAME = "@breeze.blue/sdk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@breeze.blue/sdk",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "ESM-first TypeScript SDK for the Breeze Blue Developer API.",
5
5
  "license": "MIT",
6
6
  "author": "Breeze Blue <support@breeze.blue>",