@ai-ecoverse/pi-audio 1.18.1 → 1.20.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/README.md +4 -0
- package/dist/fish/catalog.js +78 -0
- package/dist/fish/catalog.js.map +7 -0
- package/dist/fish/index.js +5 -0
- package/dist/fish/index.js.map +7 -0
- package/dist/fish/speak.js +269 -0
- package/dist/fish/speak.js.map +7 -0
- package/dist/fish/transcribe.js +158 -0
- package/dist/fish/transcribe.js.map +7 -0
- package/dist/fish/wire.js +386 -0
- package/dist/fish/wire.js.map +7 -0
- package/dist/google/catalog.js +61 -0
- package/dist/google/catalog.js.map +7 -0
- package/dist/google/index.js +5 -0
- package/dist/google/index.js.map +7 -0
- package/dist/google/speak.js +160 -0
- package/dist/google/speak.js.map +7 -0
- package/dist/google/wire.js +43 -0
- package/dist/google/wire.js.map +7 -0
- package/dist/types/fish/catalog.d.ts +1 -0
- package/dist/types/fish/index.d.ts +1 -0
- package/dist/types/fish/speak.d.ts +4 -0
- package/dist/types/fish/transcribe.d.ts +4 -0
- package/dist/types/fish/wire.d.ts +27 -0
- package/dist/types/google/catalog.d.ts +1 -0
- package/dist/types/google/index.d.ts +1 -0
- package/dist/types/google/speak.d.ts +4 -0
- package/dist/types/google/wire.d.ts +8 -0
- package/package.json +9 -1
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { requestHeaders } from "../openai/wire.js";
|
|
2
|
+
const boundary = "pi-audio-fish-boundary";
|
|
3
|
+
const filenames = {
|
|
4
|
+
"audio/wav": "audio.wav",
|
|
5
|
+
"audio/wave": "audio.wav",
|
|
6
|
+
"audio/x-wav": "audio.wav",
|
|
7
|
+
"audio/mpeg": "audio.mp3",
|
|
8
|
+
"audio/mp3": "audio.mp3",
|
|
9
|
+
"audio/flac": "audio.flac",
|
|
10
|
+
"audio/ogg": "audio.ogg",
|
|
11
|
+
"audio/opus": "audio.opus",
|
|
12
|
+
"audio/webm": "audio.webm",
|
|
13
|
+
"audio/mp4": "audio.m4a",
|
|
14
|
+
"audio/m4a": "audio.m4a",
|
|
15
|
+
"audio/aac": "audio.aac"
|
|
16
|
+
};
|
|
17
|
+
const speechNames = {
|
|
18
|
+
pcm16: "pcm",
|
|
19
|
+
wav: "wav",
|
|
20
|
+
mp3: "mp3",
|
|
21
|
+
opus: "opus"
|
|
22
|
+
};
|
|
23
|
+
class ByteWriter {
|
|
24
|
+
#bytes = [];
|
|
25
|
+
byte(value) {
|
|
26
|
+
this.#bytes.push(value & 255);
|
|
27
|
+
}
|
|
28
|
+
u16(value) {
|
|
29
|
+
this.byte(value >> 8);
|
|
30
|
+
this.byte(value);
|
|
31
|
+
}
|
|
32
|
+
u32(value) {
|
|
33
|
+
this.byte(value >>> 24);
|
|
34
|
+
this.byte(value >>> 16);
|
|
35
|
+
this.byte(value >>> 8);
|
|
36
|
+
this.byte(value);
|
|
37
|
+
}
|
|
38
|
+
f64(value) {
|
|
39
|
+
const view = new DataView(new ArrayBuffer(8));
|
|
40
|
+
view.setFloat64(0, value);
|
|
41
|
+
for (let index = 0; index < 8; index += 1) this.byte(view.getUint8(index));
|
|
42
|
+
}
|
|
43
|
+
raw(bytes) {
|
|
44
|
+
for (const byte of bytes) this.byte(byte);
|
|
45
|
+
}
|
|
46
|
+
toArray() {
|
|
47
|
+
return Uint8Array.from(this.#bytes);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
class ByteReader {
|
|
51
|
+
#offset = 0;
|
|
52
|
+
#data;
|
|
53
|
+
constructor(data) {
|
|
54
|
+
this.#data = data;
|
|
55
|
+
}
|
|
56
|
+
byte() {
|
|
57
|
+
const value = this.#data[this.#offset];
|
|
58
|
+
if (value === void 0) throw new Error("Fish frame ended early");
|
|
59
|
+
this.#offset += 1;
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
u16() {
|
|
63
|
+
return this.byte() << 8 | this.byte();
|
|
64
|
+
}
|
|
65
|
+
u32() {
|
|
66
|
+
return (this.byte() << 24 | this.byte() << 16 | this.byte() << 8 | this.byte()) >>> 0;
|
|
67
|
+
}
|
|
68
|
+
i16() {
|
|
69
|
+
const value = this.u16();
|
|
70
|
+
return value > 32767 ? value - 65536 : value;
|
|
71
|
+
}
|
|
72
|
+
i32() {
|
|
73
|
+
return this.u32() | 0;
|
|
74
|
+
}
|
|
75
|
+
u64() {
|
|
76
|
+
const high = this.u32();
|
|
77
|
+
const low = this.u32();
|
|
78
|
+
return high * 4294967296 + low;
|
|
79
|
+
}
|
|
80
|
+
i64() {
|
|
81
|
+
const high = this.i32();
|
|
82
|
+
const low = this.u32();
|
|
83
|
+
return high * 4294967296 + low;
|
|
84
|
+
}
|
|
85
|
+
f32() {
|
|
86
|
+
const copy = new Uint8Array(4);
|
|
87
|
+
copy.set(this.bytes(4));
|
|
88
|
+
return new DataView(copy.buffer).getFloat32(0);
|
|
89
|
+
}
|
|
90
|
+
f64() {
|
|
91
|
+
const copy = new Uint8Array(8);
|
|
92
|
+
copy.set(this.bytes(8));
|
|
93
|
+
return new DataView(copy.buffer).getFloat64(0);
|
|
94
|
+
}
|
|
95
|
+
bytes(length) {
|
|
96
|
+
const start = this.#offset;
|
|
97
|
+
if (start + length > this.#data.length) throw new Error("Fish frame ended early");
|
|
98
|
+
this.#offset = start + length;
|
|
99
|
+
return this.#data.subarray(start, this.#offset);
|
|
100
|
+
}
|
|
101
|
+
done() {
|
|
102
|
+
return this.#offset === this.#data.length;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function packString(value, out) {
|
|
106
|
+
const bytes = new TextEncoder().encode(value);
|
|
107
|
+
if (bytes.length < 32) out.byte(160 | bytes.length);
|
|
108
|
+
else if (bytes.length < 256) {
|
|
109
|
+
out.byte(217);
|
|
110
|
+
out.byte(bytes.length);
|
|
111
|
+
} else if (bytes.length < 65536) {
|
|
112
|
+
out.byte(218);
|
|
113
|
+
out.u16(bytes.length);
|
|
114
|
+
} else throw new Error("String too long");
|
|
115
|
+
out.raw(bytes);
|
|
116
|
+
}
|
|
117
|
+
function packBin(value, out) {
|
|
118
|
+
if (value.byteLength < 256) {
|
|
119
|
+
out.byte(196);
|
|
120
|
+
out.byte(value.byteLength);
|
|
121
|
+
} else if (value.byteLength < 65536) {
|
|
122
|
+
out.byte(197);
|
|
123
|
+
out.u16(value.byteLength);
|
|
124
|
+
} else throw new Error("Binary too long");
|
|
125
|
+
out.raw(value);
|
|
126
|
+
}
|
|
127
|
+
function packNumber(value, out) {
|
|
128
|
+
if (!Number.isInteger(value)) {
|
|
129
|
+
out.byte(203);
|
|
130
|
+
out.f64(value);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (value >= 0 && value <= 127) {
|
|
134
|
+
out.byte(value);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (value < 0 && value >= -32) {
|
|
138
|
+
out.byte(value & 255);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (value >= 0 && value <= 255) {
|
|
142
|
+
out.byte(204);
|
|
143
|
+
out.byte(value);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (value >= 0 && value <= 65535) {
|
|
147
|
+
out.byte(205);
|
|
148
|
+
out.u16(value);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
if (value >= 0 && value <= 4294967295) {
|
|
152
|
+
out.byte(206);
|
|
153
|
+
out.u32(value);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (value < 0 && value >= -128) {
|
|
157
|
+
out.byte(208);
|
|
158
|
+
out.byte(value & 255);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
throw new Error("Integer out of range");
|
|
162
|
+
}
|
|
163
|
+
function packArray(values, out) {
|
|
164
|
+
if (values.length < 16) out.byte(144 | values.length);
|
|
165
|
+
else if (values.length < 65536) {
|
|
166
|
+
out.byte(220);
|
|
167
|
+
out.u16(values.length);
|
|
168
|
+
} else throw new Error("Array too long");
|
|
169
|
+
for (const value of values) packInto(value, out);
|
|
170
|
+
}
|
|
171
|
+
function packMap(value, out) {
|
|
172
|
+
const entries = Object.entries(value);
|
|
173
|
+
if (entries.length < 16) out.byte(128 | entries.length);
|
|
174
|
+
else if (entries.length < 65536) {
|
|
175
|
+
out.byte(222);
|
|
176
|
+
out.u16(entries.length);
|
|
177
|
+
} else throw new Error("Map too long");
|
|
178
|
+
for (const [key, item] of entries) {
|
|
179
|
+
packString(key, out);
|
|
180
|
+
packInto(item, out);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function packInto(value, out) {
|
|
184
|
+
if (value === null) {
|
|
185
|
+
out.byte(192);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (typeof value === "boolean") {
|
|
189
|
+
out.byte(value ? 195 : 194);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === "number") {
|
|
193
|
+
packNumber(value, out);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (typeof value === "string") {
|
|
197
|
+
packString(value, out);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (value instanceof Uint8Array) {
|
|
201
|
+
packBin(value, out);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (Array.isArray(value)) {
|
|
205
|
+
packArray(value, out);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
packMap(value, out);
|
|
209
|
+
}
|
|
210
|
+
function unpackString(reader, length) {
|
|
211
|
+
return new TextDecoder().decode(reader.bytes(length));
|
|
212
|
+
}
|
|
213
|
+
function unpackArray(reader, length) {
|
|
214
|
+
const values = [];
|
|
215
|
+
for (let index = 0; index < length; index += 1) values.push(unpackOne(reader));
|
|
216
|
+
return values;
|
|
217
|
+
}
|
|
218
|
+
function unpackMap(reader, length) {
|
|
219
|
+
const value = {};
|
|
220
|
+
for (let index = 0; index < length; index += 1) {
|
|
221
|
+
const key = unpackOne(reader);
|
|
222
|
+
if (typeof key !== "string") throw new Error("Fish frame key was not a string");
|
|
223
|
+
value[key] = unpackOne(reader);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
function unpackSized(reader, marker) {
|
|
228
|
+
if (marker === 192) return null;
|
|
229
|
+
if (marker === 194) return false;
|
|
230
|
+
if (marker === 195) return true;
|
|
231
|
+
if (marker === 196) return reader.bytes(reader.byte());
|
|
232
|
+
if (marker === 197) return reader.bytes(reader.u16());
|
|
233
|
+
if (marker === 198) return reader.bytes(reader.u32());
|
|
234
|
+
if (marker === 202) return reader.f32();
|
|
235
|
+
if (marker === 203) return reader.f64();
|
|
236
|
+
if (marker === 204) return reader.byte();
|
|
237
|
+
if (marker === 205) return reader.u16();
|
|
238
|
+
if (marker === 206) return reader.u32();
|
|
239
|
+
if (marker === 207) return reader.u64();
|
|
240
|
+
if (marker === 208) return reader.byte() << 24 >> 24;
|
|
241
|
+
if (marker === 209) return reader.i16();
|
|
242
|
+
if (marker === 210) return reader.i32();
|
|
243
|
+
if (marker === 211) return reader.i64();
|
|
244
|
+
return void 0;
|
|
245
|
+
}
|
|
246
|
+
function unpackOne(reader) {
|
|
247
|
+
const marker = reader.byte();
|
|
248
|
+
if (marker <= 127) return marker;
|
|
249
|
+
if (marker >= 224) return marker - 256;
|
|
250
|
+
const sized = unpackSized(reader, marker);
|
|
251
|
+
if (sized !== void 0) return sized;
|
|
252
|
+
if ((marker & 224) === 160) return unpackString(reader, marker & 31);
|
|
253
|
+
if (marker === 217) return unpackString(reader, reader.byte());
|
|
254
|
+
if (marker === 218) return unpackString(reader, reader.u16());
|
|
255
|
+
if (marker === 219) return unpackString(reader, reader.u32());
|
|
256
|
+
if ((marker & 240) === 144) return unpackArray(reader, marker & 15);
|
|
257
|
+
if (marker === 220) return unpackArray(reader, reader.u16());
|
|
258
|
+
if (marker === 221) return unpackArray(reader, reader.u32());
|
|
259
|
+
if ((marker & 240) === 128) return unpackMap(reader, marker & 15);
|
|
260
|
+
if (marker === 222) return unpackMap(reader, reader.u16());
|
|
261
|
+
if (marker === 223) return unpackMap(reader, reader.u32());
|
|
262
|
+
throw new Error("Unsupported Fish frame");
|
|
263
|
+
}
|
|
264
|
+
function pack(value) {
|
|
265
|
+
const out = new ByteWriter();
|
|
266
|
+
packInto(value, out);
|
|
267
|
+
return out.toArray();
|
|
268
|
+
}
|
|
269
|
+
function unpack(data) {
|
|
270
|
+
const reader = new ByteReader(data);
|
|
271
|
+
const value = unpackOne(reader);
|
|
272
|
+
if (!reader.done()) throw new Error("Fish frame had trailing bytes");
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
function mediaType(mimeType) {
|
|
276
|
+
const type = mimeType.split(";")[0]?.trim().toLowerCase();
|
|
277
|
+
if (!type) throw new Error(`Unsupported audio type ${mimeType}`);
|
|
278
|
+
return type;
|
|
279
|
+
}
|
|
280
|
+
function audioFilename(mimeType) {
|
|
281
|
+
const name = filenames[mediaType(mimeType)];
|
|
282
|
+
if (!name) throw new Error(`Unsupported audio type ${mimeType}`);
|
|
283
|
+
return name;
|
|
284
|
+
}
|
|
285
|
+
function encodeForm(file, fields) {
|
|
286
|
+
const chunks = [];
|
|
287
|
+
const encoder = new TextEncoder();
|
|
288
|
+
const write = (value) => {
|
|
289
|
+
chunks.push(encoder.encode(value));
|
|
290
|
+
};
|
|
291
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
292
|
+
write(`--${boundary}\r
|
|
293
|
+
Content-Disposition: form-data; name="${name}"\r
|
|
294
|
+
\r
|
|
295
|
+
${value}\r
|
|
296
|
+
`);
|
|
297
|
+
}
|
|
298
|
+
const header = `--${boundary}\r
|
|
299
|
+
Content-Disposition: form-data; name="audio"; filename="${file.filename}"\r
|
|
300
|
+
Content-Type: ${file.mimeType}\r
|
|
301
|
+
\r
|
|
302
|
+
`;
|
|
303
|
+
write(header);
|
|
304
|
+
chunks.push(file.data);
|
|
305
|
+
write(`\r
|
|
306
|
+
--${boundary}--\r
|
|
307
|
+
`);
|
|
308
|
+
const body = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.length, 0));
|
|
309
|
+
let offset = 0;
|
|
310
|
+
for (const chunk of chunks) {
|
|
311
|
+
body.set(chunk, offset);
|
|
312
|
+
offset += chunk.length;
|
|
313
|
+
}
|
|
314
|
+
return body.buffer;
|
|
315
|
+
}
|
|
316
|
+
function formType() {
|
|
317
|
+
return `multipart/form-data; boundary=${boundary}`;
|
|
318
|
+
}
|
|
319
|
+
function fishHeaders(model, options) {
|
|
320
|
+
const headers = requestHeaders(model, options);
|
|
321
|
+
headers.model = model.id;
|
|
322
|
+
return headers;
|
|
323
|
+
}
|
|
324
|
+
function socketUrl(baseUrl, path) {
|
|
325
|
+
const url = new URL(baseUrl);
|
|
326
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
327
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path}`;
|
|
328
|
+
url.search = "";
|
|
329
|
+
return url;
|
|
330
|
+
}
|
|
331
|
+
function speechFormatName(format) {
|
|
332
|
+
const name = speechNames[format.encoding];
|
|
333
|
+
if (!name) throw new Error(`Unsupported speech format ${format.encoding}`);
|
|
334
|
+
return name;
|
|
335
|
+
}
|
|
336
|
+
function connectSocket(url, headers) {
|
|
337
|
+
const Socket = WebSocket;
|
|
338
|
+
return new Socket(url, { headers });
|
|
339
|
+
}
|
|
340
|
+
function awaitOpen(socket, label, fail) {
|
|
341
|
+
return new Promise((resolve) => {
|
|
342
|
+
let opened = false;
|
|
343
|
+
socket.addEventListener("open", () => {
|
|
344
|
+
opened = true;
|
|
345
|
+
resolve();
|
|
346
|
+
});
|
|
347
|
+
socket.addEventListener("error", () => {
|
|
348
|
+
fail(`${label} socket failed`);
|
|
349
|
+
if (!opened) resolve();
|
|
350
|
+
});
|
|
351
|
+
socket.addEventListener("close", () => {
|
|
352
|
+
if (!opened) {
|
|
353
|
+
fail(`${label} socket closed`);
|
|
354
|
+
resolve();
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
async function messageBytes(data) {
|
|
360
|
+
if (typeof data === "string") return new TextEncoder().encode(data);
|
|
361
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
362
|
+
if (data instanceof Uint8Array) return data;
|
|
363
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) {
|
|
364
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
365
|
+
}
|
|
366
|
+
throw new Error("Unsupported socket message");
|
|
367
|
+
}
|
|
368
|
+
function sameFormat(left, right) {
|
|
369
|
+
return left.encoding === right.encoding && left.sampleRate === right.sampleRate && left.channels === right.channels;
|
|
370
|
+
}
|
|
371
|
+
export {
|
|
372
|
+
audioFilename,
|
|
373
|
+
awaitOpen,
|
|
374
|
+
connectSocket,
|
|
375
|
+
encodeForm,
|
|
376
|
+
fishHeaders,
|
|
377
|
+
formType,
|
|
378
|
+
mediaType,
|
|
379
|
+
messageBytes,
|
|
380
|
+
pack,
|
|
381
|
+
sameFormat,
|
|
382
|
+
socketUrl,
|
|
383
|
+
speechFormatName,
|
|
384
|
+
unpack
|
|
385
|
+
};
|
|
386
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/fish/wire.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioFormat } from '../audio.ts';\nimport { requestHeaders } from '../openai/wire.ts';\n\nconst boundary = 'pi-audio-fish-boundary';\n\nconst filenames: Record<string, string> = {\n 'audio/wav': 'audio.wav',\n 'audio/wave': 'audio.wav',\n 'audio/x-wav': 'audio.wav',\n 'audio/mpeg': 'audio.mp3',\n 'audio/mp3': 'audio.mp3',\n 'audio/flac': 'audio.flac',\n 'audio/ogg': 'audio.ogg',\n 'audio/opus': 'audio.opus',\n 'audio/webm': 'audio.webm',\n 'audio/mp4': 'audio.m4a',\n 'audio/m4a': 'audio.m4a',\n 'audio/aac': 'audio.aac',\n};\n\nconst speechNames: Record<string, string> = {\n pcm16: 'pcm',\n wav: 'wav',\n mp3: 'mp3',\n opus: 'opus',\n};\n\nexport type PackValue =\n | null\n | boolean\n | number\n | string\n | Uint8Array\n | PackValue[]\n | { [key: string]: PackValue };\n\nexport interface FishFile {\n data: Uint8Array;\n mimeType: string;\n filename: string;\n}\n\nclass ByteWriter {\n readonly #bytes: number[] = [];\n\n byte(value: number): void {\n this.#bytes.push(value & 0xff);\n }\n\n u16(value: number): void {\n this.byte(value >> 8);\n this.byte(value);\n }\n\n u32(value: number): void {\n this.byte(value >>> 24);\n this.byte(value >>> 16);\n this.byte(value >>> 8);\n this.byte(value);\n }\n\n f64(value: number): void {\n const view = new DataView(new ArrayBuffer(8));\n view.setFloat64(0, value);\n for (let index = 0; index < 8; index += 1) this.byte(view.getUint8(index));\n }\n\n raw(bytes: Uint8Array): void {\n for (const byte of bytes) this.byte(byte);\n }\n\n toArray(): Uint8Array {\n return Uint8Array.from(this.#bytes);\n }\n}\n\nclass ByteReader {\n #offset = 0;\n readonly #data: Uint8Array;\n\n constructor(data: Uint8Array) {\n this.#data = data;\n }\n\n byte(): number {\n const value = this.#data[this.#offset];\n if (value === undefined) throw new Error('Fish frame ended early');\n this.#offset += 1;\n return value;\n }\n\n u16(): number {\n return (this.byte() << 8) | this.byte();\n }\n\n u32(): number {\n return ((this.byte() << 24) | (this.byte() << 16) | (this.byte() << 8) | this.byte()) >>> 0;\n }\n\n i16(): number {\n const value = this.u16();\n return value > 0x7fff ? value - 0x10000 : value;\n }\n\n i32(): number {\n return this.u32() | 0;\n }\n\n u64(): number {\n const high = this.u32();\n const low = this.u32();\n return high * 0x100000000 + low;\n }\n\n i64(): number {\n const high = this.i32();\n const low = this.u32();\n return high * 0x100000000 + low;\n }\n\n f32(): number {\n const copy = new Uint8Array(4);\n copy.set(this.bytes(4));\n return new DataView(copy.buffer).getFloat32(0);\n }\n\n f64(): number {\n const copy = new Uint8Array(8);\n copy.set(this.bytes(8));\n return new DataView(copy.buffer).getFloat64(0);\n }\n\n bytes(length: number): Uint8Array {\n const start = this.#offset;\n if (start + length > this.#data.length) throw new Error('Fish frame ended early');\n this.#offset = start + length;\n return this.#data.subarray(start, this.#offset);\n }\n\n done(): boolean {\n return this.#offset === this.#data.length;\n }\n}\n\nfunction packString(value: string, out: ByteWriter): void {\n const bytes = new TextEncoder().encode(value);\n if (bytes.length < 32) out.byte(0xa0 | bytes.length);\n else if (bytes.length < 256) {\n out.byte(0xd9);\n out.byte(bytes.length);\n } else if (bytes.length < 65536) {\n out.byte(0xda);\n out.u16(bytes.length);\n } else throw new Error('String too long');\n out.raw(bytes);\n}\n\nfunction packBin(value: Uint8Array, out: ByteWriter): void {\n if (value.byteLength < 256) {\n out.byte(0xc4);\n out.byte(value.byteLength);\n } else if (value.byteLength < 65536) {\n out.byte(0xc5);\n out.u16(value.byteLength);\n } else throw new Error('Binary too long');\n out.raw(value);\n}\n\nfunction packNumber(value: number, out: ByteWriter): void {\n if (!Number.isInteger(value)) {\n out.byte(0xcb);\n out.f64(value);\n return;\n }\n if (value >= 0 && value <= 0x7f) {\n out.byte(value);\n return;\n }\n if (value < 0 && value >= -32) {\n out.byte(value & 0xff);\n return;\n }\n if (value >= 0 && value <= 0xff) {\n out.byte(0xcc);\n out.byte(value);\n return;\n }\n if (value >= 0 && value <= 0xffff) {\n out.byte(0xcd);\n out.u16(value);\n return;\n }\n if (value >= 0 && value <= 0xffffffff) {\n out.byte(0xce);\n out.u32(value);\n return;\n }\n if (value < 0 && value >= -128) {\n out.byte(0xd0);\n out.byte(value & 0xff);\n return;\n }\n throw new Error('Integer out of range');\n}\n\nfunction packArray(values: PackValue[], out: ByteWriter): void {\n if (values.length < 16) out.byte(0x90 | values.length);\n else if (values.length < 65536) {\n out.byte(0xdc);\n out.u16(values.length);\n } else throw new Error('Array too long');\n for (const value of values) packInto(value, out);\n}\n\nfunction packMap(value: { [key: string]: PackValue }, out: ByteWriter): void {\n const entries = Object.entries(value);\n if (entries.length < 16) out.byte(0x80 | entries.length);\n else if (entries.length < 65536) {\n out.byte(0xde);\n out.u16(entries.length);\n } else throw new Error('Map too long');\n for (const [key, item] of entries) {\n packString(key, out);\n packInto(item, out);\n }\n}\n\nfunction packInto(value: PackValue, out: ByteWriter): void {\n if (value === null) {\n out.byte(0xc0);\n return;\n }\n if (typeof value === 'boolean') {\n out.byte(value ? 0xc3 : 0xc2);\n return;\n }\n if (typeof value === 'number') {\n packNumber(value, out);\n return;\n }\n if (typeof value === 'string') {\n packString(value, out);\n return;\n }\n if (value instanceof Uint8Array) {\n packBin(value, out);\n return;\n }\n if (Array.isArray(value)) {\n packArray(value, out);\n return;\n }\n packMap(value, out);\n}\n\nfunction unpackString(reader: ByteReader, length: number): string {\n return new TextDecoder().decode(reader.bytes(length));\n}\n\nfunction unpackArray(reader: ByteReader, length: number): PackValue[] {\n const values: PackValue[] = [];\n for (let index = 0; index < length; index += 1) values.push(unpackOne(reader));\n return values;\n}\n\nfunction unpackMap(reader: ByteReader, length: number): { [key: string]: PackValue } {\n const value: { [key: string]: PackValue } = {};\n for (let index = 0; index < length; index += 1) {\n const key = unpackOne(reader);\n if (typeof key !== 'string') throw new Error('Fish frame key was not a string');\n value[key] = unpackOne(reader);\n }\n return value;\n}\n\nfunction unpackSized(reader: ByteReader, marker: number): PackValue | undefined {\n if (marker === 0xc0) return null;\n if (marker === 0xc2) return false;\n if (marker === 0xc3) return true;\n if (marker === 0xc4) return reader.bytes(reader.byte());\n if (marker === 0xc5) return reader.bytes(reader.u16());\n if (marker === 0xc6) return reader.bytes(reader.u32());\n if (marker === 0xca) return reader.f32();\n if (marker === 0xcb) return reader.f64();\n if (marker === 0xcc) return reader.byte();\n if (marker === 0xcd) return reader.u16();\n if (marker === 0xce) return reader.u32();\n if (marker === 0xcf) return reader.u64();\n if (marker === 0xd0) return (reader.byte() << 24) >> 24;\n if (marker === 0xd1) return reader.i16();\n if (marker === 0xd2) return reader.i32();\n if (marker === 0xd3) return reader.i64();\n return undefined;\n}\n\nfunction unpackOne(reader: ByteReader): PackValue {\n const marker = reader.byte();\n if (marker <= 0x7f) return marker;\n if (marker >= 0xe0) return marker - 0x100;\n const sized = unpackSized(reader, marker);\n if (sized !== undefined) return sized;\n if ((marker & 0xe0) === 0xa0) return unpackString(reader, marker & 0x1f);\n if (marker === 0xd9) return unpackString(reader, reader.byte());\n if (marker === 0xda) return unpackString(reader, reader.u16());\n if (marker === 0xdb) return unpackString(reader, reader.u32());\n if ((marker & 0xf0) === 0x90) return unpackArray(reader, marker & 0x0f);\n if (marker === 0xdc) return unpackArray(reader, reader.u16());\n if (marker === 0xdd) return unpackArray(reader, reader.u32());\n if ((marker & 0xf0) === 0x80) return unpackMap(reader, marker & 0x0f);\n if (marker === 0xde) return unpackMap(reader, reader.u16());\n if (marker === 0xdf) return unpackMap(reader, reader.u32());\n throw new Error('Unsupported Fish frame');\n}\n\nexport function pack(value: PackValue): Uint8Array {\n const out = new ByteWriter();\n packInto(value, out);\n return out.toArray();\n}\n\nexport function unpack(data: Uint8Array): PackValue {\n const reader = new ByteReader(data);\n const value = unpackOne(reader);\n if (!reader.done()) throw new Error('Fish frame had trailing bytes');\n return value;\n}\n\nexport function mediaType(mimeType: string): string {\n const type = mimeType.split(';')[0]?.trim().toLowerCase();\n if (!type) throw new Error(`Unsupported audio type ${mimeType}`);\n return type;\n}\n\nexport function audioFilename(mimeType: string): string {\n const name = filenames[mediaType(mimeType)];\n if (!name) throw new Error(`Unsupported audio type ${mimeType}`);\n return name;\n}\n\nexport function encodeForm(file: FishFile, fields: Record<string, string>): ArrayBuffer {\n const chunks: Uint8Array[] = [];\n const encoder = new TextEncoder();\n const write = (value: string) => {\n chunks.push(encoder.encode(value));\n };\n for (const [name, value] of Object.entries(fields)) {\n write(`--${boundary}\\r\\nContent-Disposition: form-data; name=\"${name}\"\\r\\n\\r\\n${value}\\r\\n`);\n }\n const header =\n `--${boundary}\\r\\nContent-Disposition: form-data; name=\"audio\"; filename=\"${file.filename}\"\\r\\n` +\n `Content-Type: ${file.mimeType}\\r\\n\\r\\n`;\n write(header);\n chunks.push(file.data);\n write(`\\r\\n--${boundary}--\\r\\n`);\n const body = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.length, 0));\n let offset = 0;\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.length;\n }\n return body.buffer;\n}\n\nexport function formType(): string {\n return `multipart/form-data; boundary=${boundary}`;\n}\n\nexport function fishHeaders(\n model: { id: string; provider: string; headers?: Record<string, string> },\n options: ProviderRequestOptions | undefined\n): Record<string, string> {\n const headers = requestHeaders(model, options);\n headers.model = model.id;\n return headers;\n}\n\nexport function socketUrl(baseUrl: string, path: string): URL {\n const url = new URL(baseUrl);\n url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';\n url.pathname = `${url.pathname.replace(/\\/$/, '')}/${path}`;\n url.search = '';\n return url;\n}\n\nexport function speechFormatName(format: AudioFormat): string {\n const name = speechNames[format.encoding];\n if (!name) throw new Error(`Unsupported speech format ${format.encoding}`);\n return name;\n}\n\nexport function connectSocket(url: string, headers: Record<string, string>): WebSocket {\n const Socket = WebSocket as unknown as new (\n address: string,\n init: { headers: Record<string, string> }\n ) => WebSocket;\n return new Socket(url, { headers });\n}\n\nexport function awaitOpen(\n socket: WebSocket,\n label: string,\n fail: (message: string) => void\n): Promise<void> {\n return new Promise((resolve) => {\n let opened = false;\n socket.addEventListener('open', () => {\n opened = true;\n resolve();\n });\n socket.addEventListener('error', () => {\n fail(`${label} socket failed`);\n if (!opened) resolve();\n });\n socket.addEventListener('close', () => {\n if (!opened) {\n fail(`${label} socket closed`);\n resolve();\n }\n });\n });\n}\n\nexport async function messageBytes(data: unknown): Promise<Uint8Array> {\n if (typeof data === 'string') return new TextEncoder().encode(data);\n if (data instanceof ArrayBuffer) return new Uint8Array(data);\n if (data instanceof Uint8Array) return data;\n if (typeof Blob !== 'undefined' && data instanceof Blob) {\n return new Uint8Array(await data.arrayBuffer());\n }\n throw new Error('Unsupported socket message');\n}\n\nexport function sameFormat(left: AudioFormat, right: AudioFormat): boolean {\n return (\n left.encoding === right.encoding &&\n left.sampleRate === right.sampleRate &&\n left.channels === right.channels\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { registerVoiceModel } from "../models.js";
|
|
2
|
+
import { registerSpeechApiProvider } from "../registry.js";
|
|
3
|
+
import { speakGoogle } from "./speak.js";
|
|
4
|
+
const tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
5
|
+
const google = "https://generativelanguage.googleapis.com/v1beta";
|
|
6
|
+
const format = (encoding, sampleRate) => ({
|
|
7
|
+
encoding,
|
|
8
|
+
sampleRate,
|
|
9
|
+
channels: 1
|
|
10
|
+
});
|
|
11
|
+
const outputFormats = [
|
|
12
|
+
format("wav", 24e3),
|
|
13
|
+
format("pcm16", 24e3),
|
|
14
|
+
format("mulaw", 8e3),
|
|
15
|
+
format("alaw", 8e3)
|
|
16
|
+
];
|
|
17
|
+
const voices = [
|
|
18
|
+
{ id: "Kore", name: "Kore" },
|
|
19
|
+
{ id: "Puck", name: "Puck" },
|
|
20
|
+
{ id: "Charon", name: "Charon" },
|
|
21
|
+
{ id: "Fenrir", name: "Fenrir" },
|
|
22
|
+
{ id: "Aoede", name: "Aoede" }
|
|
23
|
+
];
|
|
24
|
+
function speechModel(id, perOutputMinute) {
|
|
25
|
+
return {
|
|
26
|
+
id,
|
|
27
|
+
name: id,
|
|
28
|
+
api: "google-tts",
|
|
29
|
+
provider: "google",
|
|
30
|
+
baseUrl: google,
|
|
31
|
+
type: "speech",
|
|
32
|
+
textStreaming: false,
|
|
33
|
+
outputFormats,
|
|
34
|
+
voices,
|
|
35
|
+
cloning: true,
|
|
36
|
+
cost: tokenCost,
|
|
37
|
+
audioCost: { perOutputMinute }
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function registerGoogle() {
|
|
41
|
+
const models = [
|
|
42
|
+
speechModel("gemini-3.8-flash-tts", 25 * 9 * 60 / 1e6),
|
|
43
|
+
speechModel("gemini-3.8-flash-lite-tts", 25 * 6 * 60 / 1e6)
|
|
44
|
+
];
|
|
45
|
+
const unregisterModels = models.map((model) => registerVoiceModel(model));
|
|
46
|
+
const unregisterSpeech = registerSpeechApiProvider({
|
|
47
|
+
api: "google-tts",
|
|
48
|
+
speak: speakGoogle
|
|
49
|
+
});
|
|
50
|
+
let released = false;
|
|
51
|
+
return () => {
|
|
52
|
+
if (released) return;
|
|
53
|
+
released = true;
|
|
54
|
+
for (const unregister of unregisterModels) unregister();
|
|
55
|
+
unregisterSpeech();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
registerGoogle
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/google/catalog.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AudioFormat } from '../audio.ts';\nimport { registerVoiceModel, type SpeechModel, type VoiceInfo } from '../models.ts';\nimport { registerSpeechApiProvider } from '../registry.ts';\nimport { speakGoogle } from './speak.ts';\n\nconst tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\nconst google = 'https://generativelanguage.googleapis.com/v1beta';\n\nconst format = (encoding: 'wav' | 'pcm16' | 'mulaw' | 'alaw', sampleRate: number): AudioFormat => ({\n encoding,\n sampleRate,\n channels: 1,\n});\n\nconst outputFormats = [\n format('wav', 24000),\n format('pcm16', 24000),\n format('mulaw', 8000),\n format('alaw', 8000),\n];\n\nconst voices: VoiceInfo[] = [\n { id: 'Kore', name: 'Kore' },\n { id: 'Puck', name: 'Puck' },\n { id: 'Charon', name: 'Charon' },\n { id: 'Fenrir', name: 'Fenrir' },\n { id: 'Aoede', name: 'Aoede' },\n];\n\nfunction speechModel(id: string, perOutputMinute: number): SpeechModel<string> {\n return {\n id,\n name: id,\n api: 'google-tts',\n provider: 'google',\n baseUrl: google,\n type: 'speech',\n textStreaming: false,\n outputFormats,\n voices,\n cloning: true,\n cost: tokenCost,\n audioCost: { perOutputMinute },\n };\n}\n\nexport function registerGoogle(): () => void {\n const models = [\n speechModel('gemini-3.8-flash-tts', (25 * 9 * 60) / 1_000_000),\n speechModel('gemini-3.8-flash-lite-tts', (25 * 6 * 60) / 1_000_000),\n ];\n const unregisterModels = models.map((model) => registerVoiceModel(model));\n const unregisterSpeech = registerSpeechApiProvider({\n api: 'google-tts',\n speak: speakGoogle,\n });\n let released = false;\n return () => {\n if (released) return;\n released = true;\n for (const unregister of unregisterModels) unregister();\n unregisterSpeech();\n };\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,0BAA4D;AACrE,SAAS,iCAAiC;AAC1C,SAAS,mBAAmB;AAE5B,MAAM,YAAY,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AACrE,MAAM,SAAS;AAEf,MAAM,SAAS,CAAC,UAA8C,gBAAqC;AAAA,EACjG;AAAA,EACA;AAAA,EACA,UAAU;AACZ;AAEA,MAAM,gBAAgB;AAAA,EACpB,OAAO,OAAO,IAAK;AAAA,EACnB,OAAO,SAAS,IAAK;AAAA,EACrB,OAAO,SAAS,GAAI;AAAA,EACpB,OAAO,QAAQ,GAAI;AACrB;AAEA,MAAM,SAAsB;AAAA,EAC1B,EAAE,IAAI,QAAQ,MAAM,OAAO;AAAA,EAC3B,EAAE,IAAI,QAAQ,MAAM,OAAO;AAAA,EAC3B,EAAE,IAAI,UAAU,MAAM,SAAS;AAAA,EAC/B,EAAE,IAAI,UAAU,MAAM,SAAS;AAAA,EAC/B,EAAE,IAAI,SAAS,MAAM,QAAQ;AAC/B;AAEA,SAAS,YAAY,IAAY,iBAA8C;AAC7E,SAAO;AAAA,IACL;AAAA,IACA,MAAM;AAAA,IACN,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,eAAe;AAAA,IACf;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT,MAAM;AAAA,IACN,WAAW,EAAE,gBAAgB;AAAA,EAC/B;AACF;AAEO,SAAS,iBAA6B;AAC3C,QAAM,SAAS;AAAA,IACb,YAAY,wBAAyB,KAAK,IAAI,KAAM,GAAS;AAAA,IAC7D,YAAY,6BAA8B,KAAK,IAAI,KAAM,GAAS;AAAA,EACpE;AACA,QAAM,mBAAmB,OAAO,IAAI,CAAC,UAAU,mBAAmB,KAAK,CAAC;AACxE,QAAM,mBAAmB,0BAA0B;AAAA,IACjD,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AACD,MAAI,WAAW;AACf,SAAO,MAAM;AACX,QAAI,SAAU;AACd,eAAW;AACX,eAAW,cAAc,iBAAkB,YAAW;AACtD,qBAAiB;AAAA,EACnB;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { chunkSeconds } from "../audio.js";
|
|
2
|
+
import { decodeAudioBase64 } from "../convert.js";
|
|
3
|
+
import {
|
|
4
|
+
applyPayload,
|
|
5
|
+
asVoiceError,
|
|
6
|
+
endpoint,
|
|
7
|
+
isRecord,
|
|
8
|
+
readJson,
|
|
9
|
+
requestSignal,
|
|
10
|
+
stringField
|
|
11
|
+
} from "../openai/wire.js";
|
|
12
|
+
import { SpeechEventStream } from "../speech.js";
|
|
13
|
+
import { priceAudio } from "../usage.js";
|
|
14
|
+
import { googleHeaders, sameFormat, speechMime } from "./wire.js";
|
|
15
|
+
function resolveFormat(model, requested) {
|
|
16
|
+
const fallback = model.outputFormats[0];
|
|
17
|
+
if (!fallback) throw new Error("No output format");
|
|
18
|
+
if (!requested) return fallback;
|
|
19
|
+
const match = model.outputFormats.find((format) => sameFormat(format, requested));
|
|
20
|
+
if (!match) throw new Error(`Unsupported speech format ${requested.encoding}`);
|
|
21
|
+
return match;
|
|
22
|
+
}
|
|
23
|
+
function speechBody(text, voice, format) {
|
|
24
|
+
return {
|
|
25
|
+
contents: [{ role: "user", parts: [{ text }] }],
|
|
26
|
+
generationConfig: {
|
|
27
|
+
responseModalities: ["AUDIO"],
|
|
28
|
+
responseFormat: { audio: { mimeType: speechMime(format) } },
|
|
29
|
+
speechConfig: {
|
|
30
|
+
voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } }
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function readText(text, signal) {
|
|
36
|
+
if (typeof text === "string") return text;
|
|
37
|
+
let combined = "";
|
|
38
|
+
for await (const part of text) {
|
|
39
|
+
if (signal?.aborted) throw new DOMException("aborted", "AbortError");
|
|
40
|
+
combined += part;
|
|
41
|
+
}
|
|
42
|
+
return combined;
|
|
43
|
+
}
|
|
44
|
+
function errorText(text, status) {
|
|
45
|
+
if (text === "") return `HTTP ${status}`;
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(text);
|
|
49
|
+
} catch {
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(parsed) || Array.isArray(parsed)) return text;
|
|
53
|
+
const direct = stringField(parsed, "message");
|
|
54
|
+
if (direct !== void 0) return direct;
|
|
55
|
+
const error = parsed.error;
|
|
56
|
+
if (typeof error === "string") return error;
|
|
57
|
+
if (isRecord(error) && !Array.isArray(error)) {
|
|
58
|
+
const nested = stringField(error, "message");
|
|
59
|
+
if (nested !== void 0) return nested;
|
|
60
|
+
}
|
|
61
|
+
return text;
|
|
62
|
+
}
|
|
63
|
+
function inlineData(part) {
|
|
64
|
+
const camel = part.inlineData;
|
|
65
|
+
const snake = part.inline_data;
|
|
66
|
+
const inline = isRecord(camel) && !Array.isArray(camel) ? camel : snake;
|
|
67
|
+
if (!isRecord(inline) || Array.isArray(inline)) return void 0;
|
|
68
|
+
return stringField(inline, "data");
|
|
69
|
+
}
|
|
70
|
+
function audioParts(body) {
|
|
71
|
+
const candidates = body.candidates;
|
|
72
|
+
if (!Array.isArray(candidates)) return [];
|
|
73
|
+
const first = candidates[0];
|
|
74
|
+
if (!isRecord(first) || Array.isArray(first)) return [];
|
|
75
|
+
const content = first.content;
|
|
76
|
+
if (!isRecord(content) || Array.isArray(content)) return [];
|
|
77
|
+
const parts = content.parts;
|
|
78
|
+
if (!Array.isArray(parts)) return [];
|
|
79
|
+
const chunks = [];
|
|
80
|
+
for (const part of parts) {
|
|
81
|
+
if (!isRecord(part) || Array.isArray(part)) continue;
|
|
82
|
+
const data = inlineData(part);
|
|
83
|
+
if (data === void 0 || data === "") continue;
|
|
84
|
+
chunks.push(data);
|
|
85
|
+
}
|
|
86
|
+
return chunks;
|
|
87
|
+
}
|
|
88
|
+
function concat(chunks) {
|
|
89
|
+
const length = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
|
|
90
|
+
const out = new Uint8Array(length);
|
|
91
|
+
let offset = 0;
|
|
92
|
+
for (const chunk of chunks) {
|
|
93
|
+
out.set(chunk, offset);
|
|
94
|
+
offset += chunk.byteLength;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
async function googleFetch(model, options, url, body) {
|
|
99
|
+
const headers = googleHeaders(model, options);
|
|
100
|
+
headers["Content-Type"] = "application/json";
|
|
101
|
+
const fetchImpl = options?.fetch ?? globalThis.fetch;
|
|
102
|
+
const response = await fetchImpl(url, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers,
|
|
105
|
+
body,
|
|
106
|
+
signal: requestSignal(options)
|
|
107
|
+
});
|
|
108
|
+
await options?.onResponse?.({ status: response.status, headers: {} }, model);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
const text = await response.text();
|
|
111
|
+
throw new Error(errorText(text, response.status));
|
|
112
|
+
}
|
|
113
|
+
return response;
|
|
114
|
+
}
|
|
115
|
+
function speakGoogle(model, input, options) {
|
|
116
|
+
const stream = new SpeechEventStream();
|
|
117
|
+
void pumpSpeech(model, input, options, stream);
|
|
118
|
+
return stream;
|
|
119
|
+
}
|
|
120
|
+
async function pumpSpeech(model, input, options, stream) {
|
|
121
|
+
try {
|
|
122
|
+
if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
|
|
123
|
+
const format = resolveFormat(model, input.format);
|
|
124
|
+
const text = await readText(input.text, options?.signal);
|
|
125
|
+
await postSpeech(model, text, input.voice, format, options, stream);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
const voice = asVoiceError(error);
|
|
128
|
+
stream.push({ type: "error", reason: voice.reason, error: voice.error });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function postSpeech(model, text, voice, format, options, stream) {
|
|
132
|
+
const payload = speechBody(text, voice, format);
|
|
133
|
+
const body = await applyPayload(payload, model, options);
|
|
134
|
+
if (!isRecord(body) || Array.isArray(body)) throw new Error("Speech payload was not JSON");
|
|
135
|
+
const response = await googleFetch(
|
|
136
|
+
model,
|
|
137
|
+
options,
|
|
138
|
+
endpoint(model.baseUrl, `models/${encodeURIComponent(model.id)}:generateContent`),
|
|
139
|
+
JSON.stringify(body)
|
|
140
|
+
);
|
|
141
|
+
const parsed = await readJson(response);
|
|
142
|
+
if (!isRecord(parsed) || Array.isArray(parsed)) throw new Error("Speech response had no audio");
|
|
143
|
+
const encoded = audioParts(parsed);
|
|
144
|
+
if (encoded.length === 0) throw new Error("Speech response had no audio");
|
|
145
|
+
const data = concat(encoded.map((item) => decodeAudioBase64(item)));
|
|
146
|
+
const seconds = chunkSeconds({ data, format, timestamp: 0 });
|
|
147
|
+
const meters = { characters: text.length };
|
|
148
|
+
if (seconds > 0) meters.outputSeconds = seconds;
|
|
149
|
+
stream.push({ type: "start", format });
|
|
150
|
+
stream.push({
|
|
151
|
+
type: "audio",
|
|
152
|
+
chunk: { data, format, timestamp: 0 },
|
|
153
|
+
span: { itemId: model.id, start: 0, end: text.length }
|
|
154
|
+
});
|
|
155
|
+
stream.push({ type: "done", usage: priceAudio(model.audioCost, meters) });
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
speakGoogle
|
|
159
|
+
};
|
|
160
|
+
//# sourceMappingURL=speak.js.map
|