@ai-ecoverse/pi-audio 1.8.0 → 1.9.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
@@ -29,6 +29,8 @@ pi-audio does not apply that patch. Hosts such as slicc-agent record monkeypatch
29
29
 
30
30
  `planDelivery` reads `RealtimeModel.delivery`. An `unsupported` mode throws `VoiceRequestError`. `when_idle` with `approximate` support waits while the model is producing output and runs on `response_done`, `interrupt`, or `close`. Native `when_idle` is sent immediately so the server can schedule it. `planTruncation` adds `{ itemId, audioEndMs }` only when `truncation` is `client` and `interrupt` was given what the listener heard.
31
31
 
32
+ `@ai-ecoverse/pi-audio/openai-realtime` registers `gpt-realtime-2` and `gpt-realtime-2.1`. `@ai-ecoverse/pi-audio/xai` registers `grok-voice-latest`, an OpenAI Realtime dialect: input transcripts arrive as cumulative `updated` snapshots, `inject` speaks with `force_message` (no `response.create`), and `session.update` opts into `resumption` and an optional `replace` map. Reconnect with `resumeHandle`. Output audio is tagged with the transcript range spoken since the previous delta. Both adapters take a recorded `wire` in tests and never call a provider. WebRTC uses that same event protocol on a caller-supplied data-channel wire.
33
+
32
34
  `RealtimeLedger` meters one session. Input seconds come from `appendAudio`. Output seconds come from each `output_audio` event: PCM, mulaw, and alaw from the byte length, and a compressed encoding from character alignment. `sendText` and each delivered `inject` add one text message. Tool results do not. `close()` prices those meters with `priceAudio`. Sum that usage once. `response_done.usage` is the adapter's per-response figure, not a second copy of the session total.
33
35
 
