@ai-ecoverse/pi-audio 1.12.0 → 1.14.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 +3 -0
- package/dist/captions.js +150 -0
- package/dist/captions.js.map +7 -0
- package/dist/elevenlabs/catalog.js +136 -0
- package/dist/elevenlabs/catalog.js.map +7 -0
- package/dist/elevenlabs/index.js +5 -0
- package/dist/elevenlabs/index.js.map +7 -0
- package/dist/elevenlabs/listen.js +268 -0
- package/dist/elevenlabs/listen.js.map +7 -0
- package/dist/elevenlabs/speak.js +251 -0
- package/dist/elevenlabs/speak.js.map +7 -0
- package/dist/elevenlabs/transcribe.js +132 -0
- package/dist/elevenlabs/transcribe.js.map +7 -0
- package/dist/elevenlabs/wire.js +215 -0
- package/dist/elevenlabs/wire.js.map +7 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +2 -2
- package/dist/turns.js +51 -13
- package/dist/turns.js.map +2 -2
- package/dist/types/captions.d.ts +31 -0
- package/dist/types/elevenlabs/catalog.d.ts +1 -0
- package/dist/types/elevenlabs/index.d.ts +1 -0
- package/dist/types/elevenlabs/listen.d.ts +4 -0
- package/dist/types/elevenlabs/speak.d.ts +4 -0
- package/dist/types/elevenlabs/transcribe.d.ts +4 -0
- package/dist/types/elevenlabs/wire.d.ts +26 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/turns.d.ts +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { isRecord } from "../openai/wire.js";
|
|
2
|
+
const boundary = "pi-audio-elevenlabs-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/pcm": "audio.pcm",
|
|
13
|
+
"audio/l16": "audio.pcm",
|
|
14
|
+
"audio/mulaw": "audio.mulaw",
|
|
15
|
+
"audio/basic": "audio.mulaw",
|
|
16
|
+
"audio/alaw": "audio.alaw",
|
|
17
|
+
"audio/mp4": "audio.mp4",
|
|
18
|
+
"audio/m4a": "audio.m4a",
|
|
19
|
+
"audio/aac": "audio.aac",
|
|
20
|
+
"audio/webm": "audio.webm"
|
|
21
|
+
};
|
|
22
|
+
const sttFormats = /* @__PURE__ */ new Set([
|
|
23
|
+
"pcm_8000",
|
|
24
|
+
"pcm_16000",
|
|
25
|
+
"pcm_22050",
|
|
26
|
+
"pcm_24000",
|
|
27
|
+
"pcm_44100",
|
|
28
|
+
"pcm_48000",
|
|
29
|
+
"ulaw_8000"
|
|
30
|
+
]);
|
|
31
|
+
function mediaType(mimeType) {
|
|
32
|
+
const type = mimeType.split(";")[0]?.trim().toLowerCase();
|
|
33
|
+
if (!type) throw new Error(`Unsupported audio type ${mimeType}`);
|
|
34
|
+
return type;
|
|
35
|
+
}
|
|
36
|
+
function audioFilename(mimeType) {
|
|
37
|
+
const name = filenames[mediaType(mimeType)];
|
|
38
|
+
if (!name) throw new Error(`Unsupported audio type ${mimeType}`);
|
|
39
|
+
return name;
|
|
40
|
+
}
|
|
41
|
+
function parameter(mimeType, name) {
|
|
42
|
+
for (const part of mimeType.split(";").slice(1)) {
|
|
43
|
+
const pieces = part.split("=");
|
|
44
|
+
const key = pieces[0]?.trim().toLowerCase();
|
|
45
|
+
const value = pieces[1]?.trim();
|
|
46
|
+
if (key === name && value) return value;
|
|
47
|
+
}
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
function pcmS16(mimeType) {
|
|
51
|
+
const type = mediaType(mimeType);
|
|
52
|
+
if (type !== "audio/pcm" && type !== "audio/l16") return false;
|
|
53
|
+
const rate = parameter(mimeType, "rate");
|
|
54
|
+
if (rate === void 0) return true;
|
|
55
|
+
return rate === "16000";
|
|
56
|
+
}
|
|
57
|
+
function encodeForm(file, fields) {
|
|
58
|
+
const chunks = [];
|
|
59
|
+
const encoder = new TextEncoder();
|
|
60
|
+
const write = (value) => {
|
|
61
|
+
chunks.push(encoder.encode(value));
|
|
62
|
+
};
|
|
63
|
+
for (const [name, value] of Object.entries(fields)) {
|
|
64
|
+
write(`--${boundary}\r
|
|
65
|
+
Content-Disposition: form-data; name="${name}"\r
|
|
66
|
+
\r
|
|
67
|
+
${value}\r
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
const header = `--${boundary}\r
|
|
71
|
+
Content-Disposition: form-data; name="file"; filename="${file.filename}"\r
|
|
72
|
+
Content-Type: ${file.mimeType}\r
|
|
73
|
+
\r
|
|
74
|
+
`;
|
|
75
|
+
write(header);
|
|
76
|
+
chunks.push(file.data);
|
|
77
|
+
write(`\r
|
|
78
|
+
--${boundary}--\r
|
|
79
|
+
`);
|
|
80
|
+
const body = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.length, 0));
|
|
81
|
+
let offset = 0;
|
|
82
|
+
for (const chunk of chunks) {
|
|
83
|
+
body.set(chunk, offset);
|
|
84
|
+
offset += chunk.length;
|
|
85
|
+
}
|
|
86
|
+
return body.buffer;
|
|
87
|
+
}
|
|
88
|
+
function formType() {
|
|
89
|
+
return `multipart/form-data; boundary=${boundary}`;
|
|
90
|
+
}
|
|
91
|
+
function headerValue(headers, name) {
|
|
92
|
+
if (!headers) return void 0;
|
|
93
|
+
const expected = name.toLowerCase();
|
|
94
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
95
|
+
if (key.toLowerCase() === expected && value !== null && value.trim() !== "") return value;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function xiHeaders(model, options) {
|
|
100
|
+
const headers = { ...model.headers };
|
|
101
|
+
const extra = options?.headers;
|
|
102
|
+
if (extra) {
|
|
103
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
104
|
+
if (value === null) delete headers[key];
|
|
105
|
+
else headers[key] = value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (headerValue(headers, "xi-api-key") === void 0) {
|
|
109
|
+
const apiKey = options?.apiKey;
|
|
110
|
+
if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
|
|
111
|
+
headers["xi-api-key"] = apiKey;
|
|
112
|
+
}
|
|
113
|
+
return headers;
|
|
114
|
+
}
|
|
115
|
+
function socketUrl(baseUrl, path) {
|
|
116
|
+
const url = new URL(baseUrl);
|
|
117
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
118
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}/${path}`;
|
|
119
|
+
url.search = "";
|
|
120
|
+
return url;
|
|
121
|
+
}
|
|
122
|
+
function outputFormatName(format) {
|
|
123
|
+
if (format.encoding === "mp3" && format.sampleRate === 44100) return "mp3_44100_128";
|
|
124
|
+
if (format.encoding === "mp3" && format.sampleRate === 24e3) return "mp3_24000_48";
|
|
125
|
+
if (format.encoding === "mp3" && format.sampleRate === 22050) return "mp3_22050_32";
|
|
126
|
+
if (format.encoding === "pcm16") return `pcm_${format.sampleRate}`;
|
|
127
|
+
if (format.encoding === "wav") return `wav_${format.sampleRate}`;
|
|
128
|
+
if (format.encoding === "mulaw") return "ulaw_8000";
|
|
129
|
+
if (format.encoding === "alaw") return "alaw_8000";
|
|
130
|
+
if (format.encoding === "opus") return "opus_48000_128";
|
|
131
|
+
throw new Error(`Unsupported speech format ${format.encoding}`);
|
|
132
|
+
}
|
|
133
|
+
function sttAudioFormat(format) {
|
|
134
|
+
const name = format.encoding === "mulaw" ? "ulaw_8000" : `pcm_${format.sampleRate}`;
|
|
135
|
+
if (format.encoding !== "mulaw" && format.encoding !== "pcm16") {
|
|
136
|
+
throw new Error(`Unsupported audio format ${format.encoding}`);
|
|
137
|
+
}
|
|
138
|
+
if (!sttFormats.has(name)) throw new Error(`Unsupported audio format ${format.encoding}`);
|
|
139
|
+
return name;
|
|
140
|
+
}
|
|
141
|
+
function connectSocket(url, headers) {
|
|
142
|
+
const Socket = WebSocket;
|
|
143
|
+
return new Socket(url, { headers });
|
|
144
|
+
}
|
|
145
|
+
function awaitOpen(socket, label, fail) {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
let opened = false;
|
|
148
|
+
socket.addEventListener("open", () => {
|
|
149
|
+
opened = true;
|
|
150
|
+
resolve();
|
|
151
|
+
});
|
|
152
|
+
socket.addEventListener("error", () => {
|
|
153
|
+
fail(`${label} socket failed`);
|
|
154
|
+
if (!opened) resolve();
|
|
155
|
+
});
|
|
156
|
+
socket.addEventListener("close", () => {
|
|
157
|
+
if (!opened) {
|
|
158
|
+
fail(`${label} socket closed`);
|
|
159
|
+
resolve();
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async function messageText(data) {
|
|
165
|
+
if (typeof data === "string") return data;
|
|
166
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
167
|
+
if (data instanceof Uint8Array) return new TextDecoder().decode(data);
|
|
168
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
|
|
169
|
+
throw new Error("Unsupported socket message");
|
|
170
|
+
}
|
|
171
|
+
function sameFormat(left, right) {
|
|
172
|
+
return left.encoding === right.encoding && left.sampleRate === right.sampleRate && left.channels === right.channels;
|
|
173
|
+
}
|
|
174
|
+
function numberList(value) {
|
|
175
|
+
if (!Array.isArray(value)) return void 0;
|
|
176
|
+
const numbers = [];
|
|
177
|
+
for (const item of value) {
|
|
178
|
+
if (typeof item === "number") numbers.push(item);
|
|
179
|
+
}
|
|
180
|
+
return numbers;
|
|
181
|
+
}
|
|
182
|
+
function durationMs(start, end) {
|
|
183
|
+
if (start === void 0 || end === void 0) return 0;
|
|
184
|
+
return (end - start) * 1e3;
|
|
185
|
+
}
|
|
186
|
+
function alignmentFrom(value) {
|
|
187
|
+
if (!isRecord(value)) return void 0;
|
|
188
|
+
const startMs = numberList(value.charStartTimesMs);
|
|
189
|
+
const durationMsList = numberList(value.charDurationsMs);
|
|
190
|
+
if (startMs && durationMsList) return { startMs, durationMs: durationMsList };
|
|
191
|
+
const starts = numberList(value.character_start_times_seconds);
|
|
192
|
+
const ends = numberList(value.character_end_times_seconds);
|
|
193
|
+
if (!starts || !ends) return void 0;
|
|
194
|
+
return {
|
|
195
|
+
startMs: starts.map((start) => start * 1e3),
|
|
196
|
+
durationMs: starts.map((start, index) => durationMs(start, ends[index]))
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
alignmentFrom,
|
|
201
|
+
audioFilename,
|
|
202
|
+
awaitOpen,
|
|
203
|
+
connectSocket,
|
|
204
|
+
encodeForm,
|
|
205
|
+
formType,
|
|
206
|
+
mediaType,
|
|
207
|
+
messageText,
|
|
208
|
+
outputFormatName,
|
|
209
|
+
pcmS16,
|
|
210
|
+
sameFormat,
|
|
211
|
+
socketUrl,
|
|
212
|
+
sttAudioFormat,
|
|
213
|
+
xiHeaders
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=wire.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/elevenlabs/wire.ts"],
|
|
4
|
+
"sourcesContent": ["import type { ProviderRequestOptions } from '@earendil-works/pi-ai';\nimport type { AudioFormat, CharAlignment } from '../audio.ts';\nimport { isRecord } from '../openai/wire.ts';\n\nconst boundary = 'pi-audio-elevenlabs-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/pcm': 'audio.pcm',\n 'audio/l16': 'audio.pcm',\n 'audio/mulaw': 'audio.mulaw',\n 'audio/basic': 'audio.mulaw',\n 'audio/alaw': 'audio.alaw',\n 'audio/mp4': 'audio.mp4',\n 'audio/m4a': 'audio.m4a',\n 'audio/aac': 'audio.aac',\n 'audio/webm': 'audio.webm',\n};\n\nconst sttFormats = new Set([\n 'pcm_8000',\n 'pcm_16000',\n 'pcm_22050',\n 'pcm_24000',\n 'pcm_44100',\n 'pcm_48000',\n 'ulaw_8000',\n]);\n\nexport interface ElevenFile {\n data: Uint8Array;\n mimeType: string;\n filename: string;\n}\n\ninterface HeaderModel {\n provider: string;\n headers?: Record<string, string>;\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\nfunction parameter(mimeType: string, name: string): string | undefined {\n for (const part of mimeType.split(';').slice(1)) {\n const pieces = part.split('=');\n const key = pieces[0]?.trim().toLowerCase();\n const value = pieces[1]?.trim();\n if (key === name && value) return value;\n }\n return undefined;\n}\n\nexport function pcmS16(mimeType: string): boolean {\n const type = mediaType(mimeType);\n if (type !== 'audio/pcm' && type !== 'audio/l16') return false;\n const rate = parameter(mimeType, 'rate');\n if (rate === undefined) return true;\n return rate === '16000';\n}\n\nexport function encodeForm(file: ElevenFile, 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=\"file\"; 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\nfunction headerValue(\n headers: Record<string, string | null> | undefined,\n name: string\n): string | undefined {\n if (!headers) return undefined;\n const expected = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected && value !== null && value.trim() !== '') return value;\n }\n return undefined;\n}\n\nexport function xiHeaders(\n model: HeaderModel,\n options: ProviderRequestOptions | undefined\n): Record<string, string> {\n const headers: Record<string, string> = { ...model.headers };\n const extra = options?.headers;\n if (extra) {\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) delete headers[key];\n else headers[key] = value;\n }\n }\n if (headerValue(headers, 'xi-api-key') === undefined) {\n const apiKey = options?.apiKey;\n if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);\n headers['xi-api-key'] = apiKey;\n }\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 outputFormatName(format: AudioFormat): string {\n if (format.encoding === 'mp3' && format.sampleRate === 44100) return 'mp3_44100_128';\n if (format.encoding === 'mp3' && format.sampleRate === 24000) return 'mp3_24000_48';\n if (format.encoding === 'mp3' && format.sampleRate === 22050) return 'mp3_22050_32';\n if (format.encoding === 'pcm16') return `pcm_${format.sampleRate}`;\n if (format.encoding === 'wav') return `wav_${format.sampleRate}`;\n if (format.encoding === 'mulaw') return 'ulaw_8000';\n if (format.encoding === 'alaw') return 'alaw_8000';\n if (format.encoding === 'opus') return 'opus_48000_128';\n throw new Error(`Unsupported speech format ${format.encoding}`);\n}\n\nexport function sttAudioFormat(format: AudioFormat): string {\n const name = format.encoding === 'mulaw' ? 'ulaw_8000' : `pcm_${format.sampleRate}`;\n if (format.encoding !== 'mulaw' && format.encoding !== 'pcm16') {\n throw new Error(`Unsupported audio format ${format.encoding}`);\n }\n if (!sttFormats.has(name)) throw new Error(`Unsupported audio 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 messageText(data: unknown): Promise<string> {\n if (typeof data === 'string') return data;\n if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);\n if (data instanceof Uint8Array) return new TextDecoder().decode(data);\n if (typeof Blob !== 'undefined' && data instanceof Blob) return data.text();\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\nfunction numberList(value: unknown): number[] | undefined {\n if (!Array.isArray(value)) return undefined;\n const numbers: number[] = [];\n for (const item of value) {\n if (typeof item === 'number') numbers.push(item);\n }\n return numbers;\n}\n\nfunction durationMs(start: number | undefined, end: number | undefined): number {\n if (start === undefined || end === undefined) return 0;\n return (end - start) * 1000;\n}\n\nexport function alignmentFrom(value: unknown): CharAlignment | undefined {\n if (!isRecord(value)) return undefined;\n const startMs = numberList(value.charStartTimesMs);\n const durationMsList = numberList(value.charDurationsMs);\n if (startMs && durationMsList) return { startMs, durationMs: durationMsList };\n const starts = numberList(value.character_start_times_seconds);\n const ends = numberList(value.character_end_times_seconds);\n if (!starts || !ends) return undefined;\n return {\n startMs: starts.map((start) => start * 1000),\n durationMs: starts.map((start, index) => durationMs(start, ends[index])),\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolveVoiceAuth } from "./auth.js";
|
|
2
2
|
import { blankUsage } from "./blank-usage.js";
|
|
3
|
+
import { captionsFromHistory } from "./captions.js";
|
|
3
4
|
import {
|
|
4
5
|
convertAudio,
|
|
5
6
|
convertAudioSource,
|
|
@@ -38,6 +39,7 @@ export {
|
|
|
38
39
|
askAgent,
|
|
39
40
|
bindThinker,
|
|
40
41
|
blankUsage,
|
|
42
|
+
captionsFromHistory,
|
|
41
43
|
connectRealtime,
|
|
42
44
|
convertAudio,
|
|
43
45
|
convertAudioSource,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport {\n convertAudio,\n convertAudioSource,\n decodeAudioBase64,\n encodeAudioBase64,\n} from './convert.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n DeliveryPlan,\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeSocket,\n RealtimeToken,\n TruncationPlan,\n} from './realtime.ts';\nexport { planDelivery, planTruncation, RealtimeLedger, realtimeSocket } from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type { VoiceAgentEvent, VoiceMessageChange } from './spoken.ts';\nexport type {\n BindThinkerOptions,\n BoundThinker,\n Thinker,\n ThinkerCaption,\n ThinkerPhase,\n ThinkerUpdate,\n} from './talker.ts';\nexport { askAgent, bindThinker, talkerTools } from './talker.ts';\nexport type { RealtimeConstraints } from './tokens.ts';\nexport { mintRealtimeToken } from './tokens.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n BargeIn,\n CaptionEvent,\n EchoRecommendation,\n VoiceConversation,\n VoiceCut,\n VoiceModels,\n VoicePhase,\n VoiceSession,\n VoiceTurnMeta,\n VoiceTurnOptions,\n} from './turns.ts';\nexport { voiceTurns } from './turns.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\nexport type { VadHook, VadUpdate } from './vad.ts';\nexport { energyVad } from './vad.ts';\n"],
|
|
5
|
-
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;
|
|
4
|
+
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport type { CaptionHistoryEntry } from './captions.ts';\nexport { captionsFromHistory } from './captions.ts';\nexport {\n convertAudio,\n convertAudioSource,\n decodeAudioBase64,\n encodeAudioBase64,\n} from './convert.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n DeliveryPlan,\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeSocket,\n RealtimeToken,\n TruncationPlan,\n} from './realtime.ts';\nexport { planDelivery, planTruncation, RealtimeLedger, realtimeSocket } from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type { VoiceAgentEvent, VoiceMessageChange } from './spoken.ts';\nexport type {\n BindThinkerOptions,\n BoundThinker,\n Thinker,\n ThinkerCaption,\n ThinkerPhase,\n ThinkerUpdate,\n} from './talker.ts';\nexport { askAgent, bindThinker, talkerTools } from './talker.ts';\nexport type { RealtimeConstraints } from './tokens.ts';\nexport { mintRealtimeToken } from './tokens.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n BargeIn,\n CaptionEvent,\n EchoRecommendation,\n VoiceConversation,\n VoiceCut,\n VoiceModels,\n VoicePhase,\n VoiceSession,\n VoiceTurnMeta,\n VoiceTurnOptions,\n} from './turns.ts';\nexport { voiceTurns } from './turns.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\nexport type { VadHook, VadUpdate } from './vad.ts';\nexport { energyVad } from './vad.ts';\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAE3B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYP,SAAS,cAAc,gBAAgB,gBAAgB,sBAAsB;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,yBAAyB;AAUlC,SAAS,UAAU,aAAa,mBAAmB;AAEnD,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;AAazC,SAAS,kBAAkB;AAS3B,SAAS,kBAAkB,kBAAkB;AAE7C,SAAS,iBAAiB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/turns.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chunkSeconds
|
|
3
|
+
} from "./audio.js";
|
|
4
|
+
import { spokenOffset, utteranceIndex } from "./captions.js";
|
|
1
5
|
import { VoiceRequestError } from "./errors.js";
|
|
2
6
|
import { pushQueue } from "./queue.js";
|
|
3
7
|
import {
|
|
@@ -7,7 +11,17 @@ import {
|
|
|
7
11
|
function voiceTurns(models, conversation, mic, out, options, signal) {
|
|
8
12
|
const phases = pushQueue();
|
|
9
13
|
const captions = pushQueue();
|
|
10
|
-
const session = {
|
|
14
|
+
const session = {
|
|
15
|
+
models,
|
|
16
|
+
conversation,
|
|
17
|
+
mic,
|
|
18
|
+
out,
|
|
19
|
+
options,
|
|
20
|
+
signal,
|
|
21
|
+
phases,
|
|
22
|
+
captions,
|
|
23
|
+
maps: []
|
|
24
|
+
};
|
|
11
25
|
const done = runSession(session).finally(() => {
|
|
12
26
|
phases.end();
|
|
13
27
|
captions.end();
|
|
@@ -27,14 +41,9 @@ async function runSession(session) {
|
|
|
27
41
|
}
|
|
28
42
|
return openId;
|
|
29
43
|
};
|
|
30
|
-
const stopPlayback = session.out.onPlayback((tag) => {
|
|
31
|
-
if (!active
|
|
32
|
-
active
|
|
33
|
-
entryId: tag.span.entryId,
|
|
34
|
-
contentIndex: tag.span.contentIndex,
|
|
35
|
-
heardUpTo: tag.span.end,
|
|
36
|
-
heardMs: session.out.playedMs
|
|
37
|
-
};
|
|
44
|
+
const stopPlayback = session.out.onPlayback((tag, offsetMs) => {
|
|
45
|
+
if (!active) return;
|
|
46
|
+
notePlayback(session, active, tag, offsetMs);
|
|
38
47
|
});
|
|
39
48
|
const setPhase = phaseSetter(session.phases);
|
|
40
49
|
try {
|
|
@@ -61,9 +70,9 @@ async function runSession(session) {
|
|
|
61
70
|
active = startTurn(session, setPhase, turnId, event, meta);
|
|
62
71
|
}
|
|
63
72
|
} finally {
|
|
64
|
-
stopPlayback();
|
|
65
73
|
if (session.signal.aborted && active) await interrupt(session, active);
|
|
66
74
|
else if (active) await active.done;
|
|
75
|
+
stopPlayback();
|
|
67
76
|
setPhase({ phase: "idle" });
|
|
68
77
|
}
|
|
69
78
|
}
|
|
@@ -110,6 +119,7 @@ function startTurn(session, setPhase, turnId, event, meta) {
|
|
|
110
119
|
unlink: linked.unlink,
|
|
111
120
|
turnId,
|
|
112
121
|
utteranceId: `${turnId}:agent`,
|
|
122
|
+
spokenUpTo: 0,
|
|
113
123
|
done: Promise.resolve()
|
|
114
124
|
};
|
|
115
125
|
active.done = performTurn(session, setPhase, active, event.text, event.at, meta).finally(() => {
|
|
@@ -124,6 +134,7 @@ async function performTurn(session, setPhase, active, text, at, meta) {
|
|
|
124
134
|
const message = { role: "user", content: text, timestamp: at };
|
|
125
135
|
const feed = textFeed(signal);
|
|
126
136
|
const maps = [];
|
|
137
|
+
session.maps = maps;
|
|
127
138
|
const producing = readAgent(session, setPhase, active, message, meta, feed, maps);
|
|
128
139
|
const audio = session.models.speak(
|
|
129
140
|
session.options.mouth,
|
|
@@ -202,13 +213,40 @@ async function pipeSpeech(session, setPhase, active, audio, maps) {
|
|
|
202
213
|
if (event.type !== "audio") continue;
|
|
203
214
|
setPhase({ phase: "speaking" });
|
|
204
215
|
const span = translateSpan(event.span, maps);
|
|
205
|
-
const
|
|
206
|
-
span,
|
|
207
|
-
|
|
216
|
+
const playing = {
|
|
217
|
+
start: event.span.start,
|
|
218
|
+
end: event.span.end,
|
|
219
|
+
durationMs: Math.round(chunkSeconds(event.chunk) * 1e3)
|
|
208
220
|
};
|
|
221
|
+
if (event.alignment) playing.alignment = event.alignment;
|
|
222
|
+
session.playing = playing;
|
|
223
|
+
const tag = { span };
|
|
224
|
+
if (event.alignment) tag.alignment = event.alignment;
|
|
209
225
|
session.out.write(event.chunk, tag);
|
|
210
226
|
}
|
|
211
227
|
}
|
|
228
|
+
function notePlayback(session, active, tag, offsetMs) {
|
|
229
|
+
const playing = session.playing;
|
|
230
|
+
if (playing) {
|
|
231
|
+
const upTo = utteranceIndex(spokenOffset(playing, offsetMs), session.maps);
|
|
232
|
+
if (upTo > active.spokenUpTo) {
|
|
233
|
+
active.spokenUpTo = upTo;
|
|
234
|
+
session.captions.push({
|
|
235
|
+
type: "agent_spoken",
|
|
236
|
+
turnId: active.turnId,
|
|
237
|
+
utteranceId: active.utteranceId,
|
|
238
|
+
upTo
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!("entryId" in tag.span)) return;
|
|
243
|
+
active.cut = {
|
|
244
|
+
entryId: tag.span.entryId,
|
|
245
|
+
contentIndex: tag.span.contentIndex,
|
|
246
|
+
heardUpTo: tag.span.end,
|
|
247
|
+
heardMs: session.out.playedMs
|
|
248
|
+
};
|
|
249
|
+
}
|
|
212
250
|
function translateSpan(span, maps) {
|
|
213
251
|
if ("entryId" in span) return span;
|
|
214
252
|
const map = answerMap(span, maps);
|
package/dist/turns.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/turns.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n AssistantMessageEvent,\n JsonObject,\n ProviderRequestOptions,\n ToolCall,\n UserMessage,\n} from '@earendil-works/pi-ai';\nimport type { AudioSink, AudioSource, AudioTag, TextSpan } from './audio.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel, TranscriptionModel } from './models.ts';\nimport { pushQueue } from './queue.ts';\nimport type { SpeechEvent, SpeechInput } from './speech.ts';\nimport {\n createSpokenState,\n type SpokenPiece,\n spokenPieces,\n type VoiceAgentEvent,\n} from './spoken.ts';\nimport type { ListenOptions, TranscriptionEvent } from './transcription.ts';\n\nexport interface VoiceTurnMeta {\n source: 'voice';\n transcriptionModel: string;\n confidence?: number;\n}\n\nexport interface VoiceCut {\n entryId: string;\n contentIndex: number;\n heardUpTo: number;\n heardMs: number;\n}\n\nexport interface VoiceConversation {\n prompt(\n message: UserMessage,\n meta: VoiceTurnMeta,\n signal: AbortSignal\n ): AsyncIterable<VoiceAgentEvent>;\n truncate(cut: VoiceCut): Promise<void>;\n}\n\nexport type BargeIn = 'interrupt' | 'onWords' | 'ignore' | 'auto';\n\nexport type EchoRecommendation = 'interrupt' | 'onWords' | 'headphones';\n\nexport interface VoiceTurnOptions {\n ears: TranscriptionModel<string>;\n mouth: SpeechModel<string>;\n voice: string;\n speakable?: (event: AssistantMessageEvent) => string | undefined;\n announce?: (call: ToolCall) => string | undefined;\n bargeIn?: BargeIn;\n echoRecommendation?: EchoRecommendation;\n}\n\nexport interface VoiceModels {\n listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n ): AsyncIterable<TranscriptionEvent>;\n speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n ): AsyncIterable<SpeechEvent>;\n}\n\nexport type VoicePhase =\n | { phase: 'listening' }\n | { phase: 'heard'; text: string }\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'retrying'; attempt: number; at: number }\n | { phase: 'compacting' }\n | { phase: 'speaking' }\n | { phase: 'idle' };\n\nexport type CaptionEvent =\n | { type: 'user_partial'; turnId: string; segmentId: string; text: string }\n | { type: 'user_final'; turnId: string; segmentId: string; text: string; confidence?: number }\n | { type: 'agent_generated'; turnId: string; utteranceId: string; text: string }\n | { type: 'agent_spoken'; turnId: string; utteranceId: string; upTo: number }\n | { type: 'agent_cut'; turnId: string; utteranceId: string; heardUpTo: number }\n | { type: 'announce'; turnId: string; text: string }\n | { type: 'thinker_progress'; turnId: string; callId: string; text: string }\n | { type: 'thinker_result'; turnId: string; callId: string; summary: string; isError: boolean };\n\nexport interface VoiceSession {\n phases: AsyncIterable<VoicePhase>;\n captions: AsyncIterable<CaptionEvent>;\n done: Promise<void>;\n}\n\ninterface SpokenMap {\n spokenStart: number;\n spokenEnd: number;\n contentIndex: number;\n start: number;\n end: number;\n entryId?: string;\n kind: SpokenPiece['kind'];\n}\n\ninterface ActiveTurn {\n controller: AbortController;\n unlink: () => void;\n done: Promise<void>;\n turnId: string;\n utteranceId: string;\n cut?: VoiceCut;\n}\n\nexport function voiceTurns(\n models: VoiceModels,\n conversation: VoiceConversation,\n mic: AudioSource,\n out: AudioSink,\n options: VoiceTurnOptions,\n signal: AbortSignal\n): VoiceSession {\n const phases = pushQueue<VoicePhase>();\n const captions = pushQueue<CaptionEvent>();\n const session: Session = { models, conversation, mic, out, options, signal, phases, captions };\n const done = runSession(session).finally(() => {\n phases.end();\n captions.end();\n });\n return { phases: phases.iterable, captions: captions.iterable, done };\n}\n\ninterface Session {\n models: VoiceModels;\n conversation: VoiceConversation;\n mic: AudioSource;\n out: AudioSink;\n options: VoiceTurnOptions;\n signal: AbortSignal;\n phases: ReturnType<typeof pushQueue<VoicePhase>>;\n captions: ReturnType<typeof pushQueue<CaptionEvent>>;\n}\n\nasync function runSession(session: Session): Promise<void> {\n const mode = bargeMode(session.options);\n let active: ActiveTurn | undefined;\n let confidence: number | undefined;\n let reserved = 0;\n let openId = '';\n const openTurn = () => {\n if (!openId) {\n reserved += 1;\n openId = String(reserved);\n }\n return openId;\n };\n const stopPlayback = session.out.onPlayback((tag) => {\n if (!active || !('entryId' in tag.span)) return;\n active.cut = {\n entryId: tag.span.entryId,\n contentIndex: tag.span.contentIndex,\n heardUpTo: tag.span.end,\n heardMs: session.out.playedMs,\n };\n });\n const setPhase = phaseSetter(session.phases);\n try {\n if (session.signal.aborted) return;\n setPhase({ phase: 'listening' });\n for await (const event of session.models.listen(session.options.ears, session.mic, {\n signal: session.signal,\n })) {\n if (event.type === 'error') {\n if (event.reason === 'aborted' || session.signal.aborted) return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n const captionId =\n event.type === 'partial' || event.type === 'final' || event.type === 'turn_end'\n ? openTurn()\n : openId;\n confidence = noteUser(session, event, captionId, confidence);\n if (shouldBarge(mode, event)) active = await interrupt(session, active);\n if (event.type !== 'turn_end') continue;\n const turnId = openId || openTurn();\n openId = '';\n if (mode === 'ignore') active = await finishTurn(active);\n else active = await interrupt(session, active);\n setPhase({ phase: 'heard', text: event.text });\n const meta = turnMeta(session.options, confidence);\n confidence = undefined;\n active = startTurn(session, setPhase, turnId, event, meta);\n }\n } finally {\n stopPlayback();\n if (session.signal.aborted && active) await interrupt(session, active);\n else if (active) await active.done;\n setPhase({ phase: 'idle' });\n }\n}\n\nfunction turnMeta(options: VoiceTurnOptions, confidence: number | undefined): VoiceTurnMeta {\n return {\n source: 'voice',\n transcriptionModel: options.ears.id,\n ...(confidence === undefined ? {} : { confidence }),\n };\n}\n\nfunction noteUser(\n session: Session,\n event: TranscriptionEvent,\n turnId: string,\n confidence: number | undefined\n): number | undefined {\n if (event.type === 'partial') {\n session.captions.push({\n type: 'user_partial',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n });\n }\n if (event.type === 'final') {\n session.captions.push({\n type: 'user_final',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n ...(event.confidence === undefined ? {} : { confidence: event.confidence }),\n });\n return event.confidence ?? confidence;\n }\n if (event.type === 'turn_end') {\n session.captions.push({ type: 'user_final', turnId, segmentId: 'turn', text: event.text });\n }\n return confidence;\n}\n\nasync function finishTurn(active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n await active.done;\n return undefined;\n}\n\nfunction startTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n turnId: string,\n event: Extract<TranscriptionEvent, { type: 'turn_end' }>,\n meta: VoiceTurnMeta\n): ActiveTurn {\n const linked = linkSignal(session.signal);\n const active: ActiveTurn = {\n controller: linked.controller,\n unlink: linked.unlink,\n turnId,\n utteranceId: `${turnId}:agent`,\n done: Promise.resolve(),\n };\n active.done = performTurn(session, setPhase, active, event.text, event.at, meta).finally(() => {\n linked.unlink();\n });\n return active;\n}\n\nasync function performTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n text: string,\n at: number,\n meta: VoiceTurnMeta\n): Promise<void> {\n const signal = active.controller.signal;\n try {\n setPhase({ phase: 'thinking' });\n const message: UserMessage = { role: 'user', content: text, timestamp: at };\n const feed = textFeed(signal);\n const maps: SpokenMap[] = [];\n const producing = readAgent(session, setPhase, active, message, meta, feed, maps);\n const audio = session.models.speak(\n session.options.mouth,\n { text: feed.iterable, voice: session.options.voice },\n { signal }\n );\n await Promise.all([producing, pipeSpeech(session, setPhase, active, audio, maps)]);\n if (!signal.aborted) setPhase({ phase: 'listening' });\n } catch (error) {\n if (signal.aborted || session.signal.aborted) return;\n throw error;\n }\n}\n\nasync function readAgent(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n message: UserMessage,\n meta: VoiceTurnMeta,\n feed: TextFeed,\n maps: SpokenMap[]\n): Promise<void> {\n const state = createSpokenState();\n let spokenChars = 0;\n let generated = '';\n try {\n for await (const event of session.conversation.prompt(\n message,\n meta,\n active.controller.signal\n )) {\n if (active.controller.signal.aborted) break;\n notePhase(setPhase, event);\n for (const piece of spokenPieces(event, state, session.options)) {\n spokenChars += piece.text.length;\n generated = rememberPiece(\n session,\n active,\n piece,\n maps,\n spokenChars,\n generated,\n state.entryId\n );\n feed.push(piece.text);\n }\n if (state.entryId) bindEntry(maps, state.entryId);\n }\n } finally {\n feed.end();\n }\n}\n\nfunction rememberPiece(\n session: Session,\n active: ActiveTurn,\n piece: SpokenPiece,\n maps: SpokenMap[],\n spokenEnd: number,\n generated: string,\n entryId: string | undefined\n): string {\n const bound = piece.entryId ?? entryId;\n maps.push({\n spokenStart: spokenEnd - piece.text.length,\n spokenEnd,\n contentIndex: piece.contentIndex,\n start: piece.start,\n end: piece.end,\n kind: piece.kind,\n ...(bound ? { entryId: bound } : {}),\n });\n if (piece.kind === 'announce') {\n session.captions.push({ type: 'announce', turnId: active.turnId, text: piece.text });\n return generated;\n }\n const text = generated + piece.text;\n session.captions.push({\n type: 'agent_generated',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n text,\n });\n return text;\n}\n\nasync function pipeSpeech(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n audio: AsyncIterable<SpeechEvent>,\n maps: SpokenMap[]\n): Promise<void> {\n for await (const event of audio) {\n if (active.controller.signal.aborted) return;\n if (event.type === 'error') {\n if (event.reason === 'aborted') return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n if (event.type !== 'audio') continue;\n setPhase({ phase: 'speaking' });\n const span = translateSpan(event.span, maps);\n const tag: AudioTag = {\n span,\n ...(event.alignment ? { alignment: event.alignment } : {}),\n };\n session.out.write(event.chunk, tag);\n }\n}\n\nfunction translateSpan(span: TextSpan, maps: SpokenMap[]): TextSpan {\n if ('entryId' in span) return span;\n const map = answerMap(span, maps);\n if (!map?.entryId) return span;\n const spokenLen = map.spokenEnd - map.spokenStart;\n const sourceLen = map.end - map.start;\n const start = map.start + scale(Math.max(0, span.start - map.spokenStart), spokenLen, sourceLen);\n const end =\n map.start + scale(Math.min(spokenLen, span.end - map.spokenStart), spokenLen, sourceLen);\n return { entryId: map.entryId, contentIndex: map.contentIndex, start, end };\n}\n\nfunction answerMap(span: { start: number; end: number }, maps: SpokenMap[]): SpokenMap | undefined {\n for (let index = maps.length - 1; index >= 0; index -= 1) {\n const item = maps[index];\n if (item?.kind !== 'answer' || !item.entryId) continue;\n if (item.spokenStart < span.end && item.spokenEnd > span.start) return item;\n }\n return undefined;\n}\n\nfunction scale(offset: number, spokenLen: number, sourceLen: number): number {\n if (spokenLen <= 0) return 0;\n return Math.round((offset * sourceLen) / spokenLen);\n}\n\nfunction bindEntry(maps: SpokenMap[], entryId: string): void {\n for (const map of maps) {\n if (map.kind === 'answer' && map.entryId === undefined) map.entryId = entryId;\n }\n}\n\nfunction notePhase(setPhase: (phase: VoicePhase) => void, event: VoiceAgentEvent): void {\n if (event.type === 'run_start' || event.type === 'turn_start') setPhase({ phase: 'thinking' });\n if (event.type === 'compaction_start') setPhase({ phase: 'compacting' });\n if (event.type === 'auto_retry_start' && event.attempt !== undefined && event.at !== undefined) {\n setPhase({ phase: 'retrying', attempt: event.attempt, at: event.at });\n }\n if (event.type === 'tool_execution_start' && event.toolName && event.toolCallId) {\n setPhase({\n phase: 'tool',\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n args: event.args ?? {},\n });\n }\n}\n\nasync function interrupt(session: Session, active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n active.controller.abort();\n session.out.flush();\n await active.done;\n if (active.cut) {\n session.captions.push({\n type: 'agent_cut',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n heardUpTo: active.cut.heardUpTo,\n });\n await session.conversation.truncate(active.cut);\n }\n return undefined;\n}\n\nfunction bargeMode(options: VoiceTurnOptions): 'interrupt' | 'onWords' | 'ignore' {\n const mode = options.bargeIn ?? 'interrupt';\n if (mode !== 'auto') return mode;\n if (options.echoRecommendation === 'interrupt') return 'interrupt';\n if (options.echoRecommendation === 'headphones') return 'ignore';\n return 'onWords';\n}\n\nfunction shouldBarge(mode: 'interrupt' | 'onWords' | 'ignore', event: TranscriptionEvent): boolean {\n if (mode === 'ignore') return false;\n if (mode === 'interrupt') return event.type === 'speech_start';\n return event.type === 'partial' && event.text.trim().length > 0;\n}\n\nfunction phaseSetter(phases: Session['phases']): (phase: VoicePhase) => void {\n let previous = '';\n return (phase) => {\n const key = JSON.stringify(phase);\n if (key === previous) return;\n previous = key;\n phases.push(phase);\n };\n}\n\nfunction linkSignal(parent: AbortSignal): { controller: AbortController; unlink: () => void } {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (parent.aborted) controller.abort();\n else parent.addEventListener('abort', abort);\n return {\n controller,\n unlink: () => parent.removeEventListener('abort', abort),\n };\n}\n\ninterface TextFeed {\n push(text: string): void;\n end(): void;\n iterable: AsyncIterable<string>;\n}\n\nfunction textFeed(signal: AbortSignal): TextFeed {\n const queue: string[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n const notify = () => {\n const current = wake;\n wake = undefined;\n current?.();\n };\n const onAbort = () => notify();\n signal.addEventListener('abort', onAbort);\n return {\n push(text) {\n if (ended || signal.aborted || text.length === 0) return;\n queue.push(text);\n notify();\n },\n end() {\n ended = true;\n notify();\n },\n iterable: {\n async *[Symbol.asyncIterator]() {\n try {\n while (!signal.aborted) {\n const next = queue.shift();\n if (next !== undefined) {\n yield next;\n continue;\n }\n if (ended) return;\n await new Promise<void>((resolve) => {\n if (queue.length > 0 || ended || signal.aborted) resolve();\n else wake = resolve;\n });\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n },\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type {\n AssistantMessageEvent,\n JsonObject,\n ProviderRequestOptions,\n ToolCall,\n UserMessage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioSink,\n type AudioSource,\n type AudioTag,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { type SpokenClock, spokenOffset, utteranceIndex } from './captions.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel, TranscriptionModel } from './models.ts';\nimport { pushQueue } from './queue.ts';\nimport type { SpeechEvent, SpeechInput } from './speech.ts';\nimport {\n createSpokenState,\n type SpokenPiece,\n spokenPieces,\n type VoiceAgentEvent,\n} from './spoken.ts';\nimport type { ListenOptions, TranscriptionEvent } from './transcription.ts';\n\nexport interface VoiceTurnMeta {\n source: 'voice';\n transcriptionModel: string;\n confidence?: number;\n}\n\nexport interface VoiceCut {\n entryId: string;\n contentIndex: number;\n heardUpTo: number;\n heardMs: number;\n}\n\nexport interface VoiceConversation {\n prompt(\n message: UserMessage,\n meta: VoiceTurnMeta,\n signal: AbortSignal\n ): AsyncIterable<VoiceAgentEvent>;\n truncate(cut: VoiceCut): Promise<void>;\n}\n\nexport type BargeIn = 'interrupt' | 'onWords' | 'ignore' | 'auto';\n\nexport type EchoRecommendation = 'interrupt' | 'onWords' | 'headphones';\n\nexport interface VoiceTurnOptions {\n ears: TranscriptionModel<string>;\n mouth: SpeechModel<string>;\n voice: string;\n speakable?: (event: AssistantMessageEvent) => string | undefined;\n announce?: (call: ToolCall) => string | undefined;\n bargeIn?: BargeIn;\n echoRecommendation?: EchoRecommendation;\n}\n\nexport interface VoiceModels {\n listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n ): AsyncIterable<TranscriptionEvent>;\n speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n ): AsyncIterable<SpeechEvent>;\n}\n\nexport type VoicePhase =\n | { phase: 'listening' }\n | { phase: 'heard'; text: string }\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'retrying'; attempt: number; at: number }\n | { phase: 'compacting' }\n | { phase: 'speaking' }\n | { phase: 'idle' };\n\nexport type CaptionEvent =\n | { type: 'user_partial'; turnId: string; segmentId: string; text: string }\n | { type: 'user_final'; turnId: string; segmentId: string; text: string; confidence?: number }\n | { type: 'agent_generated'; turnId: string; utteranceId: string; text: string }\n | { type: 'agent_spoken'; turnId: string; utteranceId: string; upTo: number }\n | { type: 'agent_cut'; turnId: string; utteranceId: string; heardUpTo: number }\n | { type: 'announce'; turnId: string; text: string }\n | { type: 'thinker_progress'; turnId: string; callId: string; text: string }\n | { type: 'thinker_result'; turnId: string; callId: string; summary: string; isError: boolean };\n\nexport interface VoiceSession {\n phases: AsyncIterable<VoicePhase>;\n captions: AsyncIterable<CaptionEvent>;\n done: Promise<void>;\n}\n\ninterface SpokenMap {\n spokenStart: number;\n spokenEnd: number;\n contentIndex: number;\n start: number;\n end: number;\n entryId?: string;\n kind: SpokenPiece['kind'];\n}\n\ninterface ActiveTurn {\n controller: AbortController;\n unlink: () => void;\n done: Promise<void>;\n turnId: string;\n utteranceId: string;\n spokenUpTo: number;\n cut?: VoiceCut;\n}\n\nexport function voiceTurns(\n models: VoiceModels,\n conversation: VoiceConversation,\n mic: AudioSource,\n out: AudioSink,\n options: VoiceTurnOptions,\n signal: AbortSignal\n): VoiceSession {\n const phases = pushQueue<VoicePhase>();\n const captions = pushQueue<CaptionEvent>();\n const session: Session = {\n models,\n conversation,\n mic,\n out,\n options,\n signal,\n phases,\n captions,\n maps: [],\n };\n const done = runSession(session).finally(() => {\n phases.end();\n captions.end();\n });\n return { phases: phases.iterable, captions: captions.iterable, done };\n}\n\ninterface Session {\n models: VoiceModels;\n conversation: VoiceConversation;\n mic: AudioSource;\n out: AudioSink;\n options: VoiceTurnOptions;\n signal: AbortSignal;\n phases: ReturnType<typeof pushQueue<VoicePhase>>;\n captions: ReturnType<typeof pushQueue<CaptionEvent>>;\n maps: SpokenMap[];\n playing?: SpokenClock;\n}\n\nasync function runSession(session: Session): Promise<void> {\n const mode = bargeMode(session.options);\n let active: ActiveTurn | undefined;\n let confidence: number | undefined;\n let reserved = 0;\n let openId = '';\n const openTurn = () => {\n if (!openId) {\n reserved += 1;\n openId = String(reserved);\n }\n return openId;\n };\n const stopPlayback = session.out.onPlayback((tag, offsetMs) => {\n if (!active) return;\n notePlayback(session, active, tag, offsetMs);\n });\n const setPhase = phaseSetter(session.phases);\n try {\n if (session.signal.aborted) return;\n setPhase({ phase: 'listening' });\n for await (const event of session.models.listen(session.options.ears, session.mic, {\n signal: session.signal,\n })) {\n if (event.type === 'error') {\n if (event.reason === 'aborted' || session.signal.aborted) return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n const captionId =\n event.type === 'partial' || event.type === 'final' || event.type === 'turn_end'\n ? openTurn()\n : openId;\n confidence = noteUser(session, event, captionId, confidence);\n if (shouldBarge(mode, event)) active = await interrupt(session, active);\n if (event.type !== 'turn_end') continue;\n const turnId = openId || openTurn();\n openId = '';\n if (mode === 'ignore') active = await finishTurn(active);\n else active = await interrupt(session, active);\n setPhase({ phase: 'heard', text: event.text });\n const meta = turnMeta(session.options, confidence);\n confidence = undefined;\n active = startTurn(session, setPhase, turnId, event, meta);\n }\n } finally {\n if (session.signal.aborted && active) await interrupt(session, active);\n else if (active) await active.done;\n stopPlayback();\n setPhase({ phase: 'idle' });\n }\n}\n\nfunction turnMeta(options: VoiceTurnOptions, confidence: number | undefined): VoiceTurnMeta {\n return {\n source: 'voice',\n transcriptionModel: options.ears.id,\n ...(confidence === undefined ? {} : { confidence }),\n };\n}\n\nfunction noteUser(\n session: Session,\n event: TranscriptionEvent,\n turnId: string,\n confidence: number | undefined\n): number | undefined {\n if (event.type === 'partial') {\n session.captions.push({\n type: 'user_partial',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n });\n }\n if (event.type === 'final') {\n session.captions.push({\n type: 'user_final',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n ...(event.confidence === undefined ? {} : { confidence: event.confidence }),\n });\n return event.confidence ?? confidence;\n }\n if (event.type === 'turn_end') {\n session.captions.push({ type: 'user_final', turnId, segmentId: 'turn', text: event.text });\n }\n return confidence;\n}\n\nasync function finishTurn(active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n await active.done;\n return undefined;\n}\n\nfunction startTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n turnId: string,\n event: Extract<TranscriptionEvent, { type: 'turn_end' }>,\n meta: VoiceTurnMeta\n): ActiveTurn {\n const linked = linkSignal(session.signal);\n const active: ActiveTurn = {\n controller: linked.controller,\n unlink: linked.unlink,\n turnId,\n utteranceId: `${turnId}:agent`,\n spokenUpTo: 0,\n done: Promise.resolve(),\n };\n active.done = performTurn(session, setPhase, active, event.text, event.at, meta).finally(() => {\n linked.unlink();\n });\n return active;\n}\n\nasync function performTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n text: string,\n at: number,\n meta: VoiceTurnMeta\n): Promise<void> {\n const signal = active.controller.signal;\n try {\n setPhase({ phase: 'thinking' });\n const message: UserMessage = { role: 'user', content: text, timestamp: at };\n const feed = textFeed(signal);\n const maps: SpokenMap[] = [];\n session.maps = maps;\n const producing = readAgent(session, setPhase, active, message, meta, feed, maps);\n const audio = session.models.speak(\n session.options.mouth,\n { text: feed.iterable, voice: session.options.voice },\n { signal }\n );\n await Promise.all([producing, pipeSpeech(session, setPhase, active, audio, maps)]);\n if (!signal.aborted) setPhase({ phase: 'listening' });\n } catch (error) {\n if (signal.aborted || session.signal.aborted) return;\n throw error;\n }\n}\n\nasync function readAgent(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n message: UserMessage,\n meta: VoiceTurnMeta,\n feed: TextFeed,\n maps: SpokenMap[]\n): Promise<void> {\n const state = createSpokenState();\n let spokenChars = 0;\n let generated = '';\n try {\n for await (const event of session.conversation.prompt(\n message,\n meta,\n active.controller.signal\n )) {\n if (active.controller.signal.aborted) break;\n notePhase(setPhase, event);\n for (const piece of spokenPieces(event, state, session.options)) {\n spokenChars += piece.text.length;\n generated = rememberPiece(\n session,\n active,\n piece,\n maps,\n spokenChars,\n generated,\n state.entryId\n );\n feed.push(piece.text);\n }\n if (state.entryId) bindEntry(maps, state.entryId);\n }\n } finally {\n feed.end();\n }\n}\n\nfunction rememberPiece(\n session: Session,\n active: ActiveTurn,\n piece: SpokenPiece,\n maps: SpokenMap[],\n spokenEnd: number,\n generated: string,\n entryId: string | undefined\n): string {\n const bound = piece.entryId ?? entryId;\n maps.push({\n spokenStart: spokenEnd - piece.text.length,\n spokenEnd,\n contentIndex: piece.contentIndex,\n start: piece.start,\n end: piece.end,\n kind: piece.kind,\n ...(bound ? { entryId: bound } : {}),\n });\n if (piece.kind === 'announce') {\n session.captions.push({ type: 'announce', turnId: active.turnId, text: piece.text });\n return generated;\n }\n const text = generated + piece.text;\n session.captions.push({\n type: 'agent_generated',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n text,\n });\n return text;\n}\n\nasync function pipeSpeech(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n audio: AsyncIterable<SpeechEvent>,\n maps: SpokenMap[]\n): Promise<void> {\n for await (const event of audio) {\n if (active.controller.signal.aborted) return;\n if (event.type === 'error') {\n if (event.reason === 'aborted') return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n if (event.type !== 'audio') continue;\n setPhase({ phase: 'speaking' });\n const span = translateSpan(event.span, maps);\n const playing: SpokenClock = {\n start: event.span.start,\n end: event.span.end,\n durationMs: Math.round(chunkSeconds(event.chunk) * 1000),\n };\n if (event.alignment) playing.alignment = event.alignment;\n session.playing = playing;\n const tag: AudioTag = { span };\n if (event.alignment) tag.alignment = event.alignment;\n session.out.write(event.chunk, tag);\n }\n}\n\nfunction notePlayback(session: Session, active: ActiveTurn, tag: AudioTag, offsetMs: number): void {\n const playing = session.playing;\n if (playing) {\n const upTo = utteranceIndex(spokenOffset(playing, offsetMs), session.maps);\n if (upTo > active.spokenUpTo) {\n active.spokenUpTo = upTo;\n session.captions.push({\n type: 'agent_spoken',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n upTo,\n });\n }\n }\n if (!('entryId' in tag.span)) return;\n active.cut = {\n entryId: tag.span.entryId,\n contentIndex: tag.span.contentIndex,\n heardUpTo: tag.span.end,\n heardMs: session.out.playedMs,\n };\n}\n\nfunction translateSpan(span: TextSpan, maps: SpokenMap[]): TextSpan {\n if ('entryId' in span) return span;\n const map = answerMap(span, maps);\n if (!map?.entryId) return span;\n const spokenLen = map.spokenEnd - map.spokenStart;\n const sourceLen = map.end - map.start;\n const start = map.start + scale(Math.max(0, span.start - map.spokenStart), spokenLen, sourceLen);\n const end =\n map.start + scale(Math.min(spokenLen, span.end - map.spokenStart), spokenLen, sourceLen);\n return { entryId: map.entryId, contentIndex: map.contentIndex, start, end };\n}\n\nfunction answerMap(span: { start: number; end: number }, maps: SpokenMap[]): SpokenMap | undefined {\n for (let index = maps.length - 1; index >= 0; index -= 1) {\n const item = maps[index];\n if (item?.kind !== 'answer' || !item.entryId) continue;\n if (item.spokenStart < span.end && item.spokenEnd > span.start) return item;\n }\n return undefined;\n}\n\nfunction scale(offset: number, spokenLen: number, sourceLen: number): number {\n if (spokenLen <= 0) return 0;\n return Math.round((offset * sourceLen) / spokenLen);\n}\n\nfunction bindEntry(maps: SpokenMap[], entryId: string): void {\n for (const map of maps) {\n if (map.kind === 'answer' && map.entryId === undefined) map.entryId = entryId;\n }\n}\n\nfunction notePhase(setPhase: (phase: VoicePhase) => void, event: VoiceAgentEvent): void {\n if (event.type === 'run_start' || event.type === 'turn_start') setPhase({ phase: 'thinking' });\n if (event.type === 'compaction_start') setPhase({ phase: 'compacting' });\n if (event.type === 'auto_retry_start' && event.attempt !== undefined && event.at !== undefined) {\n setPhase({ phase: 'retrying', attempt: event.attempt, at: event.at });\n }\n if (event.type === 'tool_execution_start' && event.toolName && event.toolCallId) {\n setPhase({\n phase: 'tool',\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n args: event.args ?? {},\n });\n }\n}\n\nasync function interrupt(session: Session, active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n active.controller.abort();\n session.out.flush();\n await active.done;\n if (active.cut) {\n session.captions.push({\n type: 'agent_cut',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n heardUpTo: active.cut.heardUpTo,\n });\n await session.conversation.truncate(active.cut);\n }\n return undefined;\n}\n\nfunction bargeMode(options: VoiceTurnOptions): 'interrupt' | 'onWords' | 'ignore' {\n const mode = options.bargeIn ?? 'interrupt';\n if (mode !== 'auto') return mode;\n if (options.echoRecommendation === 'interrupt') return 'interrupt';\n if (options.echoRecommendation === 'headphones') return 'ignore';\n return 'onWords';\n}\n\nfunction shouldBarge(mode: 'interrupt' | 'onWords' | 'ignore', event: TranscriptionEvent): boolean {\n if (mode === 'ignore') return false;\n if (mode === 'interrupt') return event.type === 'speech_start';\n return event.type === 'partial' && event.text.trim().length > 0;\n}\n\nfunction phaseSetter(phases: Session['phases']): (phase: VoicePhase) => void {\n let previous = '';\n return (phase) => {\n const key = JSON.stringify(phase);\n if (key === previous) return;\n previous = key;\n phases.push(phase);\n };\n}\n\nfunction linkSignal(parent: AbortSignal): { controller: AbortController; unlink: () => void } {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (parent.aborted) controller.abort();\n else parent.addEventListener('abort', abort);\n return {\n controller,\n unlink: () => parent.removeEventListener('abort', abort),\n };\n}\n\ninterface TextFeed {\n push(text: string): void;\n end(): void;\n iterable: AsyncIterable<string>;\n}\n\nfunction textFeed(signal: AbortSignal): TextFeed {\n const queue: string[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n const notify = () => {\n const current = wake;\n wake = undefined;\n current?.();\n };\n const onAbort = () => notify();\n signal.addEventListener('abort', onAbort);\n return {\n push(text) {\n if (ended || signal.aborted || text.length === 0) return;\n queue.push(text);\n notify();\n },\n end() {\n ended = true;\n notify();\n },\n iterable: {\n async *[Symbol.asyncIterator]() {\n try {\n while (!signal.aborted) {\n const next = queue.shift();\n if (next !== undefined) {\n yield next;\n continue;\n }\n if (ended) return;\n await new Promise<void>((resolve) => {\n if (queue.length > 0 || ended || signal.aborted) resolve();\n else wake = resolve;\n });\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n },\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { CharAlignment } from './audio.ts';
|
|
2
|
+
import type { CaptionEvent } from './turns.ts';
|
|
3
|
+
export interface SpokenClock {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
durationMs: number;
|
|
7
|
+
alignment?: CharAlignment;
|
|
8
|
+
}
|
|
9
|
+
export interface UtteranceMap {
|
|
10
|
+
spokenStart: number;
|
|
11
|
+
spokenEnd: number;
|
|
12
|
+
kind: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CaptionHistoryEntry {
|
|
15
|
+
id: string | number;
|
|
16
|
+
kind: string;
|
|
17
|
+
model?: readonly HistoryMessage[];
|
|
18
|
+
data?: unknown;
|
|
19
|
+
}
|
|
20
|
+
interface HistoryMessage {
|
|
21
|
+
role?: string;
|
|
22
|
+
content?: string | readonly HistoryBlock[];
|
|
23
|
+
}
|
|
24
|
+
interface HistoryBlock {
|
|
25
|
+
type?: string;
|
|
26
|
+
text?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function spokenOffset(clock: SpokenClock, offsetMs: number): number;
|
|
29
|
+
export declare function utteranceIndex(feedPos: number, maps: readonly UtteranceMap[]): number;
|
|
30
|
+
export declare function captionsFromHistory(entries: readonly CaptionHistoryEntry[]): CaptionEvent[];
|
|
31
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function registerElevenLabs(): () => void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { registerElevenLabs } from './catalog.ts';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AudioChunk } from '../audio.ts';
|
|
2
|
+
import type { TranscriptionModel } from '../models.ts';
|
|
3
|
+
import { type ListenOptions, TranscriptionEventStream } from '../transcription.ts';
|
|
4
|
+
export declare function listenElevenLabs(model: TranscriptionModel<string>, audio: AsyncIterable<AudioChunk>, options?: ListenOptions): TranscriptionEventStream;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { SpeechModel } from '../models.ts';
|
|
3
|
+
import { SpeechEventStream, type SpeechInput } from '../speech.ts';
|
|
4
|
+
export declare function speakElevenLabs(model: SpeechModel<string>, input: SpeechInput, options?: ProviderRequestOptions): SpeechEventStream;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { TranscriptionModel } from '../models.ts';
|
|
3
|
+
import type { AudioInput, Transcript } from '../transcription.ts';
|
|
4
|
+
export declare function transcribeElevenLabs(model: TranscriptionModel<string>, input: AudioInput, options?: ProviderRequestOptions): Promise<Transcript>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { AudioFormat, CharAlignment } from '../audio.ts';
|
|
3
|
+
export interface ElevenFile {
|
|
4
|
+
data: Uint8Array;
|
|
5
|
+
mimeType: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
}
|
|
8
|
+
interface HeaderModel {
|
|
9
|
+
provider: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare function mediaType(mimeType: string): string;
|
|
13
|
+
export declare function audioFilename(mimeType: string): string;
|
|
14
|
+
export declare function pcmS16(mimeType: string): boolean;
|
|
15
|
+
export declare function encodeForm(file: ElevenFile, fields: Record<string, string>): ArrayBuffer;
|
|
16
|
+
export declare function formType(): string;
|
|
17
|
+
export declare function xiHeaders(model: HeaderModel, options: ProviderRequestOptions | undefined): Record<string, string>;
|
|
18
|
+
export declare function socketUrl(baseUrl: string, path: string): URL;
|
|
19
|
+
export declare function outputFormatName(format: AudioFormat): string;
|
|
20
|
+
export declare function sttAudioFormat(format: AudioFormat): string;
|
|
21
|
+
export declare function connectSocket(url: string, headers: Record<string, string>): WebSocket;
|
|
22
|
+
export declare function awaitOpen(socket: WebSocket, label: string, fail: (message: string) => void): Promise<void>;
|
|
23
|
+
export declare function messageText(data: unknown): Promise<string>;
|
|
24
|
+
export declare function sameFormat(left: AudioFormat, right: AudioFormat): boolean;
|
|
25
|
+
export declare function alignmentFrom(value: unknown): CharAlignment | undefined;
|
|
26
|
+
export {};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type { AudioChunk, AudioEncoding, AudioFormat, AudioSink, AudioSource, AudioTag, CharAlignment, TextSpan, } from './audio.ts';
|
|
2
2
|
export { resolveVoiceAuth } from './auth.ts';
|
|
3
3
|
export { blankUsage } from './blank-usage.ts';
|
|
4
|
+
export type { CaptionHistoryEntry } from './captions.ts';
|
|
5
|
+
export { captionsFromHistory } from './captions.ts';
|
|
4
6
|
export { convertAudio, convertAudioSource, decodeAudioBase64, encodeAudioBase64, } from './convert.ts';
|
|
5
7
|
export { VoiceRequestError } from './errors.ts';
|
|
6
8
|
export type { Delivery, DeliverySupport, KnownRealtimeApi, KnownSpeechApi, KnownTranscriptionApi, RealtimeApi, RealtimeModel, SpeechApi, SpeechModel, TranscriptionApi, TranscriptionModel, VoiceInfo, VoiceModel, VoiceModelType, } from './models.ts';
|
package/dist/types/turns.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AssistantMessageEvent, JsonObject, ProviderRequestOptions, ToolCall, UserMessage } from '@earendil-works/pi-ai';
|
|
2
|
-
import type
|
|
2
|
+
import { type AudioSink, type AudioSource } from './audio.ts';
|
|
3
3
|
import type { SpeechModel, TranscriptionModel } from './models.ts';
|
|
4
4
|
import type { SpeechEvent, SpeechInput } from './speech.ts';
|
|
5
5
|
import { type VoiceAgentEvent } from './spoken.ts';
|