@ai-ecoverse/pi-audio 1.11.0 → 1.13.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 CHANGED
@@ -23,6 +23,8 @@ pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatch
23
23
 
24
24
  `@ai-ecoverse/pi-audio/durable` appends a `voice.truncate` entry for a `VoiceCut`. The entry has no model messages. Its `edits` replace the target assistant message with the text heard so far and keep tool calls. The stored entry stays intact, and pi-durable derives the shorter message for the next model request. `@earendil-works/pi-durable` is an optional peer (`^1.1.0`); the root package does not import it. `VoiceCut.entryId` is the decimal string of pi-durable's numeric entry id.
25
25
 
26
+ `@ai-ecoverse/pi-audio/elevenlabs` registers ElevenLabs speech adapters when `registerElevenLabs()` is called. `scribe_v2` posts multipart audio to `/speech-to-text`. `scribe_v2_realtime` streams base64 audio on `wss://…/speech-to-text/realtime` and commits turns with `turn_prediction` (`turnDetection: 'semantic'`). `eleven_flash_v2_5` and `eleven_multilingual_v2` post JSON to `/text-to-speech/{voice}/with-timestamps` for a string and stream text on `stream-input` for an async iterable, with character alignment. `eleven_v3` and `eleven_v4` stay on the HTTP path (`textStreaming: false`). Scribe prices are $0.22 per hour for batch and $0.39 per hour for realtime.
27
+
26
28
  `@ai-ecoverse/pi-audio/xai` registers xAI speech adapters when `registerXAI()` is called. `grok-voice-transcribe-2.0` posts multipart audio to `/stt` (fields before `file`) and streams raw binary frames on `wss://…/stt` after `transcript.created`, with Smart Turn (`turnDetection: 'semantic'`). The catalog id is shared, so `audioCost` uses the streaming rate of $0.20 per hour. `grok-voice-tts-1.0` posts JSON to `/tts` for a string and streams `text.delta` / `audio.delta` on `wss://…/tts` for an async iterable (`textStreaming: true`). Voices are eve, ara, rex, sal, and leo. Codecs are mp3, pcm, wav, mulaw, and alaw.
27
29
 
28
30
  `@ai-ecoverse/pi-audio/openai` registers OpenAI and OpenRouter speech adapters when `registerOpenAI()` is called. Batch transcription posts multipart audio to `/audio/transcriptions`. OpenRouter (`provider` `openrouter`, or a base URL on `openrouter.ai`) posts JSON `input_audio` instead. Speech posts JSON to `/audio/speech` and reads the raw audio response. OpenAI can return pcm, mp3, wav, opus, or flac. OpenRouter returns pcm or mp3. Realtime transcription uses a WebSocket at `/realtime?intent=transcription` for `gpt-realtime-whisper` and `gpt-live-transcribe`. The catalog key is type, provider, and id, so the batch model keeps the id `gpt-4o-transcribe`.
@@ -39,6 +41,8 @@ pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatch
39
41
 
40
42
  `@ai-ecoverse/pi-audio/gemini-live` registers `gemini-3.8-live`, `gemini-live-2.5-flash-native-audio`, and `gemini-3.1-flash-live-preview`. Setup enables sliding-window compression and session resumption. `gemini-3.8-live` leaves function behavior at the model default and accepts `vocabulary` as `customVocabulary`. The 2.5 model declares `NON_BLOCKING`. The 3.1 preview declares `BLOCKING`. `respond` maps `Delivery` onto `scheduling` (`INTERRUPT`, `WHEN_IDLE`, `SILENT`) and returns `status`, `retryable`, and `message`. A `goAway` reconnects with the latest resumable handle. There is no truncate call. The adapter takes a recorded `wire` and never calls Google.
41
43
 
44
+ `mintRealtimeToken(model, constraints, options)` returns `{ value, expiresAt, model }` and calls `options.fetch` (or `globalThis.fetch`). OpenAI `POST /v1/realtime/client_secrets` sends a realtime session with instructions, voice, and tools. xAI `POST /v1/realtime/client_secrets` sends only `expires_after.seconds` of 300. Gemini `POST /v1beta/auth_tokens` locks `models/<id>`, instructions, voice, and tools in `liveConnectConstraints` and returns `name`, which the live adapter sends as `access_token`. ElevenLabs `GET /v1/convai/conversation/get-signed-url` takes `agent_id` (or `GET /conversation/token` when the only transport is WebRTC) and expires in 15 minutes. Fish `POST /v1/agent/sessions` sends `agent_id` plus `overrides.system_prompt` and `overrides.voice_id`. `value` is the LiveKit participant token. `expiresAt` is milliseconds. A numeric `expires_at` below `1e12` is unix seconds.
45
+
42
46
  `talkerTools` is `ask_agent` and `read`. pi-audio defines `askAgent` (a pi `Tool` whose parameters are `request` and optional `context`) and the `Thinker` interface. The host implements `Thinker` and supplies `read`. `bindThinker` runs direct tools with `directTimeoutMs` and truncates their text to `directMaxChars`. On a blocking `ask_agent` it answers with `ack` immediately and injects the result later. On an async call it `respond`s with the result and `options.result`. Progress is injected with `options.progress`. `tool_call_cancelled` aborts the thinker. The binding also yields `thinker_progress` / `thinker_result` captions and `thinking` / `tool` / `idle` phases.