34
36
  `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.
@@ -0,0 +1,58 @@
1
+ import { registerVoiceModel } from "../models.js";
2
+ import { registerRealtimeApiProvider } from "../registry.js";
3
+ import { connectVendorSession, pcmFormat } from "./session.js";
4
+ const voices = [
5
+ { id: "marin", name: "Marin" },
6
+ { id: "cedar", name: "Cedar" },
7
+ { id: "alloy", name: "Alloy" },
8
+ { id: "ash", name: "Ash" },
9
+ { id: "ballad", name: "Ballad" },
10
+ { id: "coral", name: "Coral" },
11
+ { id: "echo", name: "Echo" },
12
+ { id: "sage", name: "Sage" },
13
+ { id: "shimmer", name: "Shimmer" },
14
+ { id: "verse", name: "Verse" }
15
+ ];
16
+ const audioCost = { perInputMinute: 0.0192, perOutputMinute: 0.0384 };
17
+ function realtimeModel(id, name) {
18
+ return {
19
+ id,
20
+ name,
21
+ api: "openai-realtime",
22
+ provider: "openai",
23
+ baseUrl: "wss://api.openai.com/v1/realtime",
24
+ type: "realtime",
25
+ transports: ["websocket", "webrtc"],
26
+ toolCalls: "blocking",
27
+ inputFormat: pcmFormat(24e3),
28
+ outputFormat: pcmFormat(24e3),
29
+ resumable: false,
30
+ ephemeralAuth: true,
31
+ voices,
32
+ provisioning: "none",
33
+ delivery: { interrupt: "native", when_idle: "approximate", silent: "native" },
34
+ truncation: "client",
35
+ liveToolUpdate: true,
36
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
37
+ audioCost
38
+ };
39
+ }
40
+ const gptRealtime2 = realtimeModel("gpt-realtime-2", "GPT Realtime 2");
41
+ const gptRealtime21 = realtimeModel("gpt-realtime-2.1", "GPT Realtime 2.1");
42
+ function registerOpenAIRealtime(options = {}) {
43
+ const unregisterModels = [gptRealtime2, gptRealtime21].map(registerVoiceModel);
44
+ const unregisterApi = registerRealtimeApiProvider({
45
+ api: "openai-realtime",
46
+ connect: (model, context, connectOptions) => connectVendorSession(model, context, connectOptions, { ...options, dialect: "openai" })
47
+ });
48
+ return () => {
49
+ for (const unregister of unregisterModels) unregister();
50
+ unregisterApi();
51
+ };
52
+ }
53
+ export {
54
+ gptRealtime2,
55
+ gptRealtime21,
56
+ registerOpenAIRealtime
57
+ };
58
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/openai-realtime/index.ts"],
4
+ "sourcesContent": ["import type { RealtimeModel } from '../models.ts';\nimport { registerVoiceModel } from '../models.ts';\nimport { registerRealtimeApiProvider } from '../registry.ts';\nimport { type AdapterOptions, connectVendorSession, pcmFormat } from './session.ts';\n\nconst voices = [\n { id: 'marin', name: 'Marin' },\n { id: 'cedar', name: 'Cedar' },\n { id: 'alloy', name: 'Alloy' },\n { id: 'ash', name: 'Ash' },\n { id: 'ballad', name: 'Ballad' },\n { id: 'coral', name: 'Coral' },\n { id: 'echo', name: 'Echo' },\n { id: 'sage', name: 'Sage' },\n { id: 'shimmer', name: 'Shimmer' },\n { id: 'verse', name: 'Verse' },\n];\n\nconst audioCost = { perInputMinute: 0.0192, perOutputMinute: 0.0384 };\n\nfunction realtimeModel(id: string, name: string): RealtimeModel<'openai-realtime'> {\n return {\n id,\n name,\n api: 'openai-realtime',\n provider: 'openai',\n baseUrl: 'wss://api.openai.com/v1/realtime',\n type: 'realtime',\n transports: ['websocket', 'webrtc'],\n toolCalls: 'blocking',\n inputFormat: pcmFormat(24000),\n outputFormat: pcmFormat(24000),\n resumable: false,\n ephemeralAuth: true,\n voices,\n provisioning: 'none',\n delivery: { interrupt: 'native', when_idle: 'approximate', silent: 'native' },\n truncation: 'client',\n liveToolUpdate: true,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n audioCost,\n };\n}\n\nexport const gptRealtime2 = realtimeModel('gpt-realtime-2', 'GPT Realtime 2');\n\nexport const gptRealtime21 = realtimeModel('gpt-realtime-2.1', 'GPT Realtime 2.1');\n\nexport function registerOpenAIRealtime(options: AdapterOptions = {}): () => void {\n const unregisterModels = [gptRealtime2, gptRealtime21].map(registerVoiceModel);\n const unregisterApi = registerRealtimeApiProvider({\n api: 'openai-realtime',\n connect: (model, context, connectOptions) =>\n connectVendorSession(model, context, connectOptions, { ...options, dialect: 'openai' }),\n });\n return () => {\n for (const unregister of unregisterModels) unregister();\n unregisterApi();\n };\n}\n\nexport type { AdapterOptions, VoiceWireFactory } from './session.ts';\nexport type { VoiceSocketConstructor, VoiceWire, VoiceWireTarget } from './socket.ts';\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAA8B,sBAAsB,iBAAiB;AAErE,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,SAAS,MAAM,QAAQ;AAAA,EAC7B,EAAE,IAAI,SAAS,MAAM,QAAQ;AAAA,EAC7B,EAAE,IAAI,SAAS,MAAM,QAAQ;AAAA,EAC7B,EAAE,IAAI,OAAO,MAAM,MAAM;AAAA,EACzB,EAAE,IAAI,UAAU,MAAM,SAAS;AAAA,EAC/B,EAAE,IAAI,SAAS,MAAM,QAAQ;AAAA,EAC7B,EAAE,IAAI,QAAQ,MAAM,OAAO;AAAA,EAC3B,EAAE,IAAI,QAAQ,MAAM,OAAO;AAAA,EAC3B,EAAE,IAAI,WAAW,MAAM,UAAU;AAAA,EACjC,EAAE,IAAI,SAAS,MAAM,QAAQ;AAC/B;AAEA,MAAM,YAAY,EAAE,gBAAgB,QAAQ,iBAAiB,OAAO;AAEpE,SAAS,cAAc,IAAY,MAAgD;AACjF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,KAAK;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,YAAY,CAAC,aAAa,QAAQ;AAAA,IAClC,WAAW;AAAA,IACX,aAAa,UAAU,IAAK;AAAA,IAC5B,cAAc,UAAU,IAAK;AAAA,IAC7B,WAAW;AAAA,IACX,eAAe;AAAA,IACf;AAAA,IACA,cAAc;AAAA,IACd,UAAU,EAAE,WAAW,UAAU,WAAW,eAAe,QAAQ,SAAS;AAAA,IAC5E,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,IACzD;AAAA,EACF;AACF;AAEO,MAAM,eAAe,cAAc,kBAAkB,gBAAgB;AAErE,MAAM,gBAAgB,cAAc,oBAAoB,kBAAkB;AAE1E,SAAS,uBAAuB,UAA0B,CAAC,GAAe;AAC/E,QAAM,mBAAmB,CAAC,cAAc,aAAa,EAAE,IAAI,kBAAkB;AAC7E,QAAM,gBAAgB,4BAA4B;AAAA,IAChD,KAAK;AAAA,IACL,SAAS,CAAC,OAAO,SAAS,mBACxB,qBAAqB,OAAO,SAAS,gBAAgB,EAAE,GAAG,SAAS,SAAS,SAAS,CAAC;AAAA,EAC1F,CAAC;AACD,SAAO,MAAM;AACX,eAAW,cAAc,iBAAkB,YAAW;AACtD,kBAAc;AAAA,EAChB;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,494 @@
1
+ import { blankUsage } from "../blank-usage.js";
2
+ import { decodeAudioBase64, encodeAudioBase64 } from "../convert.js";
3
+ import { VoiceRequestError } from "../errors.js";
4
+ import { planTruncation, RealtimeLedger, realtimeSocket } from "../realtime.js";
5
+ import {
6
+ openVoiceSocket
7
+ } from "./socket.js";
8
+ function record(value) {
9
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
10
+ return value;
11
+ }
12
+ function text(value) {
13
+ return typeof value === "string" ? value : "";
14
+ }
15
+ function credential(options) {
16
+ const token = options?.token?.value;
17
+ if (typeof token === "string" && token.length > 0) return token;
18
+ const apiKey = options?.apiKey;
19
+ if (typeof apiKey === "string" && apiKey.length > 0) return apiKey;
20
+ throw new VoiceRequestError("error", "missing realtime credential");
21
+ }
22
+ function headersFor(options, secret, bearer) {
23
+ const headers = {};
24
+ if (bearer) headers.Authorization = `Bearer ${secret}`;
25
+ const extra = options?.headers;
26
+ if (extra === void 0) return headers;
27
+ for (const [key, value] of Object.entries(extra)) {
28
+ if (value === null) delete headers[key];
29
+ else headers[key] = value;
30
+ }
31
+ return headers;
32
+ }
33
+ function sessionUrl(model, resumeHandle) {
34
+ const params = new URLSearchParams({ model: model.id });
35
+ if (resumeHandle !== void 0) params.set("conversation_id", resumeHandle);
36
+ return `${model.baseUrl}?${params.toString()}`;
37
+ }
38
+ function functionTool(tool) {
39
+ return {
40
+ type: "function",
41
+ name: tool.name,
42
+ description: tool.description,
43
+ parameters: tool.parameters
44
+ };
45
+ }
46
+ function openaiSession(context, model) {
47
+ return {
48
+ type: "realtime",
49
+ instructions: context.instructions,
50
+ tools: context.tools.map(functionTool),
51
+ audio: {
52
+ input: {
53
+ format: { type: "audio/pcm", rate: model.inputFormat.sampleRate },
54
+ turn_detection: { type: "server_vad" }
55
+ },
56
+ output: {
57
+ format: { type: "audio/pcm", rate: model.outputFormat.sampleRate },
58
+ voice: context.voice
59
+ }
60
+ }
61
+ };
62
+ }
63
+ function xaiSession(context, model, replace) {
64
+ const session = {
65
+ voice: context.voice,
66
+ instructions: context.instructions,
67
+ turn_detection: { type: "server_vad" },
68
+ tools: context.tools.map(functionTool),
69
+ resumption: { enabled: true },
70
+ audio: {
71
+ input: { format: { type: "audio/pcm", rate: model.inputFormat.sampleRate } },
72
+ output: { format: { type: "audio/pcm", rate: model.outputFormat.sampleRate } }
73
+ }
74
+ };
75
+ if (replace !== void 0) session.replace = replace;
76
+ return session;
77
+ }
78
+ function userItem(value) {
79
+ return {
80
+ type: "conversation.item.create",
81
+ item: {
82
+ type: "message",
83
+ role: "user",
84
+ content: [{ type: "input_text", text: value }]
85
+ }
86
+ };
87
+ }
88
+ function responseCreate() {
89
+ return { type: "response.create" };
90
+ }
91
+ function resultText(result) {
92
+ const parts = [];
93
+ for (const part of result.content) {
94
+ if (part.type === "text") parts.push(part.text);
95
+ }
96
+ return parts.join("\n");
97
+ }
98
+ function toolOutput(callId, result) {
99
+ return {
100
+ type: "conversation.item.create",
101
+ item: { type: "function_call_output", call_id: callId, output: resultText(result) }
102
+ };
103
+ }
104
+ function forceMessage(value) {
105
+ return {
106
+ type: "conversation.item.create",
107
+ item: {
108
+ type: "force_message",
109
+ role: "assistant",
110
+ content: [{ type: "output_text", text: value }]
111
+ }
112
+ };
113
+ }
114
+ function spanFor(itemId, start, end) {
115
+ return { itemId, start, end };
116
+ }
117
+ function takeAudio(pending, itemId, format, end) {
118
+ const chunks = pending.chunks;
119
+ pending.chunks = [];
120
+ if (chunks.length === 0) return [];
121
+ const start = pending.cursor;
122
+ if (end !== void 0) pending.cursor = end;
123
+ const width = end === void 0 ? 0 : end - start;
124
+ const total = chunks.reduce((sum, chunk) => sum + chunk.data.length, 0);
125
+ let used = 0;
126
+ let cursor = start;
127
+ return chunks.map((chunk, index) => {
128
+ const event = {
129
+ type: "output_audio",
130
+ itemId,
131
+ chunk: { data: chunk.data, format, timestamp: chunk.timestamp }
132
+ };
133
+ if (end === void 0 || total === 0) return event;
134
+ const last = index === chunks.length - 1;
135
+ const next = last ? end : start + Math.round((used + chunk.data.length) / total * width);
136
+ used += chunk.data.length;
137
+ event.span = spanFor(itemId, cursor, next);
138
+ cursor = next;
139
+ return event;
140
+ });
141
+ }
142
+ function argumentsOf(value) {
143
+ if (typeof value !== "string") return {};
144
+ try {
145
+ const parsed = JSON.parse(value);
146
+ const object = record(parsed);
147
+ if (object === void 0) return {};
148
+ return object;
149
+ } catch {
150
+ return {};
151
+ }
152
+ }
153
+ function responseUsage(value) {
154
+ const usage = record(value);
155
+ if (usage === void 0) return blankUsage();
156
+ const input = typeof usage.input_tokens === "number" ? usage.input_tokens : 0;
157
+ const output = typeof usage.output_tokens === "number" ? usage.output_tokens : 0;
158
+ return {
159
+ input,
160
+ output,
161
+ cacheRead: 0,
162
+ cacheWrite: 0,
163
+ totalTokens: input + output,
164
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
165
+ };
166
+ }
167
+ class VendorSession {
168
+ model;
169
+ #dialect;
170
+ #context;
171
+ #replace;
172
+ #wire;
173
+ #ledger = new RealtimeLedger();
174
+ #format;
175
+ #pending = /* @__PURE__ */ new Map();
176
+ #inputText = /* @__PURE__ */ new Map();
177
+ #queue = [];
178
+ #waiters = [];
179
+ #closed = false;
180
+ #usage;
181
+ #lastItem = "";
182
+ constructor(model, context, dialect, replace, wire) {
183
+ this.model = model;
184
+ this.#context = context;
185
+ this.#dialect = dialect;
186
+ this.#replace = replace;
187
+ this.#wire = wire;
188
+ this.#format = model.outputFormat;
189
+ }
190
+ start(options) {
191
+ this.#send(this.#sessionUpdate(this.#context));
192
+ const signal = options?.signal;
193
+ if (signal !== void 0) signal.addEventListener("abort", () => void this.close());
194
+ void this.#read();
195
+ }
196
+ appendAudio(chunk) {
197
+ this.#assertOpen();
198
+ this.#ledger.addInput(chunk);
199
+ this.#send({ type: "input_audio_buffer.append", audio: encodeAudioBase64(chunk.data) });
200
+ }
201
+ sendText(textValue) {
202
+ this.#assertOpen();
203
+ this.#ledger.addText();
204
+ this.#send(userItem(textValue));
205
+ this.#send(responseCreate());
206
+ }
207
+ inject(textValue, delivery) {
208
+ this.#assertOpen();
209
+ this.#ledger.submit(this.model.delivery, delivery, () => {
210
+ this.#assertOpen();
211
+ this.#ledger.addText();
212
+ this.#sendInject(textValue, delivery);
213
+ });
214
+ }
215
+ respond(callId, result, delivery) {
216
+ this.#assertOpen();
217
+ const send = () => {
218
+ this.#send(toolOutput(callId, result));
219
+ if (delivery !== "silent") this.#send(responseCreate());
220
+ };
221
+ if (delivery === void 0) {
222
+ send();
223
+ return;
224
+ }
225
+ this.#ledger.submit(this.model.delivery, delivery, () => {
226
+ this.#assertOpen();
227
+ send();
228
+ });
229
+ }
230
+ interrupt(heard) {
231
+ this.#assertOpen();
232
+ this.#send({ type: "response.cancel" });
233
+ const plan = planTruncation(this.model.truncation, heard);
234
+ if (plan.item !== void 0) {
235
+ this.#send({
236
+ type: "conversation.item.truncate",
237
+ item_id: plan.item.itemId,
238
+ content_index: 0,
239
+ audio_end_ms: plan.item.audioEndMs
240
+ });
241
+ }
242
+ this.#ledger.settle();
243
+ }
244
+ update(patch) {
245
+ this.#assertOpen();
246
+ if (patch.tools !== void 0 && !this.model.liveToolUpdate) {
247
+ throw new VoiceRequestError("error", "live tool update is unsupported");
248
+ }
249
+ let instructions = this.#context.instructions;
250
+ if (patch.instructions !== void 0) instructions = patch.instructions;
251
+ let tools = this.#context.tools;
252
+ if (patch.tools !== void 0) tools = patch.tools;
253
+ let voice = this.#context.voice;
254
+ if (patch.voice !== void 0) voice = patch.voice;
255
+ const context = { instructions, tools, voice };
256
+ if (patch.instructions !== void 0) this.#context.instructions = context.instructions;
257
+ if (patch.tools !== void 0) this.#context.tools = context.tools;
258
+ if (patch.voice !== void 0) this.#context.voice = context.voice;
259
+ this.#send(this.#sessionUpdate(context));
260
+ }
261
+ async close() {
262
+ if (this.#usage !== void 0) return { usage: this.#usage };
263
+ this.#ledger.settle();
264
+ this.#closed = true;
265
+ this.#wire.close();
266
+ this.#end();
267
+ this.#usage = this.#ledger.usage(this.model.audioCost);
268
+ return { usage: this.#usage };
269
+ }
270
+ async *[Symbol.asyncIterator]() {
271
+ while (true) {
272
+ const queued = this.#queue.shift();
273
+ if (queued !== void 0) {
274
+ yield queued;
275
+ continue;
276
+ }
277
+ if (this.#closed) return;
278
+ const next = await new Promise((resolve) => {
279
+ this.#waiters.push(resolve);
280
+ });
281
+ if (next.done === true || next.value === void 0) return;
282
+ yield next.value;
283
+ }
284
+ }
285
+ #sessionUpdate(context) {
286
+ if (this.#dialect === "openai") {
287
+ return { type: "session.update", session: openaiSession(context, this.model) };
288
+ }
289
+ return { type: "session.update", session: xaiSession(context, this.model, this.#replace) };
290
+ }
291
+ #sendInject(textValue, delivery) {
292
+ if (this.#dialect === "xai" && delivery !== "silent") {
293
+ this.#send(forceMessage(textValue));
294
+ return;
295
+ }
296
+ this.#send(userItem(textValue));
297
+ if (delivery !== "silent") this.#send(responseCreate());
298
+ }
299
+ #send(event) {
300
+ this.#wire.send(JSON.stringify(event));
301
+ }
302
+ #assertOpen() {
303
+ if (this.#closed) throw new VoiceRequestError("error", "session is closed");
304
+ }
305
+ #push(event) {
306
+ if (this.#closed) return;
307
+ this.#ledger.observe(event);
308
+ const waiter = this.#waiters.shift();
309
+ if (waiter !== void 0) waiter({ value: event, done: false });
310
+ else this.#queue.push(event);
311
+ }
312
+ #end() {
313
+ for (const waiter of this.#waiters) waiter({ value: void 0, done: true });
314
+ this.#waiters.length = 0;
315
+ }
316
+ #pendingFor(itemId) {
317
+ const existing = this.#pending.get(itemId);
318
+ if (existing !== void 0) return existing;
319
+ const created = { chunks: [], cursor: 0 };
320
+ this.#pending.set(itemId, created);
321
+ return created;
322
+ }
323
+ #audio(itemId, encoded) {
324
+ this.#lastItem = itemId;
325
+ const pending = this.#pendingFor(itemId);
326
+ pending.chunks.push({
327
+ data: decodeAudioBase64(encoded),
328
+ format: this.#format,
329
+ timestamp: 0
330
+ });
331
+ }
332
+ #transcript(itemId, delta, final) {
333
+ const pending = this.#pendingFor(itemId);
334
+ const end = pending.cursor + delta.length;
335
+ const tagged = delta.length === 0 ? void 0 : end;
336
+ for (const event of takeAudio(pending, itemId, this.#format, tagged)) this.#push(event);
337
+ this.#push({ type: "output_transcript", itemId, text: delta, final });
338
+ }
339
+ #input(itemId, transcript, mode, final) {
340
+ const previous = this.#inputText.get(itemId) ?? "";
341
+ const next = mode === "append" ? `${previous}${transcript}` : transcript;
342
+ this.#inputText.set(itemId, next);
343
+ this.#push({ type: "input_transcript", itemId, text: next, final });
344
+ }
345
+ #field(value, key) {
346
+ const object = record(value);
347
+ if (object === void 0) return "";
348
+ return text(object[key]);
349
+ }
350
+ #functionCall(event) {
351
+ this.#push({
352
+ type: "tool_call",
353
+ blocking: this.model.toolCalls === "blocking",
354
+ call: {
355
+ type: "toolCall",
356
+ id: text(event.call_id),
357
+ name: text(event.name),
358
+ arguments: argumentsOf(event.arguments)
359
+ }
360
+ });
361
+ }
362
+ #responseDone(event) {
363
+ const response = record(event.response);
364
+ const output = response === void 0 ? void 0 : response.output;
365
+ const first = Array.isArray(output) ? record(output[0]) : void 0;
366
+ let itemId = this.#lastItem;
367
+ if (first !== void 0 && text(first.id).length > 0) itemId = text(first.id);
368
+ const pending = this.#pendingFor(itemId);
369
+ for (const audioEvent of takeAudio(pending, itemId, this.#format, void 0)) {
370
+ this.#push(audioEvent);
371
+ }
372
+ const status = response === void 0 ? "" : text(response.status);
373
+ const usage = response === void 0 ? void 0 : response.usage;
374
+ this.#push({
375
+ type: "response_done",
376
+ itemId,
377
+ interrupted: status === "cancelled",
378
+ usage: responseUsage(usage)
379
+ });
380
+ }
381
+ #serverError(event) {
382
+ const detail = record(event.error);
383
+ const code = detail === void 0 ? "" : text(detail.code);
384
+ const message = detail === void 0 ? "" : text(detail.message);
385
+ let fatal = false;
386
+ if (code === "max_duration") fatal = true;
387
+ if (code === "session_expired") fatal = true;
388
+ this.#push({ type: "error", error: message, fatal });
389
+ }
390
+ #apply(event) {
391
+ const type = text(event.type);
392
+ if (type === "session.created") {
393
+ this.#push({ type: "session_start", sessionId: this.#field(event.session, "id") });
394
+ return;
395
+ }
396
+ if (type === "conversation.created") {
397
+ this.#push({ type: "resumption", handle: this.#field(event.conversation, "id") });
398
+ return;
399
+ }
400
+ if (type === "input_audio_buffer.speech_started") {
401
+ this.#push({ type: "input_speech_start" });
402
+ return;
403
+ }
404
+ if (type === "conversation.item.input_audio_transcription.delta") {
405
+ this.#input(text(event.item_id), text(event.delta), "append", false);
406
+ return;
407
+ }
408
+ if (type === "conversation.item.input_audio_transcription.updated") {
409
+ this.#input(text(event.item_id), text(event.transcript), "replace", false);
410
+ return;
411
+ }
412
+ if (type === "conversation.item.input_audio_transcription.completed") {
413
+ this.#input(text(event.item_id), text(event.transcript), "replace", true);
414
+ return;
415
+ }
416
+ if (type === "response.output_audio.delta") {
417
+ this.#audio(text(event.item_id), text(event.delta));
418
+ return;
419
+ }
420
+ if (type === "response.output_audio_transcript.delta") {
421
+ this.#transcript(text(event.item_id), text(event.delta), false);
422
+ return;
423
+ }
424
+ if (type === "response.output_audio_transcript.done") {
425
+ this.#transcript(text(event.item_id), text(event.transcript), true);
426
+ return;
427
+ }
428
+ if (type === "response.function_call_arguments.done") {
429
+ this.#functionCall(event);
430
+ return;
431
+ }
432
+ if (type === "response.done") {
433
+ this.#responseDone(event);
434
+ return;
435
+ }
436
+ if (type === "error") this.#serverError(event);
437
+ }
438
+ async #read() {
439
+ try {
440
+ for await (const raw of this.#wire.incoming) {
441
+ let parsed;
442
+ try {
443
+ parsed = JSON.parse(raw);
444
+ } catch {
445
+ this.#push({ type: "error", error: "invalid realtime event", fatal: false });
446
+ continue;
447
+ }
448
+ const event = record(parsed);
449
+ if (event === void 0 || typeof event.type !== "string") continue;
450
+ this.#apply(event);
451
+ }
452
+ } catch (error) {
453
+ const message = error instanceof Error ? error.message : String(error);
454
+ this.#push({ type: "error", error: message, fatal: true });
455
+ } finally {
456
+ this.#closed = true;
457
+ this.#end();
458
+ }
459
+ }
460
+ }
461
+ async function connectVendorSession(model, context, options, vendor) {
462
+ if (options?.signal?.aborted === true) {
463
+ throw new VoiceRequestError("aborted", "session aborted");
464
+ }
465
+ if (options?.transport === "webrtc" && vendor.wire === void 0) {
466
+ throw new VoiceRequestError("error", "webrtc requires a data channel wire");
467
+ }
468
+ const secret = credential(options);
469
+ const token = options?.token?.value;
470
+ const ephemeral = typeof token === "string" && token.length > 0;
471
+ const protocols = [];
472
+ if (vendor.dialect === "openai") protocols.push(`openai-insecure-api-key.${secret}`, "realtime");
473
+ else if (ephemeral) protocols.push(`xai-client-secret.${secret}`);
474
+ else protocols.push("realtime");
475
+ const resumeHandle = vendor.dialect === "xai" ? options?.resumeHandle : void 0;
476
+ const socket = realtimeSocket(sessionUrl(model, resumeHandle), options, protocols);
477
+ const target = {
478
+ url: socket.url,
479
+ protocols: socket.protocols,
480
+ headers: headersFor(options, secret, vendor.dialect === "openai" || !ephemeral)
481
+ };
482
+ const open = vendor.wire !== void 0 ? vendor.wire : (wireTarget) => openVoiceSocket(wireTarget, vendor.socket);
483
+ const session = new VendorSession(model, context, vendor.dialect, vendor.replace, open(target));
484
+ session.start(options);
485
+ return session;
486
+ }
487
+ function pcmFormat(sampleRate) {
488
+ return { encoding: "pcm16", sampleRate, channels: 1 };
489
+ }
490
+ export {
491
+ connectVendorSession,
492
+ pcmFormat
493
+ };
494
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/openai-realtime/session.ts"],
4
+ "sourcesContent": ["import type { JsonObject, Tool, ToolResultMessage, Usage } from '@earendil-works/pi-ai';\nimport type { AudioChunk, AudioFormat, TextSpan } from '../audio.ts';\nimport { blankUsage } from '../blank-usage.ts';\nimport { decodeAudioBase64, encodeAudioBase64 } from '../convert.ts';\nimport { VoiceRequestError } from '../errors.ts';\nimport type { Delivery, RealtimeModel } from '../models.ts';\nimport type {\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n} from '../realtime.ts';\nimport { planTruncation, RealtimeLedger, realtimeSocket } from '../realtime.ts';\nimport {\n openVoiceSocket,\n type VoiceSocketConstructor,\n type VoiceWire,\n type VoiceWireTarget,\n} from './socket.ts';\n\nexport type VoiceWireFactory = (target: VoiceWireTarget) => VoiceWire;\n\nexport interface AdapterOptions {\n wire?: VoiceWireFactory;\n socket?: VoiceSocketConstructor;\n replace?: Record<string, string>;\n}\n\nexport interface VendorSessionOptions extends AdapterOptions {\n dialect: 'openai' | 'xai';\n}\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined;\n return value as Record<string, unknown>;\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction credential(options: RealtimeOptions | undefined): string {\n const token = options?.token?.value;\n if (typeof token === 'string' && token.length > 0) return token;\n const apiKey = options?.apiKey;\n if (typeof apiKey === 'string' && apiKey.length > 0) return apiKey;\n throw new VoiceRequestError('error', 'missing realtime credential');\n}\n\nfunction headersFor(\n options: RealtimeOptions | undefined,\n secret: string,\n bearer: boolean\n): Record<string, string> {\n const headers: Record<string, string> = {};\n if (bearer) headers.Authorization = `Bearer ${secret}`;\n const extra = options?.headers;\n if (extra === undefined) return headers;\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) delete headers[key];\n else headers[key] = value;\n }\n return headers;\n}\n\nfunction sessionUrl(model: RealtimeModel<string>, resumeHandle: string | undefined): string {\n const params = new URLSearchParams({ model: model.id });\n if (resumeHandle !== undefined) params.set('conversation_id', resumeHandle);\n return `${model.baseUrl}?${params.toString()}`;\n}\n\nfunction functionTool(tool: Tool): JsonObject {\n return {\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown as JsonObject,\n };\n}\n\nfunction openaiSession(context: RealtimeContext, model: RealtimeModel<string>): JsonObject {\n return {\n type: 'realtime',\n instructions: context.instructions,\n tools: context.tools.map(functionTool),\n audio: {\n input: {\n format: { type: 'audio/pcm', rate: model.inputFormat.sampleRate },\n turn_detection: { type: 'server_vad' },\n },\n output: {\n format: { type: 'audio/pcm', rate: model.outputFormat.sampleRate },\n voice: context.voice,\n },\n },\n };\n}\n\nfunction xaiSession(\n context: RealtimeContext,\n model: RealtimeModel<string>,\n replace: Record<string, string> | undefined\n): JsonObject {\n const session: JsonObject = {\n voice: context.voice,\n instructions: context.instructions,\n turn_detection: { type: 'server_vad' },\n tools: context.tools.map(functionTool),\n resumption: { enabled: true },\n audio: {\n input: { format: { type: 'audio/pcm', rate: model.inputFormat.sampleRate } },\n output: { format: { type: 'audio/pcm', rate: model.outputFormat.sampleRate } },\n },\n };\n if (replace !== undefined) session.replace = replace;\n return session;\n}\n\nfunction userItem(value: string): JsonObject {\n return {\n type: 'conversation.item.create',\n item: {\n type: 'message',\n role: 'user',\n content: [{ type: 'input_text', text: value }],\n },\n };\n}\n\nfunction responseCreate(): JsonObject {\n return { type: 'response.create' };\n}\n\nfunction resultText(result: ToolResultMessage): string {\n const parts: string[] = [];\n for (const part of result.content) {\n if (part.type === 'text') parts.push(part.text);\n }\n return parts.join('\\n');\n}\n\nfunction toolOutput(callId: string, result: ToolResultMessage): JsonObject {\n return {\n type: 'conversation.item.create',\n item: { type: 'function_call_output', call_id: callId, output: resultText(result) },\n };\n}\n\nfunction forceMessage(value: string): JsonObject {\n return {\n type: 'conversation.item.create',\n item: {\n type: 'force_message',\n role: 'assistant',\n content: [{ type: 'output_text', text: value }],\n },\n };\n}\n\ninterface PendingAudio {\n chunks: AudioChunk[];\n cursor: number;\n}\n\nfunction spanFor(itemId: string, start: number, end: number): TextSpan {\n return { itemId, start, end };\n}\n\nfunction takeAudio(\n pending: PendingAudio,\n itemId: string,\n format: AudioFormat,\n end: number | undefined\n): RealtimeEvent[] {\n const chunks = pending.chunks;\n pending.chunks = [];\n if (chunks.length === 0) return [];\n const start = pending.cursor;\n if (end !== undefined) pending.cursor = end;\n const width = end === undefined ? 0 : end - start;\n const total = chunks.reduce((sum, chunk) => sum + chunk.data.length, 0);\n let used = 0;\n let cursor = start;\n return chunks.map((chunk, index) => {\n const event: RealtimeEvent = {\n type: 'output_audio',\n itemId,\n chunk: { data: chunk.data, format, timestamp: chunk.timestamp },\n };\n if (end === undefined || total === 0) return event;\n const last = index === chunks.length - 1;\n const next = last ? end : start + Math.round(((used + chunk.data.length) / total) * width);\n used += chunk.data.length;\n event.span = spanFor(itemId, cursor, next);\n cursor = next;\n return event;\n });\n}\n\nfunction argumentsOf(value: unknown): JsonObject {\n if (typeof value !== 'string') return {};\n try {\n const parsed: unknown = JSON.parse(value);\n const object = record(parsed);\n if (object === undefined) return {};\n return object as JsonObject;\n } catch {\n return {};\n }\n}\n\nfunction responseUsage(value: unknown): Usage {\n const usage = record(value);\n if (usage === undefined) return blankUsage();\n const input = typeof usage.input_tokens === 'number' ? usage.input_tokens : 0;\n const output = typeof usage.output_tokens === 'number' ? usage.output_tokens : 0;\n return {\n input,\n output,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: input + output,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n };\n}\n\nclass VendorSession implements RealtimeSession {\n readonly model: RealtimeModel<string>;\n readonly #dialect: 'openai' | 'xai';\n readonly #context: RealtimeContext;\n readonly #replace: Record<string, string> | undefined;\n readonly #wire: VoiceWire;\n readonly #ledger = new RealtimeLedger();\n readonly #format: AudioFormat;\n readonly #pending = new Map<string, PendingAudio>();\n readonly #inputText = new Map<string, string>();\n readonly #queue: RealtimeEvent[] = [];\n readonly #waiters: Array<(result: IteratorResult<RealtimeEvent>) => void> = [];\n #closed = false;\n #usage: Usage | undefined;\n #lastItem = '';\n\n constructor(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n dialect: 'openai' | 'xai',\n replace: Record<string, string> | undefined,\n wire: VoiceWire\n ) {\n this.model = model;\n this.#context = context;\n this.#dialect = dialect;\n this.#replace = replace;\n this.#wire = wire;\n this.#format = model.outputFormat;\n }\n\n start(options: RealtimeOptions | undefined): void {\n this.#send(this.#sessionUpdate(this.#context));\n const signal = options?.signal;\n if (signal !== undefined) signal.addEventListener('abort', () => void this.close());\n void this.#read();\n }\n\n appendAudio(chunk: AudioChunk): void {\n this.#assertOpen();\n this.#ledger.addInput(chunk);\n this.#send({ type: 'input_audio_buffer.append', audio: encodeAudioBase64(chunk.data) });\n }\n\n sendText(textValue: string): void {\n this.#assertOpen();\n this.#ledger.addText();\n this.#send(userItem(textValue));\n this.#send(responseCreate());\n }\n\n inject(textValue: string, delivery: Delivery): void {\n this.#assertOpen();\n this.#ledger.submit(this.model.delivery, delivery, () => {\n this.#assertOpen();\n this.#ledger.addText();\n this.#sendInject(textValue, delivery);\n });\n }\n\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void {\n this.#assertOpen();\n const send = (): void => {\n this.#send(toolOutput(callId, result));\n if (delivery !== 'silent') this.#send(responseCreate());\n };\n if (delivery === undefined) {\n send();\n return;\n }\n this.#ledger.submit(this.model.delivery, delivery, () => {\n this.#assertOpen();\n send();\n });\n }\n\n interrupt(heard?: { itemId: string; heardMs: number }): void {\n this.#assertOpen();\n this.#send({ type: 'response.cancel' });\n const plan = planTruncation(this.model.truncation, heard);\n if (plan.item !== undefined) {\n this.#send({\n type: 'conversation.item.truncate',\n item_id: plan.item.itemId,\n content_index: 0,\n audio_end_ms: plan.item.audioEndMs,\n });\n }\n this.#ledger.settle();\n }\n\n update(patch: Partial<Pick<RealtimeContext, 'instructions' | 'tools' | 'voice'>>): void {\n this.#assertOpen();\n if (patch.tools !== undefined && !this.model.liveToolUpdate) {\n throw new VoiceRequestError('error', 'live tool update is unsupported');\n }\n let instructions = this.#context.instructions;\n if (patch.instructions !== undefined) instructions = patch.instructions;\n let tools = this.#context.tools;\n if (patch.tools !== undefined) tools = patch.tools;\n let voice = this.#context.voice;\n if (patch.voice !== undefined) voice = patch.voice;\n const context: RealtimeContext = { instructions, tools, voice };\n if (patch.instructions !== undefined) this.#context.instructions = context.instructions;\n if (patch.tools !== undefined) this.#context.tools = context.tools;\n if (patch.voice !== undefined) this.#context.voice = context.voice;\n this.#send(this.#sessionUpdate(context));\n }\n\n async close(): Promise<{ usage: Usage }> {\n if (this.#usage !== undefined) return { usage: this.#usage };\n this.#ledger.settle();\n this.#closed = true;\n this.#wire.close();\n this.#end();\n this.#usage = this.#ledger.usage(this.model.audioCost);\n return { usage: this.#usage };\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<RealtimeEvent> {\n while (true) {\n const queued = this.#queue.shift();\n if (queued !== undefined) {\n yield queued;\n continue;\n }\n if (this.#closed) return;\n const next = await new Promise<IteratorResult<RealtimeEvent>>((resolve) => {\n this.#waiters.push(resolve);\n });\n if (next.done === true || next.value === undefined) return;\n yield next.value;\n }\n }\n\n #sessionUpdate(context: RealtimeContext): JsonObject {\n if (this.#dialect === 'openai') {\n return { type: 'session.update', session: openaiSession(context, this.model) };\n }\n return { type: 'session.update', session: xaiSession(context, this.model, this.#replace) };\n }\n\n #sendInject(textValue: string, delivery: Delivery): void {\n if (this.#dialect === 'xai' && delivery !== 'silent') {\n this.#send(forceMessage(textValue));\n return;\n }\n this.#send(userItem(textValue));\n if (delivery !== 'silent') this.#send(responseCreate());\n }\n\n #send(event: JsonObject): void {\n this.#wire.send(JSON.stringify(event));\n }\n\n #assertOpen(): void {\n if (this.#closed) throw new VoiceRequestError('error', 'session is closed');\n }\n\n #push(event: RealtimeEvent): void {\n if (this.#closed) return;\n this.#ledger.observe(event);\n const waiter = this.#waiters.shift();\n if (waiter !== undefined) waiter({ value: event, done: false });\n else this.#queue.push(event);\n }\n\n #end(): void {\n for (const waiter of this.#waiters) waiter({ value: undefined, done: true });\n this.#waiters.length = 0;\n }\n\n #pendingFor(itemId: string): PendingAudio {\n const existing = this.#pending.get(itemId);\n if (existing !== undefined) return existing;\n const created = { chunks: [], cursor: 0 };\n this.#pending.set(itemId, created);\n return created;\n }\n\n #audio(itemId: string, encoded: string): void {\n this.#lastItem = itemId;\n const pending = this.#pendingFor(itemId);\n pending.chunks.push({\n data: decodeAudioBase64(encoded),\n format: this.#format,\n timestamp: 0,\n });\n }\n\n #transcript(itemId: string, delta: string, final: boolean): void {\n const pending = this.#pendingFor(itemId);\n const end = pending.cursor + delta.length;\n const tagged = delta.length === 0 ? undefined : end;\n for (const event of takeAudio(pending, itemId, this.#format, tagged)) this.#push(event);\n this.#push({ type: 'output_transcript', itemId, text: delta, final });\n }\n\n #input(itemId: string, transcript: string, mode: 'append' | 'replace', final: boolean): void {\n const previous = this.#inputText.get(itemId) ?? '';\n const next = mode === 'append' ? `${previous}${transcript}` : transcript;\n this.#inputText.set(itemId, next);\n this.#push({ type: 'input_transcript', itemId, text: next, final });\n }\n\n #field(value: unknown, key: string): string {\n const object = record(value);\n if (object === undefined) return '';\n return text(object[key]);\n }\n\n #functionCall(event: Record<string, unknown>): void {\n this.#push({\n type: 'tool_call',\n blocking: this.model.toolCalls === 'blocking',\n call: {\n type: 'toolCall',\n id: text(event.call_id),\n name: text(event.name),\n arguments: argumentsOf(event.arguments),\n },\n });\n }\n\n #responseDone(event: Record<string, unknown>): void {\n const response = record(event.response);\n const output = response === undefined ? undefined : response.output;\n const first = Array.isArray(output) ? record(output[0]) : undefined;\n let itemId = this.#lastItem;\n if (first !== undefined && text(first.id).length > 0) itemId = text(first.id);\n const pending = this.#pendingFor(itemId);\n for (const audioEvent of takeAudio(pending, itemId, this.#format, undefined)) {\n this.#push(audioEvent);\n }\n const status = response === undefined ? '' : text(response.status);\n const usage = response === undefined ? undefined : response.usage;\n this.#push({\n type: 'response_done',\n itemId,\n interrupted: status === 'cancelled',\n usage: responseUsage(usage),\n });\n }\n\n #serverError(event: Record<string, unknown>): void {\n const detail = record(event.error);\n const code = detail === undefined ? '' : text(detail.code);\n const message = detail === undefined ? '' : text(detail.message);\n let fatal = false;\n if (code === 'max_duration') fatal = true;\n if (code === 'session_expired') fatal = true;\n this.#push({ type: 'error', error: message, fatal });\n }\n\n #apply(event: Record<string, unknown>): void {\n const type = text(event.type);\n if (type === 'session.created') {\n this.#push({ type: 'session_start', sessionId: this.#field(event.session, 'id') });\n return;\n }\n if (type === 'conversation.created') {\n this.#push({ type: 'resumption', handle: this.#field(event.conversation, 'id') });\n return;\n }\n if (type === 'input_audio_buffer.speech_started') {\n this.#push({ type: 'input_speech_start' });\n return;\n }\n if (type === 'conversation.item.input_audio_transcription.delta') {\n this.#input(text(event.item_id), text(event.delta), 'append', false);\n return;\n }\n if (type === 'conversation.item.input_audio_transcription.updated') {\n this.#input(text(event.item_id), text(event.transcript), 'replace', false);\n return;\n }\n if (type === 'conversation.item.input_audio_transcription.completed') {\n this.#input(text(event.item_id), text(event.transcript), 'replace', true);\n return;\n }\n if (type === 'response.output_audio.delta') {\n this.#audio(text(event.item_id), text(event.delta));\n return;\n }\n if (type === 'response.output_audio_transcript.delta') {\n this.#transcript(text(event.item_id), text(event.delta), false);\n return;\n }\n if (type === 'response.output_audio_transcript.done') {\n this.#transcript(text(event.item_id), text(event.transcript), true);\n return;\n }\n if (type === 'response.function_call_arguments.done') {\n this.#functionCall(event);\n return;\n }\n if (type === 'response.done') {\n this.#responseDone(event);\n return;\n }\n if (type === 'error') this.#serverError(event);\n }\n\n async #read(): Promise<void> {\n try {\n for await (const raw of this.#wire.incoming) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.#push({ type: 'error', error: 'invalid realtime event', fatal: false });\n continue;\n }\n const event = record(parsed);\n if (event === undefined || typeof event.type !== 'string') continue;\n this.#apply(event);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n this.#push({ type: 'error', error: message, fatal: true });\n } finally {\n this.#closed = true;\n this.#end();\n }\n }\n}\n\nexport async function connectVendorSession(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options: RealtimeOptions | undefined,\n vendor: VendorSessionOptions\n): Promise<RealtimeSession> {\n if (options?.signal?.aborted === true) {\n throw new VoiceRequestError('aborted', 'session aborted');\n }\n if (options?.transport === 'webrtc' && vendor.wire === undefined) {\n throw new VoiceRequestError('error', 'webrtc requires a data channel wire');\n }\n const secret = credential(options);\n const token = options?.token?.value;\n const ephemeral = typeof token === 'string' && token.length > 0;\n const protocols: string[] = [];\n if (vendor.dialect === 'openai') protocols.push(`openai-insecure-api-key.${secret}`, 'realtime');\n else if (ephemeral) protocols.push(`xai-client-secret.${secret}`);\n else protocols.push('realtime');\n const resumeHandle = vendor.dialect === 'xai' ? options?.resumeHandle : undefined;\n const socket = realtimeSocket(sessionUrl(model, resumeHandle), options, protocols);\n const target: VoiceWireTarget = {\n url: socket.url,\n protocols: socket.protocols,\n headers: headersFor(options, secret, vendor.dialect === 'openai' || !ephemeral),\n };\n const open =\n vendor.wire !== undefined\n ? vendor.wire\n : (wireTarget: VoiceWireTarget) => openVoiceSocket(wireTarget, vendor.socket);\n const session = new VendorSession(model, context, vendor.dialect, vendor.replace, open(target));\n session.start(options);\n return session;\n}\n\nexport function pcmFormat(sampleRate: number): AudioFormat {\n return { encoding: 'pcm16', sampleRate, channels: 1 };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,56 @@
1
+ function openVoiceSocket(target, Socket = WebSocket) {
2
+ const socket = new Socket(target.url, {
3
+ protocols: target.protocols,
4
+ headers: target.headers
5
+ });
6
+ const queue = [];
7
+ const waiters = [];
8
+ let closed = false;
9
+ const push = (value) => {
10
+ if (closed) return;
11
+ const waiter = waiters.shift();
12
+ if (waiter !== void 0) waiter({ value, done: false });
13
+ else queue.push(value);
14
+ };
15
+ const end = () => {
16
+ if (closed) return;
17
+ closed = true;
18
+ for (const waiter of waiters) waiter({ value: void 0, done: true });
19
+ waiters.length = 0;
20
+ };
21
+ socket.addEventListener("message", (event) => {
22
+ push(typeof event.data === "string" ? event.data : String(event.data));
23
+ });
24
+ socket.addEventListener("close", end);
25
+ socket.addEventListener("error", end);
26
+ return {
27
+ send(data) {
28
+ socket.send(data);
29
+ },
30
+ close() {
31
+ socket.close();
32
+ end();
33
+ },
34
+ incoming: {
35
+ async *[Symbol.asyncIterator]() {
36
+ while (true) {
37
+ const queued = queue.shift();
38
+ if (queued !== void 0) {
39
+ yield queued;
40
+ continue;
41
+ }
42
+ if (closed) return;
43
+ const next = await new Promise((resolve) => {
44
+ waiters.push(resolve);
45
+ });
46
+ if (next.done === true || next.value === void 0) return;
47
+ yield next.value;
48
+ }
49
+ }
50
+ }
51
+ };
52
+ }
53
+ export {
54
+ openVoiceSocket
55
+ };
56
+ //# sourceMappingURL=socket.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/openai-realtime/socket.ts"],
4
+ "sourcesContent": ["export interface VoiceWireTarget {\n url: string;\n protocols: string[];\n headers: Record<string, string>;\n}\n\nexport interface VoiceWire {\n send(data: string): void;\n close(): void;\n incoming: AsyncIterable<string>;\n}\n\nexport interface VoiceSocket {\n send(data: string): void;\n close(): void;\n addEventListener(type: 'message', listener: (event: { data: unknown }) => void): void;\n addEventListener(type: 'close', listener: () => void): void;\n addEventListener(type: 'error', listener: () => void): void;\n}\n\nexport type VoiceSocketInit = {\n protocols: string[];\n headers: Record<string, string>;\n};\n\nexport type VoiceSocketConstructor = new (url: string, init: VoiceSocketInit) => VoiceSocket;\n\nexport function openVoiceSocket(\n target: VoiceWireTarget,\n Socket: VoiceSocketConstructor = WebSocket as unknown as VoiceSocketConstructor\n): VoiceWire {\n const socket = new Socket(target.url, {\n protocols: target.protocols,\n headers: target.headers,\n });\n const queue: string[] = [];\n const waiters: Array<(result: IteratorResult<string>) => void> = [];\n let closed = false;\n\n const push = (value: string): void => {\n if (closed) return;\n const waiter = waiters.shift();\n if (waiter !== undefined) waiter({ value, done: false });\n else queue.push(value);\n };\n\n const end = (): void => {\n if (closed) return;\n closed = true;\n for (const waiter of waiters) waiter({ value: undefined, done: true });\n waiters.length = 0;\n };\n\n socket.addEventListener('message', (event) => {\n push(typeof event.data === 'string' ? event.data : String(event.data));\n });\n socket.addEventListener('close', end);\n socket.addEventListener('error', end);\n\n return {\n send(data) {\n socket.send(data);\n },\n close() {\n socket.close();\n end();\n },\n incoming: {\n async *[Symbol.asyncIterator]() {\n while (true) {\n const queued = queue.shift();\n if (queued !== undefined) {\n yield queued;\n continue;\n }\n if (closed) return;\n const next = await new Promise<IteratorResult<string>>((resolve) => {\n waiters.push(resolve);\n });\n if (next.done === true || next.value === undefined) return;\n yield next.value;\n }\n },\n },\n };\n}\n"],
5
+ "mappings": "AA2BO,SAAS,gBACd,QACA,SAAiC,WACtB;AACX,QAAM,SAAS,IAAI,OAAO,OAAO,KAAK;AAAA,IACpC,WAAW,OAAO;AAAA,IAClB,SAAS,OAAO;AAAA,EAClB,CAAC;AACD,QAAM,QAAkB,CAAC;AACzB,QAAM,UAA2D,CAAC;AAClE,MAAI,SAAS;AAEb,QAAM,OAAO,CAAC,UAAwB;AACpC,QAAI,OAAQ;AACZ,UAAM,SAAS,QAAQ,MAAM;AAC7B,QAAI,WAAW,OAAW,QAAO,EAAE,OAAO,MAAM,MAAM,CAAC;AAAA,QAClD,OAAM,KAAK,KAAK;AAAA,EACvB;AAEA,QAAM,MAAM,MAAY;AACtB,QAAI,OAAQ;AACZ,aAAS;AACT,eAAW,UAAU,QAAS,QAAO,EAAE,OAAO,QAAW,MAAM,KAAK,CAAC;AACrE,YAAQ,SAAS;AAAA,EACnB;AAEA,SAAO,iBAAiB,WAAW,CAAC,UAAU;AAC5C,SAAK,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO,OAAO,MAAM,IAAI,CAAC;AAAA,EACvE,CAAC;AACD,SAAO,iBAAiB,SAAS,GAAG;AACpC,SAAO,iBAAiB,SAAS,GAAG;AAEpC,SAAO;AAAA,IACL,KAAK,MAAM;AACT,aAAO,KAAK,IAAI;AAAA,IAClB;AAAA,IACA,QAAQ;AACN,aAAO,MAAM;AACb,UAAI;AAAA,IACN;AAAA,IACA,UAAU;AAAA,MACR,QAAQ,OAAO,aAAa,IAAI;AAC9B,eAAO,MAAM;AACX,gBAAM,SAAS,MAAM,MAAM;AAC3B,cAAI,WAAW,QAAW;AACxB,kBAAM;AACN;AAAA,UACF;AACA,cAAI,OAAQ;AACZ,gBAAM,OAAO,MAAM,IAAI,QAAgC,CAAC,YAAY;AAClE,oBAAQ,KAAK,OAAO;AAAA,UACtB,CAAC;AACD,cAAI,KAAK,SAAS,QAAQ,KAAK,UAAU,OAAW;AACpD,gBAAM,KAAK;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { RealtimeModel } from '../models.ts';
2
+ import { type AdapterOptions } from './session.ts';
3
+ export declare const gptRealtime2: RealtimeModel<"openai-realtime">;
4
+ export declare const gptRealtime21: RealtimeModel<"openai-realtime">;
5
+ export declare function registerOpenAIRealtime(options?: AdapterOptions): () => void;
6
+ export type { AdapterOptions, VoiceWireFactory } from './session.ts';
7
+ export type { VoiceSocketConstructor, VoiceWire, VoiceWireTarget } from './socket.ts';
@@ -0,0 +1,15 @@
1
+ import type { AudioFormat } from '../audio.ts';
2
+ import type { RealtimeModel } from '../models.ts';
3
+ import type { RealtimeContext, RealtimeOptions, RealtimeSession } from '../realtime.ts';
4
+ import { type VoiceSocketConstructor, type VoiceWire, type VoiceWireTarget } from './socket.ts';
5
+ export type VoiceWireFactory = (target: VoiceWireTarget) => VoiceWire;
6
+ export interface AdapterOptions {
7
+ wire?: VoiceWireFactory;
8
+ socket?: VoiceSocketConstructor;
9
+ replace?: Record<string, string>;
10
+ }
11
+ export interface VendorSessionOptions extends AdapterOptions {
12
+ dialect: 'openai' | 'xai';
13
+ }
14
+ export declare function connectVendorSession(model: RealtimeModel<string>, context: RealtimeContext, options: RealtimeOptions | undefined, vendor: VendorSessionOptions): Promise<RealtimeSession>;
15
+ export declare function pcmFormat(sampleRate: number): AudioFormat;
@@ -0,0 +1,25 @@
1
+ export interface VoiceWireTarget {
2
+ url: string;
3
+ protocols: string[];
4
+ headers: Record<string, string>;
5
+ }
6
+ export interface VoiceWire {
7
+ send(data: string): void;
8
+ close(): void;
9
+ incoming: AsyncIterable<string>;
10
+ }
11
+ export interface VoiceSocket {
12
+ send(data: string): void;
13
+ close(): void;
14
+ addEventListener(type: 'message', listener: (event: {
15
+ data: unknown;
16
+ }) => void): void;
17
+ addEventListener(type: 'close', listener: () => void): void;
18
+ addEventListener(type: 'error', listener: () => void): void;
19
+ }
20
+ export type VoiceSocketInit = {
21
+ protocols: string[];
22
+ headers: Record<string, string>;
23
+ };
24
+ export type VoiceSocketConstructor = new (url: string, init: VoiceSocketInit) => VoiceSocket;
25
+ export declare function openVoiceSocket(target: VoiceWireTarget, Socket?: VoiceSocketConstructor): VoiceWire;
@@ -0,0 +1,4 @@
1
+ import type { RealtimeModel } from '../models.ts';
2
+ import { type AdapterOptions } from '../openai-realtime/session.ts';
3
+ export declare const grokVoiceLatest: RealtimeModel<'xai-realtime'>;
4
+ export declare function registerXaiRealtime(options?: AdapterOptions): () => void;
@@ -0,0 +1,51 @@
1
+ import { registerVoiceModel } from "../models.js";
2
+ import {
3
+ connectVendorSession,
4
+ pcmFormat
5
+ } from "../openai-realtime/session.js";
6
+ import { registerRealtimeApiProvider } from "../registry.js";
7
+ const voices = [
8
+ { id: "eve", name: "Eve" },
9
+ { id: "ara", name: "Ara" },
10
+ { id: "rex", name: "Rex" },
11
+ { id: "sal", name: "Sal" },
12
+ { id: "leo", name: "Leo" }
13
+ ];
14
+ const grokVoiceLatest = {
15
+ id: "grok-voice-latest",
16
+ name: "Grok Voice",
17
+ api: "xai-realtime",
18
+ provider: "xai",
19
+ baseUrl: "wss://api.x.ai/v1/realtime",
20
+ type: "realtime",
21
+ transports: ["websocket", "webrtc"],
22
+ toolCalls: "blocking",
23
+ inputFormat: pcmFormat(24e3),
24
+ outputFormat: pcmFormat(24e3),
25
+ maxSessionMs: 72e5,
26
+ resumable: true,
27
+ ephemeralAuth: true,
28
+ voices,
29
+ provisioning: "none",
30
+ delivery: { interrupt: "native", when_idle: "approximate", silent: "native" },
31
+ truncation: "client",
32
+ liveToolUpdate: true,
33
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
34
+ audioCost: { perInputMinute: 0.08, perOutputMinute: 0.08, perTextMessage: 4e-3 }
35
+ };
36
+ function registerXaiRealtime(options = {}) {
37
+ const unregisterModel = registerVoiceModel(grokVoiceLatest);
38
+ const unregisterApi = registerRealtimeApiProvider({
39
+ api: "xai-realtime",
40
+ connect: (model, context, connectOptions) => connectVendorSession(model, context, connectOptions, { ...options, dialect: "xai" })
41
+ });
42
+ return () => {
43
+ unregisterModel();
44
+ unregisterApi();
45
+ };
46
+ }
47
+ export {
48
+ grokVoiceLatest,
49
+ registerXaiRealtime
50
+ };
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/xai/index.ts"],
4
+ "sourcesContent": ["import type { RealtimeModel } from '../models.ts';\nimport { registerVoiceModel } from '../models.ts';\nimport {\n type AdapterOptions,\n connectVendorSession,\n pcmFormat,\n} from '../openai-realtime/session.ts';\nimport { registerRealtimeApiProvider } from '../registry.ts';\n\nconst voices = [\n { id: 'eve', name: 'Eve' },\n { id: 'ara', name: 'Ara' },\n { id: 'rex', name: 'Rex' },\n { id: 'sal', name: 'Sal' },\n { id: 'leo', name: 'Leo' },\n];\n\nexport const grokVoiceLatest: RealtimeModel<'xai-realtime'> = {\n id: 'grok-voice-latest',\n name: 'Grok Voice',\n api: 'xai-realtime',\n provider: 'xai',\n baseUrl: 'wss://api.x.ai/v1/realtime',\n type: 'realtime',\n transports: ['websocket', 'webrtc'],\n toolCalls: 'blocking',\n inputFormat: pcmFormat(24000),\n outputFormat: pcmFormat(24000),\n maxSessionMs: 7_200_000,\n resumable: true,\n ephemeralAuth: true,\n voices,\n provisioning: 'none',\n delivery: { interrupt: 'native', when_idle: 'approximate', silent: 'native' },\n truncation: 'client',\n liveToolUpdate: true,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n audioCost: { perInputMinute: 0.08, perOutputMinute: 0.08, perTextMessage: 0.004 },\n};\n\nexport function registerXaiRealtime(options: AdapterOptions = {}): () => void {\n const unregisterModel = registerVoiceModel(grokVoiceLatest);\n const unregisterApi = registerRealtimeApiProvider({\n api: 'xai-realtime',\n connect: (model, context, connectOptions) =>\n connectVendorSession(model, context, connectOptions, { ...options, dialect: 'xai' }),\n });\n return () => {\n unregisterModel();\n unregisterApi();\n };\n}\n"],
5
+ "mappings": "AACA,SAAS,0BAA0B;AACnC;AAAA,EAEE;AAAA,EACA;AAAA,OACK;AACP,SAAS,mCAAmC;AAE5C,MAAM,SAAS;AAAA,EACb,EAAE,IAAI,OAAO,MAAM,MAAM;AAAA,EACzB,EAAE,IAAI,OAAO,MAAM,MAAM;AAAA,EACzB,EAAE,IAAI,OAAO,MAAM,MAAM;AAAA,EACzB,EAAE,IAAI,OAAO,MAAM,MAAM;AAAA,EACzB,EAAE,IAAI,OAAO,MAAM,MAAM;AAC3B;AAEO,MAAM,kBAAiD;AAAA,EAC5D,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,KAAK;AAAA,EACL,UAAU;AAAA,EACV,SAAS;AAAA,EACT,MAAM;AAAA,EACN,YAAY,CAAC,aAAa,QAAQ;AAAA,EAClC,WAAW;AAAA,EACX,aAAa,UAAU,IAAK;AAAA,EAC5B,cAAc,UAAU,IAAK;AAAA,EAC7B,cAAc;AAAA,EACd,WAAW;AAAA,EACX,eAAe;AAAA,EACf;AAAA,EACA,cAAc;AAAA,EACd,UAAU,EAAE,WAAW,UAAU,WAAW,eAAe,QAAQ,SAAS;AAAA,EAC5E,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,MAAM,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,YAAY,EAAE;AAAA,EACzD,WAAW,EAAE,gBAAgB,MAAM,iBAAiB,MAAM,gBAAgB,KAAM;AAClF;AAEO,SAAS,oBAAoB,UAA0B,CAAC,GAAe;AAC5E,QAAM,kBAAkB,mBAAmB,eAAe;AAC1D,QAAM,gBAAgB,4BAA4B;AAAA,IAChD,KAAK;AAAA,IACL,SAAS,CAAC,OAAO,SAAS,mBACxB,qBAAqB,OAAO,SAAS,gBAAgB,EAAE,GAAG,SAAS,SAAS,MAAM,CAAC;AAAA,EACvF,CAAC;AACD,SAAO,MAAM;AACX,oBAAgB;AAChB,kBAAc;AAAA,EAChB;AACF;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/pi-audio",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -30,6 +30,10 @@
30
30
  "types": "./dist/types/faux/index.d.ts",
31
31
  "default": "./dist/faux/index.js"
32
32
  },
33
+ "./openai-realtime": {
34
+ "types": "./dist/types/openai-realtime/index.d.ts",
35
+ "default": "./dist/openai-realtime/index.js"
36
+ },
33
37
  "./worklet": {
34
38
  "types": "./dist/types/worklet/convert.d.ts",
35
39
  "default": "./dist/worklet/convert.js"
@@ -37,6 +41,10 @@
37
41
  "./openai": {
38
42
  "types": "./dist/types/openai/index.d.ts",
39
43
  "default": "./dist/openai/index.js"
44
+ },
45
+ "./xai": {
46
+ "types": "./dist/types/xai/index.d.ts",
47
+ "default": "./dist/xai/index.js"
40
48
  }
41
49
  },
42
50
  "files": [