@ai-ecoverse/pi-audio 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/echo.js +157 -0
- package/dist/echo.js.map +7 -0
- package/dist/gemini-live/session.js +32 -15
- package/dist/gemini-live/session.js.map +2 -2
- package/dist/index.js +16 -0
- package/dist/index.js.map +2 -2
- package/dist/openai-realtime/session.js +35 -9
- package/dist/openai-realtime/session.js.map +2 -2
- package/dist/openai-realtime/socket.js +12 -0
- package/dist/openai-realtime/socket.js.map +2 -2
- package/dist/talker.js +71 -27
- package/dist/talker.js.map +3 -3
- package/dist/turns.js +7 -1
- package/dist/turns.js.map +2 -2
- package/dist/types/echo.d.ts +29 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/openai-realtime/socket.d.ts +1 -0
- package/dist/types/talker.d.ts +1 -1
- package/dist/types/turns.d.ts +2 -0
- package/package.json +1 -1
|
@@ -51,6 +51,7 @@ function openaiSession(context, model) {
|
|
|
51
51
|
audio: {
|
|
52
52
|
input: {
|
|
53
53
|
format: { type: "audio/pcm", rate: model.inputFormat.sampleRate },
|
|
54
|
+
transcription: { model: "gpt-4o-mini-transcribe" },
|
|
54
55
|
turn_detection: { type: "server_vad" }
|
|
55
56
|
},
|
|
56
57
|
output: {
|
|
@@ -68,7 +69,10 @@ function xaiSession(context, model, replace) {
|
|
|
68
69
|
tools: context.tools.map(functionTool),
|
|
69
70
|
resumption: { enabled: true },
|
|
70
71
|
audio: {
|
|
71
|
-
input: {
|
|
72
|
+
input: {
|
|
73
|
+
format: { type: "audio/pcm", rate: model.inputFormat.sampleRate },
|
|
74
|
+
transcription: { model: "grok-transcribe" }
|
|
75
|
+
},
|
|
72
76
|
output: { format: { type: "audio/pcm", rate: model.outputFormat.sampleRate } }
|
|
73
77
|
}
|
|
74
78
|
};
|
|
@@ -179,6 +183,9 @@ class VendorSession {
|
|
|
179
183
|
#closed = false;
|
|
180
184
|
#usage;
|
|
181
185
|
#lastItem = "";
|
|
186
|
+
#busy = false;
|
|
187
|
+
#heldCreate = false;
|
|
188
|
+
#calls = /* @__PURE__ */ new Set();
|
|
182
189
|
constructor(model, context, dialect, replace, wire) {
|
|
183
190
|
this.model = model;
|
|
184
191
|
this.#context = context;
|
|
@@ -202,7 +209,7 @@ class VendorSession {
|
|
|
202
209
|
this.#assertOpen();
|
|
203
210
|
this.#ledger.addText();
|
|
204
211
|
this.#send(userItem(textValue));
|
|
205
|
-
this.#
|
|
212
|
+
this.#armCreate(false);
|
|
206
213
|
}
|
|
207
214
|
inject(textValue, delivery) {
|
|
208
215
|
this.#assertOpen();
|
|
@@ -216,7 +223,9 @@ class VendorSession {
|
|
|
216
223
|
this.#assertOpen();
|
|
217
224
|
const send = () => {
|
|
218
225
|
this.#send(toolOutput(callId, result));
|
|
219
|
-
|
|
226
|
+
this.#calls.delete(callId);
|
|
227
|
+
if (delivery !== "silent") this.#armCreate(true);
|
|
228
|
+
else if (this.#heldCreate && this.#calls.size === 0) this.#armCreate(false);
|
|
220
229
|
};
|
|
221
230
|
if (delivery === void 0) {
|
|
222
231
|
send();
|
|
@@ -294,7 +303,7 @@ class VendorSession {
|
|
|
294
303
|
return;
|
|
295
304
|
}
|
|
296
305
|
this.#send(userItem(textValue));
|
|
297
|
-
if (delivery !== "silent") this.#
|
|
306
|
+
if (delivery !== "silent") this.#armCreate(false);
|
|
298
307
|
}
|
|
299
308
|
#send(event) {
|
|
300
309
|
this.#wire.send(JSON.stringify(event));
|
|
@@ -329,12 +338,26 @@ class VendorSession {
|
|
|
329
338
|
timestamp: 0
|
|
330
339
|
});
|
|
331
340
|
}
|
|
332
|
-
#
|
|
341
|
+
#armCreate(waitForCalls) {
|
|
342
|
+
if (this.#busy || waitForCalls && this.#calls.size > 0) {
|
|
343
|
+
this.#heldCreate = true;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
this.#heldCreate = false;
|
|
347
|
+
this.#busy = true;
|
|
348
|
+
this.#send(responseCreate());
|
|
349
|
+
}
|
|
350
|
+
#releaseResponse() {
|
|
351
|
+
this.#busy = false;
|
|
352
|
+
if (this.#heldCreate && this.#calls.size === 0) this.#armCreate(false);
|
|
353
|
+
}
|
|
354
|
+
#transcript(itemId, value, final) {
|
|
333
355
|
const pending = this.#pendingFor(itemId);
|
|
334
|
-
const
|
|
335
|
-
const
|
|
356
|
+
const addition = final ? value.slice(pending.cursor) : value;
|
|
357
|
+
const end = pending.cursor + addition.length;
|
|
358
|
+
const tagged = addition.length === 0 ? void 0 : end;
|
|
336
359
|
for (const event of takeAudio(pending, itemId, this.#format, tagged)) this.#push(event);
|
|
337
|
-
this.#push({ type: "output_transcript", itemId, text:
|
|
360
|
+
this.#push({ type: "output_transcript", itemId, text: value, final });
|
|
338
361
|
}
|
|
339
362
|
#input(itemId, transcript, mode, final) {
|
|
340
363
|
const previous = this.#inputText.get(itemId) ?? "";
|
|
@@ -348,18 +371,21 @@ class VendorSession {
|
|
|
348
371
|
return text(object[key]);
|
|
349
372
|
}
|
|
350
373
|
#functionCall(event) {
|
|
374
|
+
const id = text(event.call_id);
|
|
375
|
+
if (id.length > 0) this.#calls.add(id);
|
|
351
376
|
this.#push({
|
|
352
377
|
type: "tool_call",
|
|
353
378
|
blocking: this.model.toolCalls === "blocking",
|
|
354
379
|
call: {
|
|
355
380
|
type: "toolCall",
|
|
356
|
-
id
|
|
381
|
+
id,
|
|
357
382
|
name: text(event.name),
|
|
358
383
|
arguments: argumentsOf(event.arguments)
|
|
359
384
|
}
|
|
360
385
|
});
|
|
361
386
|
}
|
|
362
387
|
#responseDone(event) {
|
|
388
|
+
this.#releaseResponse();
|
|
363
389
|
const response = record(event.response);
|
|
364
390
|
const output = response === void 0 ? void 0 : response.output;
|
|
365
391
|
const first = Array.isArray(output) ? record(output[0]) : void 0;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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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",
|
|
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 transcription: { model: 'gpt-4o-mini-transcribe' },\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: {\n format: { type: 'audio/pcm', rate: model.inputFormat.sampleRate },\n transcription: { model: 'grok-transcribe' },\n },\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 #busy = false;\n #heldCreate = false;\n readonly #calls = new Set<string>();\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.#armCreate(false);\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 this.#calls.delete(callId);\n if (delivery !== 'silent') this.#armCreate(true);\n else if (this.#heldCreate && this.#calls.size === 0) this.#armCreate(false);\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.#armCreate(false);\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 #armCreate(waitForCalls: boolean): void {\n if (this.#busy || (waitForCalls && this.#calls.size > 0)) {\n this.#heldCreate = true;\n return;\n }\n this.#heldCreate = false;\n this.#busy = true;\n this.#send(responseCreate());\n }\n\n #releaseResponse(): void {\n this.#busy = false;\n if (this.#heldCreate && this.#calls.size === 0) this.#armCreate(false);\n }\n\n #transcript(itemId: string, value: string, final: boolean): void {\n const pending = this.#pendingFor(itemId);\n const addition = final ? value.slice(pending.cursor) : value;\n const end = pending.cursor + addition.length;\n const tagged = addition.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: value, 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 const id = text(event.call_id);\n if (id.length > 0) this.#calls.add(id);\n this.#push({\n type: 'tool_call',\n blocking: this.model.toolCalls === 'blocking',\n call: {\n type: 'toolCall',\n id,\n name: text(event.name),\n arguments: argumentsOf(event.arguments),\n },\n });\n }\n\n #responseDone(event: Record<string, unknown>): void {\n this.#releaseResponse();\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
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,8 +4,10 @@ function openVoiceSocket(target, Socket = WebSocket) {
|
|
|
4
4
|
headers: target.headers
|
|
5
5
|
});
|
|
6
6
|
const queue = [];
|
|
7
|
+
const outgoing = [];
|
|
7
8
|
const waiters = [];
|
|
8
9
|
let closed = false;
|
|
10
|
+
let opened = false;
|
|
9
11
|
const push = (value) => {
|
|
10
12
|
if (closed) return;
|
|
11
13
|
const waiter = waiters.shift();
|
|
@@ -18,6 +20,11 @@ function openVoiceSocket(target, Socket = WebSocket) {
|
|
|
18
20
|
for (const waiter of waiters) waiter({ value: void 0, done: true });
|
|
19
21
|
waiters.length = 0;
|
|
20
22
|
};
|
|
23
|
+
socket.addEventListener("open", () => {
|
|
24
|
+
opened = true;
|
|
25
|
+
const queued = outgoing.splice(0);
|
|
26
|
+
for (const data of queued) socket.send(data);
|
|
27
|
+
});
|
|
21
28
|
socket.addEventListener("message", (event) => {
|
|
22
29
|
push(typeof event.data === "string" ? event.data : String(event.data));
|
|
23
30
|
});
|
|
@@ -25,6 +32,11 @@ function openVoiceSocket(target, Socket = WebSocket) {
|
|
|
25
32
|
socket.addEventListener("error", end);
|
|
26
33
|
return {
|
|
27
34
|
send(data) {
|
|
35
|
+
if (closed) return;
|
|
36
|
+
if (!opened) {
|
|
37
|
+
outgoing.push(data);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
28
40
|
socket.send(data);
|
|
29
41
|
},
|
|
30
42
|
close() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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": "
|
|
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: 'open', listener: () => void): 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 outgoing: string[] = [];\n const waiters: Array<(result: IteratorResult<string>) => void> = [];\n let closed = false;\n let opened = 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('open', () => {\n opened = true;\n const queued = outgoing.splice(0);\n for (const data of queued) socket.send(data);\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 if (closed) return;\n if (!opened) {\n outgoing.push(data);\n return;\n }\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": "AA4BO,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,WAAqB,CAAC;AAC5B,QAAM,UAA2D,CAAC;AAClE,MAAI,SAAS;AACb,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,QAAQ,MAAM;AACpC,aAAS;AACT,UAAM,SAAS,SAAS,OAAO,CAAC;AAChC,eAAW,QAAQ,OAAQ,QAAO,KAAK,IAAI;AAAA,EAC7C,CAAC;AACD,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,UAAI,OAAQ;AACZ,UAAI,CAAC,QAAQ;AACX,iBAAS,KAAK,IAAI;AAClB;AAAA,MACF;AACA,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
6
|
"names": []
|
|
7
7
|
}
|
package/dist/talker.js
CHANGED
|
@@ -88,6 +88,16 @@ function capResult(result, maxChars) {
|
|
|
88
88
|
});
|
|
89
89
|
return { ...result, content };
|
|
90
90
|
}
|
|
91
|
+
function requestContext(call) {
|
|
92
|
+
const value = call.arguments.context;
|
|
93
|
+
return typeof value === "string" ? value : void 0;
|
|
94
|
+
}
|
|
95
|
+
function track(pending, promise) {
|
|
96
|
+
pending.add(promise);
|
|
97
|
+
void promise.finally(() => {
|
|
98
|
+
pending.delete(promise);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
91
101
|
function bindThinker(session, thinker, direct, options) {
|
|
92
102
|
const captions = new PushStream();
|
|
93
103
|
const phases = new PushStream();
|
|
@@ -102,42 +112,31 @@ function bindThinker(session, thinker, direct, options) {
|
|
|
102
112
|
async function run(session, thinker, direct, options, captions, phases) {
|
|
103
113
|
const heard = [];
|
|
104
114
|
const running = /* @__PURE__ */ new Map();
|
|
115
|
+
const pending = /* @__PURE__ */ new Set();
|
|
105
116
|
for await (const event of session) {
|
|
106
117
|
if (event.type === "input_transcript" && event.final) heard.push(`user: ${event.text}`);
|
|
107
118
|
if (event.type === "output_transcript" && event.final) heard.push(`talker: ${event.text}`);
|
|
108
119
|
if (event.type === "tool_call_cancelled") {
|
|
109
|
-
|
|
110
|
-
if (!job2) continue;
|
|
111
|
-
job2.abort.abort();
|
|
112
|
-
if (!job2.answered) {
|
|
113
|
-
job2.answered = true;
|
|
114
|
-
send(() => session.respond(event.callId, errorResult(job2.call, new Error("cancelled"))));
|
|
115
|
-
}
|
|
120
|
+
cancelJob(session, running, event.callId);
|
|
116
121
|
continue;
|
|
117
122
|
}
|
|
118
123
|
if (event.type !== "tool_call") continue;
|
|
119
124
|
const call = event.call;
|
|
120
125
|
if (call.name !== askAgent.name) {
|
|
121
|
-
phases
|
|
122
|
-
phase: "tool",
|
|
123
|
-
toolName: call.name,
|
|
124
|
-
toolCallId: call.id,
|
|
125
|
-
args: call.arguments
|
|
126
|
-
});
|
|
127
|
-
void direct(call, AbortSignal.timeout(options.directTimeoutMs)).then((result) => {
|
|
128
|
-
send(() => session.respond(call.id, capResult(result, options.directMaxChars)));
|
|
129
|
-
phases.push({ phase: "idle" });
|
|
130
|
-
}).catch((error) => {
|
|
131
|
-
send(() => session.respond(call.id, errorResult(call, error)));
|
|
132
|
-
phases.push({ phase: "idle" });
|
|
133
|
-
});
|
|
126
|
+
track(pending, runDirect(session, direct, options, phases, running, call));
|
|
134
127
|
continue;
|
|
135
128
|
}
|
|
136
|
-
const job = {
|
|
129
|
+
const job = {
|
|
130
|
+
abort: new AbortController(),
|
|
131
|
+
answered: false,
|
|
132
|
+
dropped: false,
|
|
133
|
+
call,
|
|
134
|
+
kind: "ask"
|
|
135
|
+
};
|
|
137
136
|
running.set(call.id, job);
|
|
138
137
|
phases.push({ phase: "thinking" });
|
|
139
138
|
if (event.blocking) send(() => session.respond(call.id, acknowledged(call, options.ack)));
|
|
140
|
-
|
|
139
|
+
const ask = followAsk(
|
|
141
140
|
session,
|
|
142
141
|
thinker,
|
|
143
142
|
options,
|
|
@@ -149,14 +148,52 @@ async function run(session, thinker, direct, options, captions, phases) {
|
|
|
149
148
|
event.blocking,
|
|
150
149
|
job
|
|
151
150
|
);
|
|
151
|
+
track(pending, ask);
|
|
152
152
|
}
|
|
153
|
+
for (const job of running.values()) {
|
|
154
|
+
job.dropped = true;
|
|
155
|
+
job.abort.abort();
|
|
156
|
+
}
|
|
157
|
+
await Promise.allSettled([...pending]);
|
|
158
|
+
}
|
|
159
|
+
function cancelJob(session, running, callId) {
|
|
160
|
+
const job = running.get(callId);
|
|
161
|
+
if (!job) return;
|
|
162
|
+
job.abort.abort();
|
|
163
|
+
job.dropped = true;
|
|
164
|
+
if (job.kind !== "ask" || job.answered) return;
|
|
165
|
+
job.answered = true;
|
|
166
|
+
send(() => session.respond(callId, errorResult(job.call, new Error("cancelled"))));
|
|
167
|
+
}
|
|
168
|
+
function runDirect(session, direct, options, phases, running, call) {
|
|
169
|
+
const abort = new AbortController();
|
|
170
|
+
const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(options.directTimeoutMs)]);
|
|
171
|
+
const job = { abort, answered: false, dropped: false, call, kind: "direct" };
|
|
172
|
+
running.set(call.id, job);
|
|
173
|
+
phases.push({
|
|
174
|
+
phase: "tool",
|
|
175
|
+
toolName: call.name,
|
|
176
|
+
toolCallId: call.id,
|
|
177
|
+
args: call.arguments
|
|
178
|
+
});
|
|
179
|
+
return direct(call, signal).then((result) => {
|
|
180
|
+
if (job.dropped) return;
|
|
181
|
+
send(() => session.respond(call.id, capResult(result, options.directMaxChars)));
|
|
182
|
+
}).catch((error) => {
|
|
183
|
+
if (job.dropped) return;
|
|
184
|
+
send(() => session.respond(call.id, errorResult(call, error)));
|
|
185
|
+
}).finally(() => {
|
|
186
|
+
running.delete(call.id);
|
|
187
|
+
phases.push({ phase: "idle" });
|
|
188
|
+
});
|
|
153
189
|
}
|
|
154
190
|
async function followAsk(session, thinker, options, captions, phases, running, heard, call, blocking, job) {
|
|
155
191
|
try {
|
|
156
192
|
for await (const update of thinker.ask(
|
|
157
193
|
String(call.arguments.request),
|
|
158
194
|
heard.slice(-heardLimit),
|
|
159
|
-
job.abort.signal
|
|
195
|
+
job.abort.signal,
|
|
196
|
+
requestContext(call)
|
|
160
197
|
)) {
|
|
161
198
|
if (job.abort.signal.aborted) break;
|
|
162
199
|
if (update.type === "progress") {
|
|
@@ -175,10 +212,17 @@ async function followAsk(session, thinker, options, captions, phases, running, h
|
|
|
175
212
|
else send(() => session.respond(call.id, update.message, options.result));
|
|
176
213
|
}
|
|
177
214
|
} catch (error) {
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
215
|
+
if (job.abort.signal.aborted || job.answered) return;
|
|
216
|
+
job.answered = true;
|
|
217
|
+
const message = errorResult(call, error);
|
|
218
|
+
captions.push({
|
|
219
|
+
type: "thinker_result",
|
|
220
|
+
callId: call.id,
|
|
221
|
+
summary: textOf(message),
|
|
222
|
+
isError: true
|
|
223
|
+
});
|
|
224
|
+
if (blocking) send(() => session.inject(textOf(message), options.result));
|
|
225
|
+
else send(() => session.respond(call.id, message));
|
|
182
226
|
} finally {
|
|
183
227
|
running.delete(call.id);
|
|
184
228
|
phases.push({ phase: "idle" });
|
package/dist/talker.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/talker.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n type JsonObject,\n type Tool,\n type ToolCall,\n type ToolResultMessage,\n Type,\n} from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery } from './models.ts';\nimport type { RealtimeSession } from './realtime.ts';\n\nconst heardLimit = 20;\n\nexport const talkerTools = ['ask_agent', 'read'] as const;\n\nexport const askAgent: Tool = {\n name: 'ask_agent',\n description:\n 'Hand a request to the coding agent. Use for anything that needs files, the browser, tools or more than a few seconds of thought.',\n parameters: Type.Object({\n request: Type.String(),\n context: Type.Optional(Type.String()),\n }),\n};\n\nexport type ThinkerUpdate =\n | { type: 'progress'; text: string }\n | { type: 'result'; message: ToolResultMessage };\n\nexport interface Thinker {\n ask(request: string
|
|
5
|
-
"mappings": "AAAA;AAAA,EAKE;AAAA,OACK;AACP,SAAS,yBAAyB;AAIlC,MAAM,aAAa;AAEZ,MAAM,cAAc,CAAC,aAAa,MAAM;AAExC,MAAM,WAAiB;AAAA,EAC5B,MAAM;AAAA,EACN,aACE;AAAA,EACF,YAAY,KAAK,OAAO;AAAA,IACtB,SAAS,KAAK,OAAO;AAAA,IACrB,SAAS,KAAK,SAAS,KAAK,OAAO,CAAC;AAAA,EACtC,CAAC;AACH;
|
|
6
|
-
"names": [
|
|
4
|
+
"sourcesContent": ["import {\n type JsonObject,\n type Tool,\n type ToolCall,\n type ToolResultMessage,\n Type,\n} from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery } from './models.ts';\nimport type { RealtimeSession } from './realtime.ts';\n\nconst heardLimit = 20;\n\nexport const talkerTools = ['ask_agent', 'read'] as const;\n\nexport const askAgent: Tool = {\n name: 'ask_agent',\n description:\n 'Hand a request to the coding agent. Use for anything that needs files, the browser, tools or more than a few seconds of thought.',\n parameters: Type.Object({\n request: Type.String(),\n context: Type.Optional(Type.String()),\n }),\n};\n\nexport type ThinkerUpdate =\n | { type: 'progress'; text: string }\n | { type: 'result'; message: ToolResultMessage };\n\nexport interface Thinker {\n ask(\n request: string,\n heard: string[],\n signal: AbortSignal,\n context?: string\n ): AsyncIterable<ThinkerUpdate>;\n}\n\nexport interface BindThinkerOptions {\n progress: Delivery;\n result: Delivery;\n ack: string;\n directTimeoutMs: number;\n directMaxChars: number;\n}\n\nexport type ThinkerCaption =\n | { type: 'thinker_progress'; callId: string; text: string }\n | { type: 'thinker_result'; callId: string; summary: string; isError: boolean };\n\nexport type ThinkerPhase =\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'idle' };\n\nexport interface BoundThinker {\n captions: AsyncIterable<ThinkerCaption>;\n phases: AsyncIterable<ThinkerPhase>;\n done: Promise<void>;\n}\n\nclass PushStream<T> implements AsyncIterable<T> {\n readonly #queue: T[] = [];\n readonly #waiters: Array<(result: { value?: T; done: boolean }) => void> = [];\n #closed = false;\n\n push(value: T): void {\n if (this.#closed) return;\n const waiter = this.#waiters.shift();\n if (waiter) waiter({ value, done: false });\n else this.#queue.push(value);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true });\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\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<{ value?: T; done: boolean }>((resolve) =>\n this.#waiters.push(resolve)\n );\n if (next.done || next.value === undefined) return;\n yield next.value;\n }\n }\n}\n\nfunction send(action: () => void): void {\n try {\n action();\n } catch (error) {\n if (!(error instanceof VoiceRequestError)) throw error;\n }\n}\n\nfunction textOf(message: ToolResultMessage): string {\n return message.content\n .filter((part) => part.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nfunction acknowledged(call: ToolCall, ack: string): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: ack }],\n isError: false,\n timestamp: Date.now(),\n };\n}\n\nfunction errorResult(call: ToolCall, error: unknown): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n timestamp: Date.now(),\n };\n}\n\nfunction capResult(result: ToolResultMessage, maxChars: number): ToolResultMessage {\n let left = maxChars;\n const content = result.content.map((part) => {\n if (part.type !== 'text') return part;\n if (left <= 0) return { ...part, text: '' };\n if (part.text.length <= left) {\n left -= part.text.length;\n return part;\n }\n const text = part.text.slice(0, left);\n left = 0;\n return { ...part, text };\n });\n return { ...result, content };\n}\n\ninterface AskJob {\n abort: AbortController;\n answered: boolean;\n dropped: boolean;\n call: ToolCall;\n kind: 'ask' | 'direct';\n}\n\nfunction requestContext(call: ToolCall): string | undefined {\n const value = call.arguments.context;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction track(pending: Set<Promise<void>>, promise: Promise<void>): void {\n pending.add(promise);\n void promise.finally(() => {\n pending.delete(promise);\n });\n}\n\nexport function bindThinker(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions\n): BoundThinker {\n const captions = new PushStream<ThinkerCaption>();\n const phases = new PushStream<ThinkerPhase>();\n const done = run(session, thinker, direct, options, captions, phases).finally(() => {\n captions.close();\n phases.close();\n captions.close();\n phases.close();\n });\n return { captions, phases, done };\n}\n\nasync function run(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>\n): Promise<void> {\n const heard: string[] = [];\n const running = new Map<string, AskJob>();\n const pending = new Set<Promise<void>>();\n for await (const event of session) {\n if (event.type === 'input_transcript' && event.final) heard.push(`user: ${event.text}`);\n if (event.type === 'output_transcript' && event.final) heard.push(`talker: ${event.text}`);\n if (event.type === 'tool_call_cancelled') {\n cancelJob(session, running, event.callId);\n continue;\n }\n if (event.type !== 'tool_call') continue;\n const call = event.call;\n if (call.name !== askAgent.name) {\n track(pending, runDirect(session, direct, options, phases, running, call));\n continue;\n }\n const job: AskJob = {\n abort: new AbortController(),\n answered: false,\n dropped: false,\n call,\n kind: 'ask',\n };\n running.set(call.id, job);\n phases.push({ phase: 'thinking' });\n if (event.blocking) send(() => session.respond(call.id, acknowledged(call, options.ack)));\n const ask = followAsk(\n session,\n thinker,\n options,\n captions,\n phases,\n running,\n heard,\n call,\n event.blocking,\n job\n );\n track(pending, ask);\n }\n for (const job of running.values()) {\n job.dropped = true;\n job.abort.abort();\n }\n await Promise.allSettled([...pending]);\n}\n\nfunction cancelJob(session: RealtimeSession, running: Map<string, AskJob>, callId: string): void {\n const job = running.get(callId);\n if (!job) return;\n job.abort.abort();\n job.dropped = true;\n if (job.kind !== 'ask' || job.answered) return;\n job.answered = true;\n send(() => session.respond(callId, errorResult(job.call, new Error('cancelled'))));\n}\n\nfunction runDirect(\n session: RealtimeSession,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions,\n phases: PushStream<ThinkerPhase>,\n running: Map<string, AskJob>,\n call: ToolCall\n): Promise<void> {\n const abort = new AbortController();\n const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(options.directTimeoutMs)]);\n const job: AskJob = { abort, answered: false, dropped: false, call, kind: 'direct' };\n running.set(call.id, job);\n phases.push({\n phase: 'tool',\n toolName: call.name,\n toolCallId: call.id,\n args: call.arguments,\n });\n return direct(call, signal)\n .then((result) => {\n if (job.dropped) return;\n send(() => session.respond(call.id, capResult(result, options.directMaxChars)));\n })\n .catch((error: unknown) => {\n if (job.dropped) return;\n send(() => session.respond(call.id, errorResult(call, error)));\n })\n .finally(() => {\n running.delete(call.id);\n phases.push({ phase: 'idle' });\n });\n}\n\nasync function followAsk(\n session: RealtimeSession,\n thinker: Thinker,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>,\n running: Map<string, AskJob>,\n heard: string[],\n call: ToolCall,\n blocking: boolean,\n job: AskJob\n): Promise<void> {\n try {\n for await (const update of thinker.ask(\n String(call.arguments.request),\n heard.slice(-heardLimit),\n job.abort.signal,\n requestContext(call)\n )) {\n if (job.abort.signal.aborted) break;\n if (update.type === 'progress') {\n captions.push({ type: 'thinker_progress', callId: call.id, text: update.text });\n send(() => session.inject(update.text, options.progress));\n continue;\n }\n job.answered = true;\n captions.push({\n type: 'thinker_result',\n callId: call.id,\n summary: textOf(update.message),\n isError: update.message.isError,\n });\n if (blocking) send(() => session.inject(textOf(update.message), options.result));\n else send(() => session.respond(call.id, update.message, options.result));\n }\n } catch (error) {\n if (job.abort.signal.aborted || job.answered) return;\n job.answered = true;\n const message = errorResult(call, error);\n captions.push({\n type: 'thinker_result',\n callId: call.id,\n summary: textOf(message),\n isError: true,\n });\n if (blocking) send(() => session.inject(textOf(message), options.result));\n else send(() => session.respond(call.id, message));\n } finally {\n running.delete(call.id);\n phases.push({ phase: 'idle' });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
7
|
}
|
package/dist/turns.js
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
chunkSeconds
|
|
3
3
|
} from "./audio.js";
|
|
4
4
|
import { spokenOffset, utteranceIndex } from "./captions.js";
|
|
5
|
+
import { openEchoMic } from "./echo.js";
|
|
5
6
|
import { VoiceRequestError } from "./errors.js";
|
|
6
7
|
import { pushQueue } from "./queue.js";
|
|
7
8
|
import {
|
|
@@ -18,7 +19,7 @@ function voiceTurns(models, conversation, mic, out, options, signal) {
|
|
|
18
19
|
const session = {
|
|
19
20
|
models,
|
|
20
21
|
conversation,
|
|
21
|
-
mic,
|
|
22
|
+
mic: heardMic(options, mic),
|
|
22
23
|
out: speechOf(out),
|
|
23
24
|
options,
|
|
24
25
|
signal,
|
|
@@ -323,6 +324,11 @@ function shouldBarge(mode, event) {
|
|
|
323
324
|
if (mode === "interrupt") return event.type === "speech_start";
|
|
324
325
|
return event.type === "partial" && event.text.trim().length > 0;
|
|
325
326
|
}
|
|
327
|
+
function heardMic(options, mic) {
|
|
328
|
+
const echo = options.echo;
|
|
329
|
+
if (echo === void 0) return mic;
|
|
330
|
+
return openEchoMic(echo, mic);
|
|
331
|
+
}
|
|
326
332
|
function speechOf(out) {
|
|
327
333
|
if (isAudioOut(out)) return out.speech;
|
|
328
334
|
return out;
|