@ai-ecoverse/pi-audio 1.10.0 → 1.12.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 +6 -0
- package/dist/gemini-live/index.js +71 -0
- package/dist/gemini-live/index.js.map +7 -0
- package/dist/gemini-live/session.js +599 -0
- package/dist/gemini-live/session.js.map +7 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +2 -2
- package/dist/tokens.js +200 -0
- package/dist/tokens.js.map +7 -0
- package/dist/types/gemini-live/index.d.ts +7 -0
- package/dist/types/gemini-live/session.d.ts +12 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/tokens.d.ts +11 -0
- package/dist/types/xai/catalog.d.ts +1 -0
- package/dist/types/xai/index.d.ts +1 -0
- package/dist/types/xai/listen.d.ts +4 -0
- package/dist/types/xai/speak.d.ts +4 -0
- package/dist/types/xai/transcribe.d.ts +4 -0
- package/dist/types/xai/wire.d.ts +20 -0
- package/dist/xai/catalog.js +133 -0
- package/dist/xai/catalog.js.map +7 -0
- package/dist/xai/index.js +2 -0
- package/dist/xai/index.js.map +2 -2
- package/dist/xai/listen.js +211 -0
- package/dist/xai/listen.js.map +7 -0
- package/dist/xai/speak.js +208 -0
- package/dist/xai/speak.js.map +7 -0
- package/dist/xai/transcribe.js +113 -0
- package/dist/xai/transcribe.js.map +7 -0
- package/dist/xai/wire.js +171 -0
- package/dist/xai/wire.js.map +7 -0
- package/package.json +5 -1
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
import { blankUsage } from "../blank-usage.js";
|
|
2
|
+
import { decodeAudioBase64, encodeAudioBase64 } from "../convert.js";
|
|
3
|
+
import { VoiceRequestError } from "../errors.js";
|
|
4
|
+
import {
|
|
5
|
+
openVoiceSocket
|
|
6
|
+
} from "../openai-realtime/socket.js";
|
|
7
|
+
import { planTruncation, RealtimeLedger, realtimeSocket } from "../realtime.js";
|
|
8
|
+
const scheduling = {
|
|
9
|
+
interrupt: "INTERRUPT",
|
|
10
|
+
when_idle: "WHEN_IDLE",
|
|
11
|
+
silent: "SILENT"
|
|
12
|
+
};
|
|
13
|
+
function record(value) {
|
|
14
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function text(value) {
|
|
18
|
+
return typeof value === "string" ? value : "";
|
|
19
|
+
}
|
|
20
|
+
function credential(options) {
|
|
21
|
+
const token = options?.token?.value;
|
|
22
|
+
if (typeof token === "string" && token.length > 0) return { secret: token, ephemeral: true };
|
|
23
|
+
const apiKey = options?.apiKey;
|
|
24
|
+
if (typeof apiKey === "string" && apiKey.length > 0) return { secret: apiKey, ephemeral: false };
|
|
25
|
+
throw new VoiceRequestError("error", "missing realtime credential");
|
|
26
|
+
}
|
|
27
|
+
function headersFor(options) {
|
|
28
|
+
const headers = {};
|
|
29
|
+
const extra = options?.headers;
|
|
30
|
+
if (extra === void 0) return headers;
|
|
31
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
32
|
+
if (value === null) delete headers[key];
|
|
33
|
+
else headers[key] = value;
|
|
34
|
+
}
|
|
35
|
+
return headers;
|
|
36
|
+
}
|
|
37
|
+
function sessionUrl(model, secret, ephemeral) {
|
|
38
|
+
const url = new URL(model.baseUrl);
|
|
39
|
+
if (ephemeral) url.searchParams.set("access_token", secret);
|
|
40
|
+
else url.searchParams.set("key", secret);
|
|
41
|
+
return url.toString();
|
|
42
|
+
}
|
|
43
|
+
function timeLeftMs(value) {
|
|
44
|
+
if (typeof value === "number" && Number.isFinite(value)) return Math.round(value * 1e3);
|
|
45
|
+
if (typeof value === "string") {
|
|
46
|
+
const matched = /^(\d+(?:\.\d+)?)s$/.exec(value);
|
|
47
|
+
if (matched !== null) return Math.round(Number(matched[1]) * 1e3);
|
|
48
|
+
const asNumber = Number(value);
|
|
49
|
+
if (Number.isFinite(asNumber)) return Math.round(asNumber * 1e3);
|
|
50
|
+
}
|
|
51
|
+
const duration = record(value);
|
|
52
|
+
if (duration !== void 0 && typeof duration.seconds === "number") {
|
|
53
|
+
const nanos = typeof duration.nanos === "number" ? duration.nanos : 0;
|
|
54
|
+
return Math.round(duration.seconds * 1e3 + nanos / 1e6);
|
|
55
|
+
}
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
function count(source, key) {
|
|
59
|
+
const value = source[key];
|
|
60
|
+
return typeof value === "number" ? value : 0;
|
|
61
|
+
}
|
|
62
|
+
function usageFrom(value) {
|
|
63
|
+
const usage = record(value);
|
|
64
|
+
if (usage === void 0) return blankUsage();
|
|
65
|
+
const input = count(usage, "promptTokenCount");
|
|
66
|
+
const response = count(usage, "responseTokenCount");
|
|
67
|
+
const candidates = count(usage, "candidatesTokenCount");
|
|
68
|
+
const output = response > 0 ? response : candidates;
|
|
69
|
+
return {
|
|
70
|
+
input,
|
|
71
|
+
output,
|
|
72
|
+
cacheRead: 0,
|
|
73
|
+
cacheWrite: 0,
|
|
74
|
+
totalTokens: input + output,
|
|
75
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function argumentsOf(value) {
|
|
79
|
+
if (typeof value === "string") {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(value);
|
|
82
|
+
const object2 = record(parsed);
|
|
83
|
+
if (object2 === void 0) return {};
|
|
84
|
+
return object2;
|
|
85
|
+
} catch {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const object = record(value);
|
|
90
|
+
if (object === void 0) return {};
|
|
91
|
+
return object;
|
|
92
|
+
}
|
|
93
|
+
function resultText(result) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
for (const part of result.content) {
|
|
96
|
+
if (part.type === "text") parts.push(part.text);
|
|
97
|
+
}
|
|
98
|
+
return parts.join("\n");
|
|
99
|
+
}
|
|
100
|
+
function historyTurns(messages) {
|
|
101
|
+
if (messages === void 0) return [];
|
|
102
|
+
const turns = [];
|
|
103
|
+
for (const message of messages) {
|
|
104
|
+
if (message.role !== "user" && message.role !== "assistant") continue;
|
|
105
|
+
const parts = [];
|
|
106
|
+
for (const part of message.content) {
|
|
107
|
+
if (typeof part === "string") {
|
|
108
|
+
parts.push({ text: part });
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (part.type === "text") parts.push({ text: part.text });
|
|
112
|
+
}
|
|
113
|
+
if (parts.length === 0) continue;
|
|
114
|
+
turns.push({ role: message.role === "assistant" ? "model" : "user", parts });
|
|
115
|
+
}
|
|
116
|
+
return turns;
|
|
117
|
+
}
|
|
118
|
+
function declaration(tool, model) {
|
|
119
|
+
const fn = {
|
|
120
|
+
name: tool.name,
|
|
121
|
+
description: tool.description,
|
|
122
|
+
parameters: tool.parameters
|
|
123
|
+
};
|
|
124
|
+
if (model.id === "gemini-live-2.5-flash-native-audio") fn.behavior = "NON_BLOCKING";
|
|
125
|
+
if (model.toolCalls === "blocking") fn.behavior = "BLOCKING";
|
|
126
|
+
return fn;
|
|
127
|
+
}
|
|
128
|
+
function transcription(model, vocabulary) {
|
|
129
|
+
if (model.id !== "gemini-3.8-live") return {};
|
|
130
|
+
if (vocabulary === void 0) return {};
|
|
131
|
+
return { customVocabulary: vocabulary };
|
|
132
|
+
}
|
|
133
|
+
function setupBody(model, context, handle, vocabulary, turns) {
|
|
134
|
+
const setup = {
|
|
135
|
+
model: `models/${model.id}`,
|
|
136
|
+
generationConfig: { responseModalities: ["AUDIO"] },
|
|
137
|
+
systemInstruction: { parts: [{ text: context.instructions }] },
|
|
138
|
+
speechConfig: {
|
|
139
|
+
voiceConfig: { prebuiltVoiceConfig: { voiceName: context.voice } }
|
|
140
|
+
},
|
|
141
|
+
inputAudioTranscription: transcription(model, vocabulary),
|
|
142
|
+
outputAudioTranscription: {},
|
|
143
|
+
contextWindowCompression: {
|
|
144
|
+
triggerTokens: 1e5,
|
|
145
|
+
slidingWindow: { targetTokens: 8e4 }
|
|
146
|
+
},
|
|
147
|
+
sessionResumption: handle === void 0 ? {} : { handle }
|
|
148
|
+
};
|
|
149
|
+
if (context.tools.length > 0) {
|
|
150
|
+
setup.tools = [{ functionDeclarations: context.tools.map((tool) => declaration(tool, model)) }];
|
|
151
|
+
}
|
|
152
|
+
if (turns.length > 0) setup.historyConfig = { initialHistoryInClientContent: true };
|
|
153
|
+
return setup;
|
|
154
|
+
}
|
|
155
|
+
function spanFor(itemId, start, end) {
|
|
156
|
+
return { itemId, start, end };
|
|
157
|
+
}
|
|
158
|
+
function takeAudio(pending, itemId, format, end) {
|
|
159
|
+
const chunks = pending.chunks;
|
|
160
|
+
pending.chunks = [];
|
|
161
|
+
if (chunks.length === 0) return [];
|
|
162
|
+
const start = pending.cursor;
|
|
163
|
+
if (end !== void 0) pending.cursor = end;
|
|
164
|
+
const width = end === void 0 ? 0 : end - start;
|
|
165
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.data.length, 0);
|
|
166
|
+
let used = 0;
|
|
167
|
+
let cursor = start;
|
|
168
|
+
return chunks.map((chunk, index) => {
|
|
169
|
+
const event = {
|
|
170
|
+
type: "output_audio",
|
|
171
|
+
itemId,
|
|
172
|
+
chunk: { data: chunk.data, format, timestamp: chunk.timestamp }
|
|
173
|
+
};
|
|
174
|
+
if (end === void 0) return event;
|
|
175
|
+
const last = index === chunks.length - 1;
|
|
176
|
+
const next = last ? end : start + Math.round((used + chunk.data.length) / total * width);
|
|
177
|
+
used += chunk.data.length;
|
|
178
|
+
event.span = spanFor(itemId, cursor, next);
|
|
179
|
+
cursor = next;
|
|
180
|
+
return event;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
class GeminiSession {
|
|
184
|
+
model;
|
|
185
|
+
#context;
|
|
186
|
+
#vocabulary;
|
|
187
|
+
#turns;
|
|
188
|
+
#open;
|
|
189
|
+
#target;
|
|
190
|
+
#ledger = new RealtimeLedger();
|
|
191
|
+
#queue = [];
|
|
192
|
+
#waiters = [];
|
|
193
|
+
#outgoing = [];
|
|
194
|
+
#format;
|
|
195
|
+
#wire;
|
|
196
|
+
#pending = { chunks: [], cursor: 0 };
|
|
197
|
+
#item = "g0";
|
|
198
|
+
#turn = 0;
|
|
199
|
+
#heard = "";
|
|
200
|
+
#spoken = "";
|
|
201
|
+
#handle;
|
|
202
|
+
#tokens = blankUsage();
|
|
203
|
+
#ready = false;
|
|
204
|
+
#historySent = false;
|
|
205
|
+
#closed = false;
|
|
206
|
+
#ended = false;
|
|
207
|
+
#meters;
|
|
208
|
+
constructor(model, context, vocabulary, open, target, wire) {
|
|
209
|
+
this.model = model;
|
|
210
|
+
this.#context = context;
|
|
211
|
+
this.#vocabulary = vocabulary;
|
|
212
|
+
this.#turns = historyTurns(context.history);
|
|
213
|
+
this.#open = open;
|
|
214
|
+
this.#target = target;
|
|
215
|
+
this.#wire = wire;
|
|
216
|
+
this.#format = model.outputFormat;
|
|
217
|
+
}
|
|
218
|
+
start(handle) {
|
|
219
|
+
if (handle !== void 0 && handle.length > 0) this.#handle = handle;
|
|
220
|
+
this.#wire.send(JSON.stringify({ setup: this.#setup(handle) }));
|
|
221
|
+
void this.#read(this.#wire);
|
|
222
|
+
}
|
|
223
|
+
#setup(handle) {
|
|
224
|
+
return setupBody(this.model, this.#context, handle, this.#vocabulary, this.#turns);
|
|
225
|
+
}
|
|
226
|
+
#assertOpen() {
|
|
227
|
+
if (this.#closed) throw new VoiceRequestError("error", "session is closed");
|
|
228
|
+
}
|
|
229
|
+
#send(event) {
|
|
230
|
+
if (!this.#ready) {
|
|
231
|
+
this.#outgoing.push(event);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.#wire.send(JSON.stringify(event));
|
|
235
|
+
}
|
|
236
|
+
#push(event) {
|
|
237
|
+
if (this.#closed) return;
|
|
238
|
+
this.#ledger.observe(event);
|
|
239
|
+
const waiter = this.#waiters.shift();
|
|
240
|
+
if (waiter !== void 0) waiter({ value: event, done: false });
|
|
241
|
+
else this.#queue.push(event);
|
|
242
|
+
}
|
|
243
|
+
#end() {
|
|
244
|
+
if (this.#ended) return;
|
|
245
|
+
this.#ended = true;
|
|
246
|
+
while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true });
|
|
247
|
+
}
|
|
248
|
+
appendAudio(chunk) {
|
|
249
|
+
this.#assertOpen();
|
|
250
|
+
this.#ledger.addInput(chunk);
|
|
251
|
+
this.#send({
|
|
252
|
+
realtimeInput: {
|
|
253
|
+
audio: {
|
|
254
|
+
data: encodeAudioBase64(chunk.data),
|
|
255
|
+
mimeType: `audio/pcm;rate=${String(chunk.format.sampleRate)}`
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
appendVideoFrame(jpeg) {
|
|
261
|
+
this.#assertOpen();
|
|
262
|
+
if (this.model.video === void 0) {
|
|
263
|
+
throw new VoiceRequestError("error", "video is unsupported");
|
|
264
|
+
}
|
|
265
|
+
this.#send({
|
|
266
|
+
realtimeInput: { video: { data: encodeAudioBase64(jpeg), mimeType: "image/jpeg" } }
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
sendText(value) {
|
|
270
|
+
this.#assertOpen();
|
|
271
|
+
this.#ledger.addText();
|
|
272
|
+
this.#send(this.#client(value, true));
|
|
273
|
+
}
|
|
274
|
+
inject(value, delivery) {
|
|
275
|
+
this.#assertOpen();
|
|
276
|
+
this.#ledger.submit(this.model.delivery, delivery, () => {
|
|
277
|
+
this.#assertOpen();
|
|
278
|
+
this.#ledger.addText();
|
|
279
|
+
if (delivery === "silent") this.#send(this.#client(value, false));
|
|
280
|
+
else if (delivery === "when_idle") this.#send({ realtimeInput: { text: value } });
|
|
281
|
+
else this.#send(this.#client(value, true));
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
respond(callId, result, delivery) {
|
|
285
|
+
this.#assertOpen();
|
|
286
|
+
const send = () => {
|
|
287
|
+
const mode = delivery === void 0 ? "interrupt" : delivery;
|
|
288
|
+
this.#send({
|
|
289
|
+
toolResponse: {
|
|
290
|
+
functionResponses: [
|
|
291
|
+
{
|
|
292
|
+
id: callId,
|
|
293
|
+
name: result.toolName,
|
|
294
|
+
scheduling: scheduling[mode],
|
|
295
|
+
response: {
|
|
296
|
+
status: result.isError ? "error" : "ok",
|
|
297
|
+
retryable: false,
|
|
298
|
+
message: resultText(result)
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
if (delivery === void 0) {
|
|
306
|
+
send();
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.#ledger.submit(this.model.delivery, delivery, () => {
|
|
310
|
+
this.#assertOpen();
|
|
311
|
+
send();
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
interrupt(heard) {
|
|
315
|
+
this.#assertOpen();
|
|
316
|
+
planTruncation(this.model.truncation, heard);
|
|
317
|
+
this.#ledger.settle();
|
|
318
|
+
}
|
|
319
|
+
update(patch) {
|
|
320
|
+
this.#assertOpen();
|
|
321
|
+
if (patch.tools !== void 0 && !this.model.liveToolUpdate) {
|
|
322
|
+
throw new VoiceRequestError("error", "live tool update is unsupported");
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async close() {
|
|
326
|
+
if (this.#meters !== void 0) return { usage: this.#meters };
|
|
327
|
+
this.#closed = true;
|
|
328
|
+
this.#ledger.settle();
|
|
329
|
+
this.#wire.close();
|
|
330
|
+
this.#end();
|
|
331
|
+
this.#meters = this.#ledger.usage(this.model.audioCost);
|
|
332
|
+
return { usage: this.#meters };
|
|
333
|
+
}
|
|
334
|
+
#client(value, turnComplete) {
|
|
335
|
+
return {
|
|
336
|
+
clientContent: {
|
|
337
|
+
turns: [{ role: "user", parts: [{ text: value }] }],
|
|
338
|
+
turnComplete
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
async *[Symbol.asyncIterator]() {
|
|
343
|
+
while (true) {
|
|
344
|
+
const queued = this.#queue.shift();
|
|
345
|
+
if (queued !== void 0) {
|
|
346
|
+
yield queued;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
if (this.#closed) return;
|
|
350
|
+
const next = await new Promise((resolve) => {
|
|
351
|
+
this.#waiters.push(resolve);
|
|
352
|
+
});
|
|
353
|
+
if (next.done === true || next.value === void 0) return;
|
|
354
|
+
yield next.value;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
#audio(encoded) {
|
|
358
|
+
if (encoded.length === 0) return;
|
|
359
|
+
this.#pending.chunks.push({
|
|
360
|
+
data: decodeAudioBase64(encoded),
|
|
361
|
+
format: this.#format,
|
|
362
|
+
timestamp: 0
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
#transcript(delta, final) {
|
|
366
|
+
const end = this.#pending.cursor + delta.length;
|
|
367
|
+
const tagged = delta.length === 0 ? void 0 : end;
|
|
368
|
+
for (const event of takeAudio(this.#pending, this.#item, this.#format, tagged)) {
|
|
369
|
+
this.#push(event);
|
|
370
|
+
}
|
|
371
|
+
this.#spoken += delta;
|
|
372
|
+
this.#push({ type: "output_transcript", itemId: this.#item, text: delta, final });
|
|
373
|
+
}
|
|
374
|
+
#input(transcript, final) {
|
|
375
|
+
if (transcript.length > 0) this.#heard = transcript;
|
|
376
|
+
this.#push({ type: "input_transcript", itemId: this.#item, text: this.#heard, final });
|
|
377
|
+
}
|
|
378
|
+
#finish(interrupted) {
|
|
379
|
+
for (const event of takeAudio(this.#pending, this.#item, this.#format, void 0)) {
|
|
380
|
+
this.#push(event);
|
|
381
|
+
}
|
|
382
|
+
if (this.#spoken.length > 0) {
|
|
383
|
+
this.#push({
|
|
384
|
+
type: "output_transcript",
|
|
385
|
+
itemId: this.#item,
|
|
386
|
+
text: this.#spoken,
|
|
387
|
+
final: true
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
if (this.#heard.length > 0) {
|
|
391
|
+
this.#push({ type: "input_transcript", itemId: this.#item, text: this.#heard, final: true });
|
|
392
|
+
}
|
|
393
|
+
this.#push({
|
|
394
|
+
type: "response_done",
|
|
395
|
+
itemId: this.#item,
|
|
396
|
+
interrupted,
|
|
397
|
+
usage: this.#tokens
|
|
398
|
+
});
|
|
399
|
+
this.#tokens = blankUsage();
|
|
400
|
+
this.#spoken = "";
|
|
401
|
+
this.#heard = "";
|
|
402
|
+
this.#turn += 1;
|
|
403
|
+
this.#item = `g${String(this.#turn)}`;
|
|
404
|
+
this.#pending = { chunks: [], cursor: 0 };
|
|
405
|
+
}
|
|
406
|
+
#onSetup(value) {
|
|
407
|
+
const done = record(value);
|
|
408
|
+
const sessionId = done === void 0 ? "" : text(done.sessionId);
|
|
409
|
+
this.#push({ type: "session_start", sessionId });
|
|
410
|
+
if (!this.#historySent && this.#turns.length > 0) {
|
|
411
|
+
this.#historySent = true;
|
|
412
|
+
this.#wire.send(
|
|
413
|
+
JSON.stringify({ clientContent: { turns: this.#turns, turnComplete: false } })
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
this.#ready = true;
|
|
417
|
+
const pending = this.#outgoing.splice(0);
|
|
418
|
+
for (const event of pending) this.#wire.send(JSON.stringify(event));
|
|
419
|
+
}
|
|
420
|
+
#onResume(value) {
|
|
421
|
+
const update = record(value);
|
|
422
|
+
if (update === void 0) return;
|
|
423
|
+
const handle = text(update.newHandle);
|
|
424
|
+
if (handle.length === 0 || update.resumable !== true) return;
|
|
425
|
+
this.#handle = handle;
|
|
426
|
+
this.#push({ type: "resumption", handle });
|
|
427
|
+
}
|
|
428
|
+
#onGoAway(value) {
|
|
429
|
+
const gone = record(value);
|
|
430
|
+
const left = gone === void 0 ? 0 : timeLeftMs(gone.timeLeft);
|
|
431
|
+
this.#push({ type: "go_away", timeLeftMs: left });
|
|
432
|
+
const handle = this.#handle;
|
|
433
|
+
if (handle === void 0) {
|
|
434
|
+
this.#push({ type: "error", error: "session ended", fatal: true });
|
|
435
|
+
return "stop";
|
|
436
|
+
}
|
|
437
|
+
const next = this.#open(this.#target);
|
|
438
|
+
this.#wire = next;
|
|
439
|
+
this.#ready = false;
|
|
440
|
+
next.send(JSON.stringify({ setup: this.#setup(handle) }));
|
|
441
|
+
return next;
|
|
442
|
+
}
|
|
443
|
+
#onToolCall(value) {
|
|
444
|
+
const call = record(value);
|
|
445
|
+
if (call === void 0 || !Array.isArray(call.functionCalls)) return;
|
|
446
|
+
for (const entry of call.functionCalls) {
|
|
447
|
+
const fn = record(entry);
|
|
448
|
+
if (fn === void 0) continue;
|
|
449
|
+
this.#push({
|
|
450
|
+
type: "tool_call",
|
|
451
|
+
blocking: this.model.toolCalls === "blocking",
|
|
452
|
+
call: {
|
|
453
|
+
type: "toolCall",
|
|
454
|
+
id: text(fn.id),
|
|
455
|
+
name: text(fn.name),
|
|
456
|
+
arguments: argumentsOf(fn.args)
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
#onCancel(value) {
|
|
462
|
+
const cancellation = record(value);
|
|
463
|
+
if (cancellation === void 0 || !Array.isArray(cancellation.ids)) return;
|
|
464
|
+
for (const id of cancellation.ids) {
|
|
465
|
+
if (typeof id !== "string") continue;
|
|
466
|
+
this.#push({ type: "tool_call_cancelled", callId: id });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
#onServer(value) {
|
|
470
|
+
const content = record(value);
|
|
471
|
+
if (content === void 0) return;
|
|
472
|
+
this.#modelTurn(content.modelTurn);
|
|
473
|
+
this.#caption(content.outputTranscription, "output");
|
|
474
|
+
this.#caption(content.inputTranscription, "input");
|
|
475
|
+
if (content.interrupted === true) {
|
|
476
|
+
this.#push({ type: "input_speech_start" });
|
|
477
|
+
this.#finish(true);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
if (content.turnComplete === true) this.#finish(false);
|
|
481
|
+
}
|
|
482
|
+
#modelTurn(value) {
|
|
483
|
+
const turn = record(value);
|
|
484
|
+
if (turn === void 0 || !Array.isArray(turn.parts)) return;
|
|
485
|
+
for (const part of turn.parts) {
|
|
486
|
+
const entry = record(part);
|
|
487
|
+
if (entry === void 0) continue;
|
|
488
|
+
const inline = record(entry.inlineData);
|
|
489
|
+
if (inline === void 0) continue;
|
|
490
|
+
if (!text(inline.mimeType).startsWith("audio/")) continue;
|
|
491
|
+
this.#audio(text(inline.data));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
#caption(value, kind) {
|
|
495
|
+
const caption = record(value);
|
|
496
|
+
if (caption === void 0) return;
|
|
497
|
+
if (kind === "input") this.#input(text(caption.text), false);
|
|
498
|
+
else this.#transcript(text(caption.text), false);
|
|
499
|
+
}
|
|
500
|
+
#onError(value) {
|
|
501
|
+
const detail = record(value);
|
|
502
|
+
if (detail === void 0) {
|
|
503
|
+
const message2 = text(value);
|
|
504
|
+
this.#push({
|
|
505
|
+
type: "error",
|
|
506
|
+
error: message2.length > 0 ? message2 : "realtime error",
|
|
507
|
+
fatal: false
|
|
508
|
+
});
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const code = text(detail.code);
|
|
512
|
+
const message = text(detail.message);
|
|
513
|
+
let fatal = false;
|
|
514
|
+
if (code === "MAX_DURATION" || code === "SESSION_EXPIRED") fatal = true;
|
|
515
|
+
this.#push({
|
|
516
|
+
type: "error",
|
|
517
|
+
error: message.length > 0 ? message : "realtime error",
|
|
518
|
+
fatal
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
#apply(event) {
|
|
522
|
+
if (event.usageMetadata !== void 0) this.#tokens = usageFrom(event.usageMetadata);
|
|
523
|
+
if (event.inputTranscription !== void 0) this.#caption(event.inputTranscription, "input");
|
|
524
|
+
if (event.outputTranscription !== void 0) this.#caption(event.outputTranscription, "output");
|
|
525
|
+
if (event.setupComplete !== void 0) this.#onSetup(event.setupComplete);
|
|
526
|
+
if (event.sessionResumptionUpdate !== void 0) this.#onResume(event.sessionResumptionUpdate);
|
|
527
|
+
if (event.toolCall !== void 0) this.#onToolCall(event.toolCall);
|
|
528
|
+
if (event.toolCallCancellation !== void 0) this.#onCancel(event.toolCallCancellation);
|
|
529
|
+
if (event.serverContent !== void 0) this.#onServer(event.serverContent);
|
|
530
|
+
if (event.error !== void 0) this.#onError(event.error);
|
|
531
|
+
if (event.goAway !== void 0) return this.#onGoAway(event.goAway);
|
|
532
|
+
return "ok";
|
|
533
|
+
}
|
|
534
|
+
#ingest(raw) {
|
|
535
|
+
let parsed;
|
|
536
|
+
try {
|
|
537
|
+
parsed = JSON.parse(raw);
|
|
538
|
+
} catch {
|
|
539
|
+
this.#push({ type: "error", error: "invalid realtime event", fatal: false });
|
|
540
|
+
return "ok";
|
|
541
|
+
}
|
|
542
|
+
const event = record(parsed);
|
|
543
|
+
if (event === void 0) return "ok";
|
|
544
|
+
return this.#apply(event);
|
|
545
|
+
}
|
|
546
|
+
async #read(wire) {
|
|
547
|
+
let reconnect;
|
|
548
|
+
try {
|
|
549
|
+
for await (const raw of wire.incoming) {
|
|
550
|
+
const action = this.#ingest(raw);
|
|
551
|
+
if (action === "stop") return;
|
|
552
|
+
if (action !== "ok") {
|
|
553
|
+
reconnect = action;
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
559
|
+
this.#push({ type: "error", error: message, fatal: true });
|
|
560
|
+
} finally {
|
|
561
|
+
if (reconnect !== void 0) {
|
|
562
|
+
wire.close();
|
|
563
|
+
void this.#read(reconnect);
|
|
564
|
+
} else {
|
|
565
|
+
this.#closed = true;
|
|
566
|
+
this.#end();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async function connectGeminiSession(model, context, options, gemini) {
|
|
572
|
+
if (options?.signal?.aborted === true) {
|
|
573
|
+
throw new VoiceRequestError("aborted", "session aborted");
|
|
574
|
+
}
|
|
575
|
+
const { secret, ephemeral } = credential(options);
|
|
576
|
+
const socket = realtimeSocket(sessionUrl(model, secret, ephemeral), options, []);
|
|
577
|
+
const target = {
|
|
578
|
+
url: socket.url,
|
|
579
|
+
protocols: socket.protocols,
|
|
580
|
+
headers: headersFor(options)
|
|
581
|
+
};
|
|
582
|
+
const open = gemini.wire !== void 0 ? gemini.wire : (wireTarget) => openVoiceSocket(wireTarget, gemini.socket);
|
|
583
|
+
const session = new GeminiSession(model, context, gemini.vocabulary, open, target, open(target));
|
|
584
|
+
const handle = options?.resumeHandle;
|
|
585
|
+
session.start(handle);
|
|
586
|
+
const signal = options?.signal;
|
|
587
|
+
if (signal !== void 0) {
|
|
588
|
+
signal.addEventListener("abort", () => void session.close(), { once: true });
|
|
589
|
+
}
|
|
590
|
+
return session;
|
|
591
|
+
}
|
|
592
|
+
function pcmFormat(sampleRate) {
|
|
593
|
+
return { encoding: "pcm16", sampleRate, channels: 1 };
|
|
594
|
+
}
|
|
595
|
+
export {
|
|
596
|
+
connectGeminiSession,
|
|
597
|
+
pcmFormat
|
|
598
|
+
};
|
|
599
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/gemini-live/session.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JsonObject, Message, 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 {\n openVoiceSocket,\n type VoiceSocketConstructor,\n type VoiceWire,\n type VoiceWireTarget,\n} from '../openai-realtime/socket.ts';\nimport type {\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n} from '../realtime.ts';\nimport { planTruncation, RealtimeLedger, realtimeSocket } from '../realtime.ts';\n\nexport type VoiceWireFactory = (target: VoiceWireTarget) => VoiceWire;\n\nexport interface GeminiOptions {\n wire?: VoiceWireFactory;\n socket?: VoiceSocketConstructor;\n vocabulary?: string[];\n}\n\nconst scheduling: Record<Delivery, string> = {\n interrupt: 'INTERRUPT',\n when_idle: 'WHEN_IDLE',\n silent: 'SILENT',\n};\n\ninterface PendingAudio {\n chunks: AudioChunk[];\n cursor: number;\n}\n\ninterface IteratorResultEvent {\n value?: RealtimeEvent;\n done: boolean;\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): { secret: string; ephemeral: boolean } {\n const token = options?.token?.value;\n if (typeof token === 'string' && token.length > 0) return { secret: token, ephemeral: true };\n const apiKey = options?.apiKey;\n if (typeof apiKey === 'string' && apiKey.length > 0) return { secret: apiKey, ephemeral: false };\n throw new VoiceRequestError('error', 'missing realtime credential');\n}\n\nfunction headersFor(options: RealtimeOptions | undefined): Record<string, string> {\n const headers: Record<string, string> = {};\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>, secret: string, ephemeral: boolean): string {\n const url = new URL(model.baseUrl);\n if (ephemeral) url.searchParams.set('access_token', secret);\n else url.searchParams.set('key', secret);\n return url.toString();\n}\n\nfunction timeLeftMs(value: unknown): number {\n if (typeof value === 'number' && Number.isFinite(value)) return Math.round(value * 1000);\n if (typeof value === 'string') {\n const matched = /^(\\d+(?:\\.\\d+)?)s$/.exec(value);\n if (matched !== null) return Math.round(Number(matched[1]) * 1000);\n const asNumber = Number(value);\n if (Number.isFinite(asNumber)) return Math.round(asNumber * 1000);\n }\n const duration = record(value);\n if (duration !== undefined && typeof duration.seconds === 'number') {\n const nanos = typeof duration.nanos === 'number' ? duration.nanos : 0;\n return Math.round(duration.seconds * 1000 + nanos / 1e6);\n }\n return 0;\n}\n\nfunction count(source: Record<string, unknown>, key: string): number {\n const value = source[key];\n return typeof value === 'number' ? value : 0;\n}\n\nfunction usageFrom(value: unknown): Usage {\n const usage = record(value);\n if (usage === undefined) return blankUsage();\n const input = count(usage, 'promptTokenCount');\n const response = count(usage, 'responseTokenCount');\n const candidates = count(usage, 'candidatesTokenCount');\n const output = response > 0 ? response : candidates;\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\nfunction argumentsOf(value: unknown): JsonObject {\n if (typeof value === 'string') {\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 const object = record(value);\n if (object === undefined) return {};\n return object as JsonObject;\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 historyTurns(messages: Message[] | undefined): JsonObject[] {\n if (messages === undefined) return [];\n const turns: JsonObject[] = [];\n for (const message of messages) {\n if (message.role !== 'user' && message.role !== 'assistant') continue;\n const parts: JsonObject[] = [];\n for (const part of message.content) {\n if (typeof part === 'string') {\n parts.push({ text: part });\n continue;\n }\n if (part.type === 'text') parts.push({ text: part.text });\n }\n if (parts.length === 0) continue;\n turns.push({ role: message.role === 'assistant' ? 'model' : 'user', parts });\n }\n return turns;\n}\n\nfunction declaration(tool: Tool, model: RealtimeModel<string>): JsonObject {\n const fn: JsonObject = {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown as JsonObject,\n };\n if (model.id === 'gemini-live-2.5-flash-native-audio') fn.behavior = 'NON_BLOCKING';\n if (model.toolCalls === 'blocking') fn.behavior = 'BLOCKING';\n return fn;\n}\n\nfunction transcription(model: RealtimeModel<string>, vocabulary: string[] | undefined): JsonObject {\n if (model.id !== 'gemini-3.8-live') return {};\n if (vocabulary === undefined) return {};\n return { customVocabulary: vocabulary };\n}\n\nfunction setupBody(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n handle: string | undefined,\n vocabulary: string[] | undefined,\n turns: JsonObject[]\n): JsonObject {\n const setup: JsonObject = {\n model: `models/${model.id}`,\n generationConfig: { responseModalities: ['AUDIO'] },\n systemInstruction: { parts: [{ text: context.instructions }] },\n speechConfig: {\n voiceConfig: { prebuiltVoiceConfig: { voiceName: context.voice } },\n },\n inputAudioTranscription: transcription(model, vocabulary),\n outputAudioTranscription: {},\n contextWindowCompression: {\n triggerTokens: 100_000,\n slidingWindow: { targetTokens: 80_000 },\n },\n sessionResumption: handle === undefined ? {} : { handle },\n };\n if (context.tools.length > 0) {\n setup.tools = [{ functionDeclarations: context.tools.map((tool) => declaration(tool, model)) }];\n }\n if (turns.length > 0) setup.historyConfig = { initialHistoryInClientContent: true };\n return setup;\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) 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\nclass GeminiSession implements RealtimeSession {\n readonly model: RealtimeModel<string>;\n readonly #context: RealtimeContext;\n readonly #vocabulary: string[] | undefined;\n readonly #turns: JsonObject[];\n readonly #open: VoiceWireFactory;\n readonly #target: VoiceWireTarget;\n readonly #ledger = new RealtimeLedger();\n readonly #queue: RealtimeEvent[] = [];\n readonly #waiters: Array<(result: IteratorResultEvent) => void> = [];\n readonly #outgoing: JsonObject[] = [];\n readonly #format: AudioFormat;\n #wire: VoiceWire;\n #pending: PendingAudio = { chunks: [], cursor: 0 };\n #item = 'g0';\n #turn = 0;\n #heard = '';\n #spoken = '';\n #handle: string | undefined;\n #tokens: Usage = blankUsage();\n #ready = false;\n #historySent = false;\n #closed = false;\n #ended = false;\n #meters: Usage | undefined;\n\n constructor(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n vocabulary: string[] | undefined,\n open: VoiceWireFactory,\n target: VoiceWireTarget,\n wire: VoiceWire\n ) {\n this.model = model;\n this.#context = context;\n this.#vocabulary = vocabulary;\n this.#turns = historyTurns(context.history);\n this.#open = open;\n this.#target = target;\n this.#wire = wire;\n this.#format = model.outputFormat;\n }\n\n start(handle: string | undefined): void {\n if (handle !== undefined && handle.length > 0) this.#handle = handle;\n this.#wire.send(JSON.stringify({ setup: this.#setup(handle) }));\n void this.#read(this.#wire);\n }\n\n #setup(handle: string | undefined): JsonObject {\n return setupBody(this.model, this.#context, handle, this.#vocabulary, this.#turns);\n }\n\n #assertOpen(): void {\n if (this.#closed) throw new VoiceRequestError('error', 'session is closed');\n }\n\n #send(event: JsonObject): void {\n if (!this.#ready) {\n this.#outgoing.push(event);\n return;\n }\n this.#wire.send(JSON.stringify(event));\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 if (this.#ended) return;\n this.#ended = true;\n while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true });\n }\n\n appendAudio(chunk: AudioChunk): void {\n this.#assertOpen();\n this.#ledger.addInput(chunk);\n this.#send({\n realtimeInput: {\n audio: {\n data: encodeAudioBase64(chunk.data),\n mimeType: `audio/pcm;rate=${String(chunk.format.sampleRate)}`,\n },\n },\n });\n }\n\n appendVideoFrame(jpeg: Uint8Array): void {\n this.#assertOpen();\n if (this.model.video === undefined) {\n throw new VoiceRequestError('error', 'video is unsupported');\n }\n this.#send({\n realtimeInput: { video: { data: encodeAudioBase64(jpeg), mimeType: 'image/jpeg' } },\n });\n }\n\n sendText(value: string): void {\n this.#assertOpen();\n this.#ledger.addText();\n this.#send(this.#client(value, true));\n }\n\n inject(value: string, delivery: Delivery): void {\n this.#assertOpen();\n this.#ledger.submit(this.model.delivery, delivery, () => {\n this.#assertOpen();\n this.#ledger.addText();\n if (delivery === 'silent') this.#send(this.#client(value, false));\n else if (delivery === 'when_idle') this.#send({ realtimeInput: { text: value } });\n else this.#send(this.#client(value, true));\n });\n }\n\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void {\n this.#assertOpen();\n const send = () => {\n const mode = delivery === undefined ? 'interrupt' : delivery;\n this.#send({\n toolResponse: {\n functionResponses: [\n {\n id: callId,\n name: result.toolName,\n scheduling: scheduling[mode],\n response: {\n status: result.isError ? 'error' : 'ok',\n retryable: false,\n message: resultText(result),\n },\n },\n ],\n },\n });\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 planTruncation(this.model.truncation, heard);\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 }\n\n async close(): Promise<{ usage: Usage }> {\n if (this.#meters !== undefined) return { usage: this.#meters };\n this.#closed = true;\n this.#ledger.settle();\n this.#wire.close();\n this.#end();\n this.#meters = this.#ledger.usage(this.model.audioCost);\n return { usage: this.#meters };\n }\n\n #client(value: string, turnComplete: boolean): JsonObject {\n return {\n clientContent: {\n turns: [{ role: 'user', parts: [{ text: value }] }],\n turnComplete,\n },\n };\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<IteratorResultEvent>((resolve) => {\n this.#waiters.push(resolve);\n });\n if (next.done === true || next.value === undefined) return;\n yield next.value;\n }\n }\n\n #audio(encoded: string): void {\n if (encoded.length === 0) return;\n this.#pending.chunks.push({\n data: decodeAudioBase64(encoded),\n format: this.#format,\n timestamp: 0,\n });\n }\n\n #transcript(delta: string, final: boolean): void {\n const end = this.#pending.cursor + delta.length;\n const tagged = delta.length === 0 ? undefined : end;\n for (const event of takeAudio(this.#pending, this.#item, this.#format, tagged)) {\n this.#push(event);\n }\n this.#spoken += delta;\n this.#push({ type: 'output_transcript', itemId: this.#item, text: delta, final });\n }\n\n #input(transcript: string, final: boolean): void {\n if (transcript.length > 0) this.#heard = transcript;\n this.#push({ type: 'input_transcript', itemId: this.#item, text: this.#heard, final });\n }\n\n #finish(interrupted: boolean): void {\n for (const event of takeAudio(this.#pending, this.#item, this.#format, undefined)) {\n this.#push(event);\n }\n if (this.#spoken.length > 0) {\n this.#push({\n type: 'output_transcript',\n itemId: this.#item,\n text: this.#spoken,\n final: true,\n });\n }\n if (this.#heard.length > 0) {\n this.#push({ type: 'input_transcript', itemId: this.#item, text: this.#heard, final: true });\n }\n this.#push({\n type: 'response_done',\n itemId: this.#item,\n interrupted,\n usage: this.#tokens,\n });\n this.#tokens = blankUsage();\n this.#spoken = '';\n this.#heard = '';\n this.#turn += 1;\n this.#item = `g${String(this.#turn)}`;\n this.#pending = { chunks: [], cursor: 0 };\n }\n\n #onSetup(value: unknown): void {\n const done = record(value);\n const sessionId = done === undefined ? '' : text(done.sessionId);\n this.#push({ type: 'session_start', sessionId });\n if (!this.#historySent && this.#turns.length > 0) {\n this.#historySent = true;\n this.#wire.send(\n JSON.stringify({ clientContent: { turns: this.#turns, turnComplete: false } })\n );\n }\n this.#ready = true;\n const pending = this.#outgoing.splice(0);\n for (const event of pending) this.#wire.send(JSON.stringify(event));\n }\n\n #onResume(value: unknown): void {\n const update = record(value);\n if (update === undefined) return;\n const handle = text(update.newHandle);\n if (handle.length === 0 || update.resumable !== true) return;\n this.#handle = handle;\n this.#push({ type: 'resumption', handle });\n }\n\n #onGoAway(value: unknown): 'stop' | VoiceWire {\n const gone = record(value);\n const left = gone === undefined ? 0 : timeLeftMs(gone.timeLeft);\n this.#push({ type: 'go_away', timeLeftMs: left });\n const handle = this.#handle;\n if (handle === undefined) {\n this.#push({ type: 'error', error: 'session ended', fatal: true });\n return 'stop';\n }\n const next = this.#open(this.#target);\n this.#wire = next;\n this.#ready = false;\n next.send(JSON.stringify({ setup: this.#setup(handle) }));\n return next;\n }\n\n #onToolCall(value: unknown): void {\n const call = record(value);\n if (call === undefined || !Array.isArray(call.functionCalls)) return;\n for (const entry of call.functionCalls) {\n const fn = record(entry);\n if (fn === undefined) continue;\n this.#push({\n type: 'tool_call',\n blocking: this.model.toolCalls === 'blocking',\n call: {\n type: 'toolCall',\n id: text(fn.id),\n name: text(fn.name),\n arguments: argumentsOf(fn.args),\n },\n });\n }\n }\n\n #onCancel(value: unknown): void {\n const cancellation = record(value);\n if (cancellation === undefined || !Array.isArray(cancellation.ids)) return;\n for (const id of cancellation.ids) {\n if (typeof id !== 'string') continue;\n this.#push({ type: 'tool_call_cancelled', callId: id });\n }\n }\n\n #onServer(value: unknown): void {\n const content = record(value);\n if (content === undefined) return;\n this.#modelTurn(content.modelTurn);\n this.#caption(content.outputTranscription, 'output');\n this.#caption(content.inputTranscription, 'input');\n if (content.interrupted === true) {\n this.#push({ type: 'input_speech_start' });\n this.#finish(true);\n return;\n }\n if (content.turnComplete === true) this.#finish(false);\n }\n\n #modelTurn(value: unknown): void {\n const turn = record(value);\n if (turn === undefined || !Array.isArray(turn.parts)) return;\n for (const part of turn.parts) {\n const entry = record(part);\n if (entry === undefined) continue;\n const inline = record(entry.inlineData);\n if (inline === undefined) continue;\n if (!text(inline.mimeType).startsWith('audio/')) continue;\n this.#audio(text(inline.data));\n }\n }\n\n #caption(value: unknown, kind: 'input' | 'output'): void {\n const caption = record(value);\n if (caption === undefined) return;\n if (kind === 'input') this.#input(text(caption.text), false);\n else this.#transcript(text(caption.text), false);\n }\n\n #onError(value: unknown): void {\n const detail = record(value);\n if (detail === undefined) {\n const message = text(value);\n this.#push({\n type: 'error',\n error: message.length > 0 ? message : 'realtime error',\n fatal: false,\n });\n return;\n }\n const code = text(detail.code);\n const message = text(detail.message);\n let fatal = false;\n if (code === 'MAX_DURATION' || code === 'SESSION_EXPIRED') fatal = true;\n this.#push({\n type: 'error',\n error: message.length > 0 ? message : 'realtime error',\n fatal,\n });\n }\n\n #apply(event: Record<string, unknown>): 'ok' | 'stop' | VoiceWire {\n if (event.usageMetadata !== undefined) this.#tokens = usageFrom(event.usageMetadata);\n if (event.inputTranscription !== undefined) this.#caption(event.inputTranscription, 'input');\n if (event.outputTranscription !== undefined) this.#caption(event.outputTranscription, 'output');\n if (event.setupComplete !== undefined) this.#onSetup(event.setupComplete);\n if (event.sessionResumptionUpdate !== undefined) this.#onResume(event.sessionResumptionUpdate);\n if (event.toolCall !== undefined) this.#onToolCall(event.toolCall);\n if (event.toolCallCancellation !== undefined) this.#onCancel(event.toolCallCancellation);\n if (event.serverContent !== undefined) this.#onServer(event.serverContent);\n if (event.error !== undefined) this.#onError(event.error);\n if (event.goAway !== undefined) return this.#onGoAway(event.goAway);\n return 'ok';\n }\n\n #ingest(raw: string): 'ok' | 'stop' | VoiceWire {\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 return 'ok';\n }\n const event = record(parsed);\n if (event === undefined) return 'ok';\n return this.#apply(event);\n }\n\n async #read(wire: VoiceWire): Promise<void> {\n let reconnect: VoiceWire | undefined;\n try {\n for await (const raw of wire.incoming) {\n const action = this.#ingest(raw);\n if (action === 'stop') return;\n if (action !== 'ok') {\n reconnect = action;\n return;\n }\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 if (reconnect !== undefined) {\n wire.close();\n void this.#read(reconnect);\n } else {\n this.#closed = true;\n this.#end();\n }\n }\n }\n}\n\nexport async function connectGeminiSession(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options: RealtimeOptions | undefined,\n gemini: GeminiOptions\n): Promise<RealtimeSession> {\n if (options?.signal?.aborted === true) {\n throw new VoiceRequestError('aborted', 'session aborted');\n }\n const { secret, ephemeral } = credential(options);\n const socket = realtimeSocket(sessionUrl(model, secret, ephemeral), options, []);\n const target: VoiceWireTarget = {\n url: socket.url,\n protocols: socket.protocols,\n headers: headersFor(options),\n };\n const open =\n gemini.wire !== undefined\n ? gemini.wire\n : (wireTarget: VoiceWireTarget) => openVoiceSocket(wireTarget, gemini.socket);\n const session = new GeminiSession(model, context, gemini.vocabulary, open, target, open(target));\n const handle = options?.resumeHandle;\n session.start(handle);\n const signal = options?.signal;\n if (signal !== undefined) {\n signal.addEventListener('abort', () => void session.close(), { once: true });\n }\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": ["object", "message"]
|
|
7
|
+
}
|