43
47
 
44
48
  ## Development
@@ -0,0 +1,136 @@
1
+ import {
2
+ registerVoiceModel
3
+ } from "../models.js";
4
+ import { registerSpeechApiProvider, registerTranscriptionApiProvider } from "../registry.js";
5
+ import { listenElevenLabs } from "./listen.js";
6
+ import { speakElevenLabs } from "./speak.js";
7
+ import { transcribeElevenLabs } from "./transcribe.js";
8
+ const tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
9
+ const eleven = "https://api.elevenlabs.io/v1";
10
+ const pcm16 = (sampleRate) => ({
11
+ encoding: "pcm16",
12
+ sampleRate,
13
+ channels: 1
14
+ });
15
+ const law = (encoding) => ({
16
+ encoding,
17
+ sampleRate: 8e3,
18
+ channels: 1
19
+ });
20
+ const container = (encoding, sampleRate) => ({
21
+ encoding,
22
+ sampleRate,
23
+ channels: 1
24
+ });
25
+ const voices = [
26
+ { id: "21m00Tcm4TlvDq8ikWAM", name: "Rachel" },
27
+ { id: "pNInz6obpgDQGcFmaJgB", name: "Adam" },
28
+ { id: "EXAVITQu4vr4xnSDxMaL", name: "Bella" },
29
+ { id: "ErXwobaYiN019PkySvjV", name: "Antoni" },
30
+ { id: "TxGEqnHWrfWFTfGW9XjX", name: "Josh" }
31
+ ];
32
+ const speechFormats = [
33
+ container("mp3", 44100),
34
+ container("mp3", 24e3),
35
+ container("mp3", 22050),
36
+ pcm16(16e3),
37
+ pcm16(24e3),
38
+ pcm16(8e3),
39
+ container("wav", 16e3),
40
+ law("mulaw"),
41
+ law("alaw"),
42
+ container("opus", 48e3)
43
+ ];
44
+ function speech(id, textStreaming, perThousandChars) {
45
+ return {
46
+ id,
47
+ name: id,
48
+ api: "elevenlabs-tts",
49
+ provider: "elevenlabs",
50
+ baseUrl: eleven,
51
+ type: "speech",
52
+ textStreaming,
53
+ outputFormats: speechFormats,
54
+ voices,
55
+ cloning: true,
56
+ cost: tokenCost,
57
+ audioCost: { perThousandChars }
58
+ };
59
+ }
60
+ function elevenCatalog() {
61
+ const batch = {
62
+ id: "scribe_v2",
63
+ name: "scribe_v2",
64
+ api: "elevenlabs-scribe",
65
+ provider: "elevenlabs",
66
+ baseUrl: eleven,
67
+ type: "transcription",
68
+ streaming: false,
69
+ turnDetection: "none",
70
+ inputFormats: [
71
+ container("wav", 16e3),
72
+ container("mp3", 16e3),
73
+ container("flac", 16e3),
74
+ pcm16(16e3),
75
+ law("mulaw")
76
+ ],
77
+ diarization: true,
78
+ cost: tokenCost,
79
+ audioCost: { perInputMinute: 0.22 / 60 }
80
+ };
81
+ const realtime = {
82
+ id: "scribe_v2_realtime",
83
+ name: "scribe_v2_realtime",
84
+ api: "elevenlabs-scribe",
85
+ provider: "elevenlabs",
86
+ baseUrl: eleven,
87
+ type: "transcription",
88
+ streaming: true,
89
+ turnDetection: "semantic",
90
+ inputFormats: [
91
+ pcm16(8e3),
92
+ pcm16(16e3),
93
+ pcm16(22050),
94
+ pcm16(24e3),
95
+ pcm16(44100),
96
+ pcm16(48e3),
97
+ law("mulaw")
98
+ ],
99
+ diarization: true,
100
+ cost: tokenCost,
101
+ audioCost: { perInputMinute: 0.39 / 60 }
102
+ };
103
+ return [
104
+ batch,
105
+ realtime,
106
+ speech("eleven_flash_v2_5", true, 0.05),
107
+ speech("eleven_multilingual_v2", true, 0.1),
108
+ speech("eleven_v3", false, 0.1),
109
+ speech("eleven_v4", false, 0.1)
110
+ ];
111
+ }
112
+ function registerElevenLabs() {
113
+ const models = elevenCatalog();
114
+ const unregisterModels = models.map((model) => registerVoiceModel(model));
115
+ const unregisterScribe = registerTranscriptionApiProvider({
116
+ api: "elevenlabs-scribe",
117
+ transcribe: transcribeElevenLabs,
118
+ listen: listenElevenLabs
119
+ });
120
+ const unregisterSpeech = registerSpeechApiProvider({
121
+ api: "elevenlabs-tts",
122
+ speak: speakElevenLabs
123
+ });
124
+ let released = false;
125
+ return () => {
126
+ if (released) return;
127
+ released = true;
128
+ for (const unregister of unregisterModels) unregister();
129
+ unregisterScribe();
130
+ unregisterSpeech();
131
+ };
132
+ }
133
+ export {
134
+ registerElevenLabs
135
+ };
136
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/elevenlabs/catalog.ts"],
4
+ "sourcesContent": ["import type { AudioFormat } from '../audio.ts';\nimport {\n registerVoiceModel,\n type SpeechModel,\n type TranscriptionModel,\n type VoiceInfo,\n} from '../models.ts';\nimport { registerSpeechApiProvider, registerTranscriptionApiProvider } from '../registry.ts';\nimport { listenElevenLabs } from './listen.ts';\nimport { speakElevenLabs } from './speak.ts';\nimport { transcribeElevenLabs } from './transcribe.ts';\n\nconst tokenCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };\nconst eleven = 'https://api.elevenlabs.io/v1';\n\nconst pcm16 = (sampleRate: number): AudioFormat => ({\n encoding: 'pcm16',\n sampleRate,\n channels: 1,\n});\n\nconst law = (encoding: 'mulaw' | 'alaw'): AudioFormat => ({\n encoding,\n sampleRate: 8000,\n channels: 1,\n});\n\nconst container = (encoding: 'wav' | 'mp3' | 'flac' | 'opus', sampleRate: number): AudioFormat => ({\n encoding,\n sampleRate,\n channels: 1,\n});\n\nconst voices: VoiceInfo[] = [\n { id: '21m00Tcm4TlvDq8ikWAM', name: 'Rachel' },\n { id: 'pNInz6obpgDQGcFmaJgB', name: 'Adam' },\n { id: 'EXAVITQu4vr4xnSDxMaL', name: 'Bella' },\n { id: 'ErXwobaYiN019PkySvjV', name: 'Antoni' },\n { id: 'TxGEqnHWrfWFTfGW9XjX', name: 'Josh' },\n];\n\nconst speechFormats: AudioFormat[] = [\n container('mp3', 44100),\n container('mp3', 24000),\n container('mp3', 22050),\n pcm16(16000),\n pcm16(24000),\n pcm16(8000),\n container('wav', 16000),\n law('mulaw'),\n law('alaw'),\n container('opus', 48000),\n];\n\nfunction speech(id: string, textStreaming: boolean, perThousandChars: number): SpeechModel<string> {\n return {\n id,\n name: id,\n api: 'elevenlabs-tts',\n provider: 'elevenlabs',\n baseUrl: eleven,\n type: 'speech',\n textStreaming,\n outputFormats: speechFormats,\n voices,\n cloning: true,\n cost: tokenCost,\n audioCost: { perThousandChars },\n };\n}\n\nfunction elevenCatalog(): Array<TranscriptionModel<string> | SpeechModel<string>> {\n const batch: TranscriptionModel<string> = {\n id: 'scribe_v2',\n name: 'scribe_v2',\n api: 'elevenlabs-scribe',\n provider: 'elevenlabs',\n baseUrl: eleven,\n type: 'transcription',\n streaming: false,\n turnDetection: 'none',\n inputFormats: [\n container('wav', 16000),\n container('mp3', 16000),\n container('flac', 16000),\n pcm16(16000),\n law('mulaw'),\n ],\n diarization: true,\n cost: tokenCost,\n audioCost: { perInputMinute: 0.22 / 60 },\n };\n const realtime: TranscriptionModel<string> = {\n id: 'scribe_v2_realtime',\n name: 'scribe_v2_realtime',\n api: 'elevenlabs-scribe',\n provider: 'elevenlabs',\n baseUrl: eleven,\n type: 'transcription',\n streaming: true,\n turnDetection: 'semantic',\n inputFormats: [\n pcm16(8000),\n pcm16(16000),\n pcm16(22050),\n pcm16(24000),\n pcm16(44100),\n pcm16(48000),\n law('mulaw'),\n ],\n diarization: true,\n cost: tokenCost,\n audioCost: { perInputMinute: 0.39 / 60 },\n };\n return [\n batch,\n realtime,\n speech('eleven_flash_v2_5', true, 0.05),\n speech('eleven_multilingual_v2', true, 0.1),\n speech('eleven_v3', false, 0.1),\n speech('eleven_v4', false, 0.1),\n ];\n}\n\nexport function registerElevenLabs(): () => void {\n const models = elevenCatalog();\n const unregisterModels = models.map((model) => registerVoiceModel(model));\n const unregisterScribe = registerTranscriptionApiProvider({\n api: 'elevenlabs-scribe',\n transcribe: transcribeElevenLabs,\n listen: listenElevenLabs,\n });\n const unregisterSpeech = registerSpeechApiProvider({\n api: 'elevenlabs-tts',\n speak: speakElevenLabs,\n });\n let released = false;\n return () => {\n if (released) return;\n released = true;\n for (const unregister of unregisterModels) unregister();\n unregisterScribe();\n unregisterSpeech();\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,5 @@
1
+ import { registerElevenLabs } from "./catalog.js";
2
+ export {
3
+ registerElevenLabs
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/elevenlabs/index.ts"],
4
+ "sourcesContent": ["export { registerElevenLabs } from './catalog.ts';\n"],
5
+ "mappings": "AAAA,SAAS,0BAA0B;",
6
+ "names": []
7
+ }
@@ -0,0 +1,268 @@
1
+ import { chunkSeconds } from "../audio.js";
2
+ import { encodeAudioBase64 } from "../convert.js";
3
+ import { asVoiceError, isRecord, numberField, stringField } from "../openai/wire.js";
4
+ import {
5
+ TranscriptionEventStream
6
+ } from "../transcription.js";
7
+ import { priceAudio } from "../usage.js";
8
+ import {
9
+ awaitOpen,
10
+ connectSocket,
11
+ messageText,
12
+ sameFormat,
13
+ socketUrl,
14
+ sttAudioFormat,
15
+ xiHeaders
16
+ } from "./wire.js";
17
+ const failures = /* @__PURE__ */ new Set([
18
+ "error",
19
+ "auth_error",
20
+ "quota_exceeded",
21
+ "commit_throttled",
22
+ "unaccepted_terms",
23
+ "rate_limited",
24
+ "queue_overflow",
25
+ "resource_exhausted",
26
+ "session_time_limit_exceeded",
27
+ "input_error",
28
+ "invalid_request",
29
+ "chunk_size_exceeded",
30
+ "insufficient_audio_activity",
31
+ "transcriber_error"
32
+ ]);
33
+ function terms(options) {
34
+ const vocabulary = options?.vocabulary;
35
+ if (!vocabulary) return [];
36
+ return vocabulary;
37
+ }
38
+ function commitStrategy(model) {
39
+ if (model.turnDetection === "semantic") return "turn_prediction";
40
+ if (model.turnDetection === "vad") return "vad";
41
+ return "manual";
42
+ }
43
+ function sttUrl(model, format, options) {
44
+ const url = socketUrl(model.baseUrl, "speech-to-text/realtime");
45
+ const params = new URLSearchParams();
46
+ params.set("model_id", model.id);
47
+ params.set("audio_format", sttAudioFormat(format));
48
+ params.set("commit_strategy", commitStrategy(model));
49
+ params.set("include_timestamps", "true");
50
+ if (options?.language) params.set("language_code", options.language);
51
+ for (const term of terms(options)) params.append("keyterms", term);
52
+ url.search = params.toString();
53
+ return url.toString();
54
+ }
55
+ function chosenFormat(model, chunk) {
56
+ const format = chunk === void 0 ? model.inputFormats[0] : chunk.format;
57
+ if (!format) throw new Error("No input format");
58
+ sttAudioFormat(format);
59
+ const known = model.inputFormats.some((item) => sameFormat(item, format));
60
+ if (!known) throw new Error(`Unsupported audio format ${format.encoding}`);
61
+ return format;
62
+ }
63
+ function textOr(event) {
64
+ const text = stringField(event, "text");
65
+ if (text === void 0) return "";
66
+ return text;
67
+ }
68
+ function secondsOf(word, key) {
69
+ const value = numberField(word, key);
70
+ if (value === void 0) return 0;
71
+ return value;
72
+ }
73
+ function wordFrom(word) {
74
+ if (stringField(word, "type") === "spacing") return void 0;
75
+ const speaker = stringField(word, "speaker_id");
76
+ const segment = {
77
+ text: textOr(word),
78
+ start: secondsOf(word, "start"),
79
+ end: secondsOf(word, "end")
80
+ };
81
+ if (speaker !== void 0) segment.speaker = speaker;
82
+ return segment;
83
+ }
84
+ function wordsOf(event, state) {
85
+ if (!Array.isArray(event.words)) return;
86
+ for (const word of event.words) {
87
+ if (!isRecord(word)) continue;
88
+ const segment = wordFrom(word);
89
+ if (segment) state.segments.push(segment);
90
+ }
91
+ }
92
+ function emitCommit(spoken, state, push) {
93
+ if (!state.started) {
94
+ state.started = true;
95
+ push({ type: "speech_start", at: 0 });
96
+ }
97
+ const segmentId = String(state.segment);
98
+ state.segment += 1;
99
+ state.turns.push(spoken);
100
+ push({ type: "final", segmentId, text: spoken });
101
+ state.started = false;
102
+ push({ type: "turn_end", text: spoken, at: 0 });
103
+ }
104
+ function applyListen(event, state, push) {
105
+ const type = stringField(event, "message_type");
106
+ if (type === "session_started") {
107
+ state.ready = true;
108
+ return;
109
+ }
110
+ if (type === "partial_transcript") {
111
+ if (!state.started) {
112
+ state.started = true;
113
+ push({ type: "speech_start", at: 0 });
114
+ }
115
+ push({ type: "partial", segmentId: String(state.segment), text: textOr(event) });
116
+ return;
117
+ }
118
+ if (type === "committed_transcript") {
119
+ const spoken = textOr(event);
120
+ state.lastCommit = spoken;
121
+ emitCommit(spoken, state, push);
122
+ return;
123
+ }
124
+ if (type === "committed_transcript_with_timestamps") {
125
+ const spoken = textOr(event);
126
+ wordsOf(event, state);
127
+ if (state.lastCommit !== spoken) emitCommit(spoken, state, push);
128
+ state.lastCommit = void 0;
129
+ return;
130
+ }
131
+ if (type !== void 0 && failures.has(type)) {
132
+ const message = stringField(event, "error");
133
+ state.failed = true;
134
+ push({
135
+ type: "error",
136
+ reason: "error",
137
+ error: message === void 0 ? "ElevenLabs transcription failed" : message
138
+ });
139
+ }
140
+ }
141
+ function finish(model, format, state, push) {
142
+ if (state.failed || state.completed) return;
143
+ state.completed = true;
144
+ const heard = chunkSeconds({ data: new Uint8Array(state.bytes), format, timestamp: 0 });
145
+ push({
146
+ type: "done",
147
+ transcript: {
148
+ text: state.turns.join(" "),
149
+ ...state.segments.length > 0 ? { segments: state.segments } : {},
150
+ usage: priceAudio(model.audioCost, { inputSeconds: heard })
151
+ }
152
+ });
153
+ }
154
+ function listenElevenLabs(model, audio, options) {
155
+ const stream = new TranscriptionEventStream();
156
+ void pumpListen(model, audio, options, stream);
157
+ return stream;
158
+ }
159
+ async function pumpListen(model, audio, options, stream) {
160
+ const state = {
161
+ turns: [],
162
+ segments: [],
163
+ segment: 0,
164
+ started: false,
165
+ bytes: 0,
166
+ opened: false,
167
+ ready: false,
168
+ clientDone: false,
169
+ failed: false,
170
+ completed: false,
171
+ lastCommit: void 0
172
+ };
173
+ let settled = false;
174
+ const push = (event) => {
175
+ if (settled) return;
176
+ if (event.type === "done" || event.type === "error") settled = true;
177
+ stream.push(event);
178
+ };
179
+ let socket;
180
+ const readyWait = Promise.withResolvers();
181
+ const doneWait = Promise.withResolvers();
182
+ const wake = (kind) => {
183
+ if (state.ready || state.failed) readyWait.resolve();
184
+ if (state.failed) doneWait.resolve();
185
+ if (!state.clientDone) return;
186
+ if (kind === "committed_transcript" || kind === "committed_transcript_with_timestamps") {
187
+ doneWait.resolve();
188
+ }
189
+ if (kind === "close") doneWait.resolve();
190
+ };
191
+ try {
192
+ if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
193
+ const iterator = audio[Symbol.asyncIterator]();
194
+ const first = await iterator.next();
195
+ const format = chosenFormat(model, first.done ? void 0 : first.value);
196
+ socket = connectSocket(sttUrl(model, format, options), xiHeaders(model, options));
197
+ let incoming = Promise.resolve();
198
+ socket.addEventListener("message", (event) => {
199
+ incoming = incoming.then(() => onMessage(event.data, state, push, wake));
200
+ });
201
+ socket.addEventListener("close", () => {
202
+ if (state.opened && !state.failed && !state.completed && !state.clientDone) {
203
+ state.failed = true;
204
+ push({ type: "error", reason: "error", error: "ElevenLabs transcription socket closed" });
205
+ }
206
+ wake("close");
207
+ });
208
+ await awaitOpen(socket, "ElevenLabs transcription", (message) => {
209
+ state.failed = true;
210
+ push({ type: "error", reason: "error", error: message });
211
+ wake("fail");
212
+ });
213
+ if (state.failed) return;
214
+ state.opened = true;
215
+ if (!state.ready) await readyWait.promise;
216
+ if (state.failed) return;
217
+ if (!first.done) sendChunk(socket, first.value, format, state, false);
218
+ while (!state.failed) {
219
+ if (options?.signal?.aborted) throw new DOMException("aborted", "AbortError");
220
+ const next = await iterator.next();
221
+ if (next.done) break;
222
+ sendChunk(socket, next.value, format, state, false);
223
+ }
224
+ if (state.failed) return;
225
+ sendChunk(socket, void 0, format, state, true);
226
+ state.clientDone = true;
227
+ await doneWait.promise;
228
+ finish(model, format, state, push);
229
+ } catch (error) {
230
+ const voice = asVoiceError(error);
231
+ state.failed = true;
232
+ push({ type: "error", reason: voice.reason, error: voice.error });
233
+ } finally {
234
+ socket?.close();
235
+ }
236
+ }
237
+ function sendChunk(socket, chunk, format, state, commit) {
238
+ if (chunk && !sameFormat(chunk.format, format)) {
239
+ throw new Error(`Unsupported audio format ${chunk.format.encoding}`);
240
+ }
241
+ const data = chunk ? chunk.data : new Uint8Array();
242
+ state.bytes += data.byteLength;
243
+ socket.send(
244
+ JSON.stringify({
245
+ message_type: "input_audio_chunk",
246
+ audio_base_64: encodeAudioBase64(data),
247
+ commit,
248
+ sample_rate: format.sampleRate
249
+ })
250
+ );
251
+ }
252
+ async function onMessage(data, state, push, wake) {
253
+ try {
254
+ const parsed = JSON.parse(await messageText(data));
255
+ if (!isRecord(parsed)) throw new Error("ElevenLabs transcription event was not JSON");
256
+ applyListen(parsed, state, push);
257
+ wake(stringField(parsed, "message_type"));
258
+ } catch (error) {
259
+ const voice = asVoiceError(error);
260
+ state.failed = true;
261
+ push({ type: "error", reason: voice.reason, error: voice.error });
262
+ wake("fail");
263
+ }
264
+ }
265
+ export {
266
+ listenElevenLabs
267
+ };
268
+ //# sourceMappingURL=listen.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/elevenlabs/listen.ts"],
4
+ "sourcesContent": ["import type { AudioChunk, AudioFormat } from '../audio.ts';\nimport { chunkSeconds } from '../audio.ts';\nimport { encodeAudioBase64 } from '../convert.ts';\nimport type { TranscriptionModel } from '../models.ts';\nimport { asVoiceError, isRecord, numberField, stringField } from '../openai/wire.ts';\nimport {\n type ListenOptions,\n type TranscriptionEvent,\n TranscriptionEventStream,\n type TranscriptSegment,\n} from '../transcription.ts';\nimport { priceAudio } from '../usage.ts';\nimport {\n awaitOpen,\n connectSocket,\n messageText,\n sameFormat,\n socketUrl,\n sttAudioFormat,\n xiHeaders,\n} from './wire.ts';\n\nconst failures = new Set([\n 'error',\n 'auth_error',\n 'quota_exceeded',\n 'commit_throttled',\n 'unaccepted_terms',\n 'rate_limited',\n 'queue_overflow',\n 'resource_exhausted',\n 'session_time_limit_exceeded',\n 'input_error',\n 'invalid_request',\n 'chunk_size_exceeded',\n 'insufficient_audio_activity',\n 'transcriber_error',\n]);\n\ninterface ListenState {\n turns: string[];\n segments: TranscriptSegment[];\n segment: number;\n started: boolean;\n bytes: number;\n opened: boolean;\n ready: boolean;\n clientDone: boolean;\n failed: boolean;\n completed: boolean;\n lastCommit: string | undefined;\n}\n\nfunction terms(options: ListenOptions | undefined): string[] {\n const vocabulary = options?.vocabulary;\n if (!vocabulary) return [];\n return vocabulary;\n}\n\nfunction commitStrategy(model: TranscriptionModel<string>): string {\n if (model.turnDetection === 'semantic') return 'turn_prediction';\n if (model.turnDetection === 'vad') return 'vad';\n return 'manual';\n}\n\nfunction sttUrl(\n model: TranscriptionModel<string>,\n format: AudioFormat,\n options: ListenOptions | undefined\n): string {\n const url = socketUrl(model.baseUrl, 'speech-to-text/realtime');\n const params = new URLSearchParams();\n params.set('model_id', model.id);\n params.set('audio_format', sttAudioFormat(format));\n params.set('commit_strategy', commitStrategy(model));\n params.set('include_timestamps', 'true');\n if (options?.language) params.set('language_code', options.language);\n for (const term of terms(options)) params.append('keyterms', term);\n url.search = params.toString();\n return url.toString();\n}\n\nfunction chosenFormat(\n model: TranscriptionModel<string>,\n chunk: AudioChunk | undefined\n): AudioFormat {\n const format = chunk === undefined ? model.inputFormats[0] : chunk.format;\n if (!format) throw new Error('No input format');\n sttAudioFormat(format);\n const known = model.inputFormats.some((item) => sameFormat(item, format));\n if (!known) throw new Error(`Unsupported audio format ${format.encoding}`);\n return format;\n}\n\nfunction textOr(event: Record<string, unknown>): string {\n const text = stringField(event, 'text');\n if (text === undefined) return '';\n return text;\n}\n\nfunction secondsOf(word: Record<string, unknown>, key: string): number {\n const value = numberField(word, key);\n if (value === undefined) return 0;\n return value;\n}\n\nfunction wordFrom(word: Record<string, unknown>): TranscriptSegment | undefined {\n if (stringField(word, 'type') === 'spacing') return undefined;\n const speaker = stringField(word, 'speaker_id');\n const segment: TranscriptSegment = {\n text: textOr(word),\n start: secondsOf(word, 'start'),\n end: secondsOf(word, 'end'),\n };\n if (speaker !== undefined) segment.speaker = speaker;\n return segment;\n}\n\nfunction wordsOf(event: Record<string, unknown>, state: ListenState) {\n if (!Array.isArray(event.words)) return;\n for (const word of event.words) {\n if (!isRecord(word)) continue;\n const segment = wordFrom(word);\n if (segment) state.segments.push(segment);\n }\n}\n\nfunction emitCommit(spoken: string, state: ListenState, push: (event: TranscriptionEvent) => void) {\n if (!state.started) {\n state.started = true;\n push({ type: 'speech_start', at: 0 });\n }\n const segmentId = String(state.segment);\n state.segment += 1;\n state.turns.push(spoken);\n push({ type: 'final', segmentId, text: spoken });\n state.started = false;\n push({ type: 'turn_end', text: spoken, at: 0 });\n}\n\nfunction applyListen(\n event: Record<string, unknown>,\n state: ListenState,\n push: (event: TranscriptionEvent) => void\n) {\n const type = stringField(event, 'message_type');\n if (type === 'session_started') {\n state.ready = true;\n return;\n }\n if (type === 'partial_transcript') {\n if (!state.started) {\n state.started = true;\n push({ type: 'speech_start', at: 0 });\n }\n push({ type: 'partial', segmentId: String(state.segment), text: textOr(event) });\n return;\n }\n if (type === 'committed_transcript') {\n const spoken = textOr(event);\n state.lastCommit = spoken;\n emitCommit(spoken, state, push);\n return;\n }\n if (type === 'committed_transcript_with_timestamps') {\n const spoken = textOr(event);\n wordsOf(event, state);\n if (state.lastCommit !== spoken) emitCommit(spoken, state, push);\n state.lastCommit = undefined;\n return;\n }\n if (type !== undefined && failures.has(type)) {\n const message = stringField(event, 'error');\n state.failed = true;\n push({\n type: 'error',\n reason: 'error',\n error: message === undefined ? 'ElevenLabs transcription failed' : message,\n });\n }\n}\n\nfunction finish(\n model: TranscriptionModel<string>,\n format: AudioFormat,\n state: ListenState,\n push: (event: TranscriptionEvent) => void\n) {\n if (state.failed || state.completed) return;\n state.completed = true;\n const heard = chunkSeconds({ data: new Uint8Array(state.bytes), format, timestamp: 0 });\n push({\n type: 'done',\n transcript: {\n text: state.turns.join(' '),\n ...(state.segments.length > 0 ? { segments: state.segments } : {}),\n usage: priceAudio(model.audioCost, { inputSeconds: heard }),\n },\n });\n}\n\nexport function listenElevenLabs(\n model: TranscriptionModel<string>,\n audio: AsyncIterable<AudioChunk>,\n options?: ListenOptions\n): TranscriptionEventStream {\n const stream = new TranscriptionEventStream();\n void pumpListen(model, audio, options, stream);\n return stream;\n}\n\nasync function pumpListen(\n model: TranscriptionModel<string>,\n audio: AsyncIterable<AudioChunk>,\n options: ListenOptions | undefined,\n stream: TranscriptionEventStream\n) {\n const state: ListenState = {\n turns: [],\n segments: [],\n segment: 0,\n started: false,\n bytes: 0,\n opened: false,\n ready: false,\n clientDone: false,\n failed: false,\n completed: false,\n lastCommit: undefined,\n };\n let settled = false;\n const push = (event: TranscriptionEvent) => {\n if (settled) return;\n if (event.type === 'done' || event.type === 'error') settled = true;\n stream.push(event);\n };\n let socket: WebSocket | undefined;\n const readyWait = Promise.withResolvers<void>();\n const doneWait = Promise.withResolvers<void>();\n const wake = (kind: string | undefined) => {\n if (state.ready || state.failed) readyWait.resolve();\n if (state.failed) doneWait.resolve();\n if (!state.clientDone) return;\n if (kind === 'committed_transcript' || kind === 'committed_transcript_with_timestamps') {\n doneWait.resolve();\n }\n if (kind === 'close') doneWait.resolve();\n };\n try {\n if (options?.signal?.aborted) throw new DOMException('aborted', 'AbortError');\n const iterator = audio[Symbol.asyncIterator]();\n const first = await iterator.next();\n const format = chosenFormat(model, first.done ? undefined : first.value);\n socket = connectSocket(sttUrl(model, format, options), xiHeaders(model, options));\n let incoming = Promise.resolve();\n socket.addEventListener('message', (event) => {\n incoming = incoming.then(() => onMessage(event.data, state, push, wake));\n });\n socket.addEventListener('close', () => {\n if (state.opened && !state.failed && !state.completed && !state.clientDone) {\n state.failed = true;\n push({ type: 'error', reason: 'error', error: 'ElevenLabs transcription socket closed' });\n }\n wake('close');\n });\n await awaitOpen(socket, 'ElevenLabs transcription', (message) => {\n state.failed = true;\n push({ type: 'error', reason: 'error', error: message });\n wake('fail');\n });\n if (state.failed) return;\n state.opened = true;\n if (!state.ready) await readyWait.promise;\n if (state.failed) return;\n if (!first.done) sendChunk(socket, first.value, format, state, false);\n while (!state.failed) {\n if (options?.signal?.aborted) throw new DOMException('aborted', 'AbortError');\n const next = await iterator.next();\n if (next.done) break;\n sendChunk(socket, next.value, format, state, false);\n }\n if (state.failed) return;\n sendChunk(socket, undefined, format, state, true);\n state.clientDone = true;\n await doneWait.promise;\n finish(model, format, state, push);\n } catch (error) {\n const voice = asVoiceError(error);\n state.failed = true;\n push({ type: 'error', reason: voice.reason, error: voice.error });\n } finally {\n socket?.close();\n }\n}\n\nfunction sendChunk(\n socket: WebSocket,\n chunk: AudioChunk | undefined,\n format: AudioFormat,\n state: ListenState,\n commit: boolean\n) {\n if (chunk && !sameFormat(chunk.format, format)) {\n throw new Error(`Unsupported audio format ${chunk.format.encoding}`);\n }\n const data = chunk ? chunk.data : new Uint8Array();\n state.bytes += data.byteLength;\n socket.send(\n JSON.stringify({\n message_type: 'input_audio_chunk',\n audio_base_64: encodeAudioBase64(data),\n commit,\n sample_rate: format.sampleRate,\n })\n );\n}\n\nasync function onMessage(\n data: unknown,\n state: ListenState,\n push: (event: TranscriptionEvent) => void,\n wake: (kind: string | undefined) => void\n) {\n try {\n const parsed = JSON.parse(await messageText(data)) as unknown;\n if (!isRecord(parsed)) throw new Error('ElevenLabs transcription event was not JSON');\n applyListen(parsed, state, push);\n wake(stringField(parsed, 'message_type'));\n } catch (error) {\n const voice = asVoiceError(error);\n state.failed = true;\n push({ type: 'error', reason: voice.reason, error: voice.error });\n wake('fail');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }