@ai-ecoverse/pi-audio 1.16.0 → 1.18.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 +4 -0
- package/dist/echo.js +157 -0
- package/dist/echo.js.map +7 -0
- package/dist/elevenlabs-agents/events.js +172 -0
- package/dist/elevenlabs-agents/events.js.map +7 -0
- package/dist/elevenlabs-agents/index.js +58 -0
- package/dist/elevenlabs-agents/index.js.map +7 -0
- package/dist/elevenlabs-agents/provision.js +167 -0
- package/dist/elevenlabs-agents/provision.js.map +7 -0
- package/dist/elevenlabs-agents/session.js +272 -0
- package/dist/elevenlabs-agents/session.js.map +7 -0
- package/dist/hosted-agent.js +35 -0
- package/dist/hosted-agent.js.map +7 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +2 -2
- package/dist/turns.js +7 -1
- package/dist/turns.js.map +2 -2
- package/dist/types/echo.d.ts +29 -0
- package/dist/types/elevenlabs-agents/events.d.ts +13 -0
- package/dist/types/elevenlabs-agents/index.d.ts +9 -0
- package/dist/types/elevenlabs-agents/provision.d.ts +11 -0
- package/dist/types/elevenlabs-agents/session.d.ts +16 -0
- package/dist/types/hosted-agent.d.ts +23 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/turns.d.ts +2 -0
- package/package.json +5 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/elevenlabs-agents/provision.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JsonObject, ProviderRequestOptions, Tool } from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from '../errors.ts';\nimport {\n type CustomLlm,\n type HostedAgent,\n type HostedAgentSpec,\n type HostedCache,\n hashHostedSpec,\n hostedCacheKey,\n memoryHostedCache,\n} from '../hosted-agent.ts';\nimport type { RealtimeModel } from '../models.ts';\n\nexport interface ProvisionOptions extends ProviderRequestOptions {\n agentId?: string;\n now?: () => number;\n cache?: HostedCache;\n}\n\nconst defaultCache = memoryHostedCache();\n\nconst clientEvents = [\n 'conversation_initiation_metadata',\n 'ping',\n 'audio',\n 'interruption',\n 'user_transcript',\n 'agent_response',\n 'agent_response_correction',\n 'client_tool_call',\n 'vad_score',\n 'agent_chat_response_part',\n 'agent_response_complete',\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asVoice(error: unknown): VoiceRequestError {\n if (error instanceof VoiceRequestError) return error;\n if (error instanceof Error && error.name === 'AbortError') {\n return new VoiceRequestError('aborted', 'session aborted');\n }\n if (error instanceof Error) return new VoiceRequestError('error', error.message);\n return new VoiceRequestError('error', String(error));\n}\n\nexport function httpsOrigin(baseUrl: string): string {\n const url = new URL(baseUrl);\n let protocol = url.protocol;\n if (protocol === 'wss:') protocol = 'https:';\n if (protocol === 'ws:') protocol = 'http:';\n return `${protocol}//${url.host}`;\n}\n\nfunction mergedHeaders(\n options: ProvisionOptions,\n defaults: Record<string, string>\n): Record<string, string> {\n const headers = { ...defaults };\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 apiKey(options: ProvisionOptions): string {\n const key = options.apiKey;\n if (typeof key === 'string' && key.length > 0) return key;\n throw new VoiceRequestError('error', 'missing realtime credential');\n}\n\nfunction timeoutSeconds(timeoutMs: number | undefined): number {\n if (timeoutMs === undefined) return 20;\n return Math.min(120, Math.max(1, Math.round(timeoutMs / 1000)));\n}\n\nfunction clientTool(tool: Tool, timeoutMs: number | undefined): JsonObject {\n return {\n type: 'client',\n name: tool.name,\n description: tool.description,\n expects_response: true,\n response_timeout_secs: timeoutSeconds(timeoutMs),\n parameters: tool.parameters as unknown as JsonObject,\n };\n}\n\nfunction customLlmFields(llm: CustomLlm): JsonObject {\n const custom: JsonObject = {\n url: llm.baseUrl,\n model_id: llm.id,\n api_type: llm.api === 'openai-responses' ? 'responses' : 'chat_completions',\n };\n const headers = llm.headers;\n if (headers !== undefined && Object.keys(headers).length > 0) custom.request_headers = headers;\n return { llm: 'custom-llm', custom_llm: custom };\n}\n\nfunction promptFields(spec: HostedAgentSpec): JsonObject {\n const llm = typeof spec.llm === 'string' ? { llm: spec.llm } : customLlmFields(spec.llm);\n return {\n prompt: spec.instructions,\n tools: spec.tools.map((tool) => clientTool(tool, spec.clientToolTimeoutMs)),\n ...llm,\n };\n}\n\nexport function agentBody(spec: HostedAgentSpec): JsonObject {\n const agent: JsonObject = { prompt: promptFields(spec) };\n if (spec.firstMessage !== undefined) agent.first_message = spec.firstMessage;\n return {\n name: spec.name,\n conversation_config: {\n agent,\n tts: { voice_id: spec.voice },\n conversation: { client_events: clientEvents },\n },\n platform_settings: {\n overrides: {\n conversation_config_override: {\n agent: { prompt: { prompt: true }, first_message: true },\n tts: { voice_id: true },\n },\n custom_llm_extra_body: true,\n },\n },\n };\n}\n\nasync function requestJson(\n fetchImpl: typeof fetch,\n url: string,\n init: RequestInit\n): Promise<Record<string, unknown>> {\n let response: Response;\n try {\n response = await fetchImpl(url, init);\n } catch (error) {\n throw asVoice(error);\n }\n if (!response.ok) {\n throw new VoiceRequestError('error', `agent request failed (${response.status})`);\n }\n let json: unknown;\n try {\n json = await response.json();\n } catch (error) {\n throw asVoice(error);\n }\n if (!isRecord(json)) throw new VoiceRequestError('error', 'agent response was not an object');\n return json;\n}\n\nfunction agentIdFrom(json: Record<string, unknown>, fallback: string | undefined): string {\n const id = json.agent_id;\n if (typeof id === 'string' && id.length > 0) return id;\n if (fallback !== undefined) return fallback;\n throw new VoiceRequestError('error', 'agent response had no agent_id');\n}\n\nexport async function provisionAgent(\n model: RealtimeModel<string>,\n spec: HostedAgentSpec,\n options: ProvisionOptions = {}\n): Promise<HostedAgent> {\n if (options.signal?.aborted === true) throw new VoiceRequestError('aborted', 'session aborted');\n const cache = options.cache ?? defaultCache;\n const now = options.now ?? Date.now;\n const key = hostedCacheKey(model.provider, spec.name);\n const hash = hashHostedSpec(spec);\n const cached = cache.get(key);\n if (cached !== undefined && cached.specHash === hash) return cached;\n const existing = cached?.agentId ?? options.agentId;\n const origin = httpsOrigin(model.baseUrl);\n const url =\n existing === undefined\n ? `${origin}/v1/convai/agents/create`\n : `${origin}/v1/convai/agents/${encodeURIComponent(existing)}`;\n const headers = mergedHeaders(options, {\n 'Content-Type': 'application/json',\n 'xi-api-key': apiKey(options),\n });\n const fetchImpl = options.fetch ?? globalThis.fetch;\n const json = await requestJson(fetchImpl, url, {\n method: existing === undefined ? 'POST' : 'PATCH',\n headers,\n body: JSON.stringify(agentBody(spec)),\n signal: options.signal,\n });\n const agent: HostedAgent = {\n agentId: agentIdFrom(json, existing),\n specHash: hash,\n publishedAt: now(),\n };\n cache.set(key, agent);\n return agent;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { encodeAudioBase64 } from "../convert.js";
|
|
2
|
+
import { VoiceRequestError } from "../errors.js";
|
|
3
|
+
import {
|
|
4
|
+
openVoiceSocket
|
|
5
|
+
} from "../openai-realtime/socket.js";
|
|
6
|
+
import { pushQueue } from "../queue.js";
|
|
7
|
+
import { planTruncation, RealtimeLedger, realtimeSocket } from "../realtime.js";
|
|
8
|
+
import { mintRealtimeToken } from "../tokens.js";
|
|
9
|
+
import { priceAudio } from "../usage.js";
|
|
10
|
+
import { ingestElevenLabs } from "./events.js";
|
|
11
|
+
import { provisionAgent } from "./provision.js";
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function resultText(result) {
|
|
16
|
+
const parts = [];
|
|
17
|
+
for (const part of result.content) {
|
|
18
|
+
if (part.type === "text") parts.push(part.text);
|
|
19
|
+
}
|
|
20
|
+
return parts.join("\n");
|
|
21
|
+
}
|
|
22
|
+
function injectMessage(value, delivery) {
|
|
23
|
+
if (delivery === "silent") {
|
|
24
|
+
return { type: "contextual_update", text: value, context_id: "pi-audio" };
|
|
25
|
+
}
|
|
26
|
+
const text = delivery === "when_idle" ? `[agent update] ${value}` : value;
|
|
27
|
+
return { type: "user_message", text };
|
|
28
|
+
}
|
|
29
|
+
function toolResult(callId, result) {
|
|
30
|
+
return {
|
|
31
|
+
type: "client_tool_result",
|
|
32
|
+
tool_call_id: callId,
|
|
33
|
+
result: resultText(result),
|
|
34
|
+
is_error: result.isError
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function talkerLlm(model) {
|
|
38
|
+
const cascade = model.cascade;
|
|
39
|
+
if (cascade === void 0) return "gemini-2.5-flash";
|
|
40
|
+
return cascade.llm;
|
|
41
|
+
}
|
|
42
|
+
function specFromContext(model, context) {
|
|
43
|
+
return {
|
|
44
|
+
name: model.name,
|
|
45
|
+
instructions: context.instructions,
|
|
46
|
+
tools: context.tools,
|
|
47
|
+
voice: context.voice,
|
|
48
|
+
llm: talkerLlm(model)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function hasKey(options) {
|
|
52
|
+
return typeof options?.apiKey === "string" && options.apiKey.length > 0;
|
|
53
|
+
}
|
|
54
|
+
function agentFromToken(value) {
|
|
55
|
+
if (!value.startsWith("ws")) return void 0;
|
|
56
|
+
const id = new URL(value).searchParams.get("agent_id");
|
|
57
|
+
return id === null ? void 0 : id;
|
|
58
|
+
}
|
|
59
|
+
function conversationUrl(model, agentId, token) {
|
|
60
|
+
const url = new URL(model.baseUrl);
|
|
61
|
+
url.searchParams.set("agent_id", agentId);
|
|
62
|
+
if (token !== void 0) url.searchParams.set("token", token);
|
|
63
|
+
return url.toString();
|
|
64
|
+
}
|
|
65
|
+
function socketHeaders(options) {
|
|
66
|
+
const headers = {};
|
|
67
|
+
const extra = options?.headers;
|
|
68
|
+
if (extra === void 0) return headers;
|
|
69
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
70
|
+
if (value === null) delete headers[key];
|
|
71
|
+
else headers[key] = value;
|
|
72
|
+
}
|
|
73
|
+
return headers;
|
|
74
|
+
}
|
|
75
|
+
function placeholder(agentId) {
|
|
76
|
+
return { agentId, specHash: "", publishedAt: 0 };
|
|
77
|
+
}
|
|
78
|
+
async function ensureAgent(model, context, options, settings) {
|
|
79
|
+
const token = options?.token?.value;
|
|
80
|
+
if (typeof token === "string" && token.length > 0) {
|
|
81
|
+
return placeholder(agentFromToken(token) ?? settings.agentId ?? model.id);
|
|
82
|
+
}
|
|
83
|
+
if (!hasKey(options)) return placeholder(settings.agentId ?? model.id);
|
|
84
|
+
const provision = {
|
|
85
|
+
...options,
|
|
86
|
+
agentId: settings.agentId,
|
|
87
|
+
cache: settings.cache,
|
|
88
|
+
now: settings.now
|
|
89
|
+
};
|
|
90
|
+
return provisionAgent(model, specFromContext(model, context), provision);
|
|
91
|
+
}
|
|
92
|
+
async function socketUrl(model, agentId, context, options) {
|
|
93
|
+
const token = options?.token?.value;
|
|
94
|
+
if (typeof token === "string" && token.startsWith("ws")) return token;
|
|
95
|
+
if (typeof token === "string" && token.length > 0) return conversationUrl(model, agentId, token);
|
|
96
|
+
if (!hasKey(options)) return conversationUrl(model, agentId);
|
|
97
|
+
const minted = await mintRealtimeToken(
|
|
98
|
+
model,
|
|
99
|
+
{
|
|
100
|
+
agentId,
|
|
101
|
+
instructions: context.instructions,
|
|
102
|
+
voice: context.voice,
|
|
103
|
+
tools: context.tools
|
|
104
|
+
},
|
|
105
|
+
options
|
|
106
|
+
);
|
|
107
|
+
return minted.value;
|
|
108
|
+
}
|
|
109
|
+
function initiation(context, firstMessage) {
|
|
110
|
+
const agent = { prompt: { prompt: context.instructions } };
|
|
111
|
+
if (firstMessage !== void 0) agent.first_message = firstMessage;
|
|
112
|
+
return {
|
|
113
|
+
type: "conversation_initiation_client_data",
|
|
114
|
+
conversation_config_override: { agent, tts: { voice_id: context.voice } },
|
|
115
|
+
dynamic_variables: {},
|
|
116
|
+
custom_llm_extra_body: {}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
class ElevenLabsSession {
|
|
120
|
+
model;
|
|
121
|
+
#context;
|
|
122
|
+
#wire;
|
|
123
|
+
#now;
|
|
124
|
+
#started;
|
|
125
|
+
#ledger = new RealtimeLedger();
|
|
126
|
+
#events = pushQueue();
|
|
127
|
+
#turn = { vad: false, dropAudio: false, lastInterruptId: -1 };
|
|
128
|
+
#closed = false;
|
|
129
|
+
#usage;
|
|
130
|
+
constructor(model, context, wire, now) {
|
|
131
|
+
this.model = model;
|
|
132
|
+
this.#context = context;
|
|
133
|
+
this.#wire = wire;
|
|
134
|
+
this.#now = now;
|
|
135
|
+
this.#started = now();
|
|
136
|
+
}
|
|
137
|
+
start(firstMessage) {
|
|
138
|
+
this.#send(initiation(this.#context, firstMessage));
|
|
139
|
+
void this.#read();
|
|
140
|
+
}
|
|
141
|
+
#assertOpen() {
|
|
142
|
+
if (this.#closed) throw new VoiceRequestError("error", "session is closed");
|
|
143
|
+
}
|
|
144
|
+
#send(event) {
|
|
145
|
+
this.#wire.send(JSON.stringify(event));
|
|
146
|
+
}
|
|
147
|
+
#emit(event) {
|
|
148
|
+
if (this.#closed) return;
|
|
149
|
+
this.#ledger.observe(event);
|
|
150
|
+
this.#events.push(event);
|
|
151
|
+
}
|
|
152
|
+
appendAudio(chunk) {
|
|
153
|
+
this.#assertOpen();
|
|
154
|
+
this.#ledger.addInput(chunk);
|
|
155
|
+
this.#send({ user_audio_chunk: encodeAudioBase64(chunk.data) });
|
|
156
|
+
}
|
|
157
|
+
sendText(value) {
|
|
158
|
+
this.#assertOpen();
|
|
159
|
+
this.#ledger.addText();
|
|
160
|
+
this.#send({ type: "user_message", text: value });
|
|
161
|
+
}
|
|
162
|
+
inject(value, delivery) {
|
|
163
|
+
this.#assertOpen();
|
|
164
|
+
this.#ledger.submit(this.model.delivery, delivery, () => {
|
|
165
|
+
this.#assertOpen();
|
|
166
|
+
this.#ledger.addText();
|
|
167
|
+
this.#send(injectMessage(value, delivery));
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
respond(callId, result, delivery) {
|
|
171
|
+
this.#assertOpen();
|
|
172
|
+
const send = () => {
|
|
173
|
+
this.#send(toolResult(callId, result));
|
|
174
|
+
};
|
|
175
|
+
if (delivery === void 0) {
|
|
176
|
+
send();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.#ledger.submit(this.model.delivery, delivery, () => {
|
|
180
|
+
this.#assertOpen();
|
|
181
|
+
send();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
interrupt(heard) {
|
|
185
|
+
this.#assertOpen();
|
|
186
|
+
planTruncation(this.model.truncation, heard);
|
|
187
|
+
this.#turn.dropAudio = true;
|
|
188
|
+
this.#ledger.settle();
|
|
189
|
+
}
|
|
190
|
+
update(patch) {
|
|
191
|
+
this.#assertOpen();
|
|
192
|
+
if (patch.tools !== void 0 && this.model.liveToolUpdate === false) {
|
|
193
|
+
throw new VoiceRequestError("error", "live tool update is unsupported");
|
|
194
|
+
}
|
|
195
|
+
if (patch.tools !== void 0) this.#context.tools = patch.tools;
|
|
196
|
+
if (patch.instructions !== void 0) {
|
|
197
|
+
this.#context.instructions = patch.instructions;
|
|
198
|
+
this.#send({
|
|
199
|
+
type: "contextual_update",
|
|
200
|
+
text: patch.instructions,
|
|
201
|
+
context_id: "pi-audio"
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (patch.voice !== void 0) this.#context.voice = patch.voice;
|
|
205
|
+
}
|
|
206
|
+
async close() {
|
|
207
|
+
if (this.#usage !== void 0) return { usage: this.#usage };
|
|
208
|
+
this.#ledger.settle();
|
|
209
|
+
this.#closed = true;
|
|
210
|
+
this.#wire.close();
|
|
211
|
+
this.#events.end();
|
|
212
|
+
const inputSeconds = Math.max(0, this.#now() - this.#started) / 1e3;
|
|
213
|
+
this.#usage = priceAudio(this.model.audioCost, {
|
|
214
|
+
inputSeconds,
|
|
215
|
+
outputSeconds: this.#ledger.outputSeconds,
|
|
216
|
+
textMessages: this.#ledger.textMessages
|
|
217
|
+
});
|
|
218
|
+
return { usage: this.#usage };
|
|
219
|
+
}
|
|
220
|
+
[Symbol.asyncIterator]() {
|
|
221
|
+
return this.#events.iterable[Symbol.asyncIterator]();
|
|
222
|
+
}
|
|
223
|
+
#onRaw(raw) {
|
|
224
|
+
let parsed;
|
|
225
|
+
try {
|
|
226
|
+
parsed = JSON.parse(raw);
|
|
227
|
+
} catch {
|
|
228
|
+
this.#emit({ type: "error", error: "invalid realtime event", fatal: false });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!isRecord(parsed)) return;
|
|
232
|
+
const ingested = ingestElevenLabs(this.#turn, parsed, this.model.outputFormat);
|
|
233
|
+
for (const reply of ingested.replies) this.#send(reply);
|
|
234
|
+
for (const event of ingested.events) this.#emit(event);
|
|
235
|
+
}
|
|
236
|
+
#fail(error) {
|
|
237
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
238
|
+
this.#emit({ type: "error", error: message, fatal: true });
|
|
239
|
+
}
|
|
240
|
+
async #read() {
|
|
241
|
+
try {
|
|
242
|
+
for await (const raw of this.#wire.incoming) this.#onRaw(raw);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
this.#fail(error);
|
|
245
|
+
} finally {
|
|
246
|
+
this.#closed = true;
|
|
247
|
+
this.#events.end();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function connectElevenLabs(model, context, options, settings = {}) {
|
|
252
|
+
if (options?.signal?.aborted === true) throw new VoiceRequestError("aborted", "session aborted");
|
|
253
|
+
const agent = await ensureAgent(model, context, options, settings);
|
|
254
|
+
const url = await socketUrl(model, agent.agentId, context, options);
|
|
255
|
+
const routed = realtimeSocket(url, options, []);
|
|
256
|
+
const target = {
|
|
257
|
+
url: routed.url,
|
|
258
|
+
protocols: routed.protocols,
|
|
259
|
+
headers: socketHeaders(options)
|
|
260
|
+
};
|
|
261
|
+
const open = settings.wire !== void 0 ? settings.wire : (wireTarget) => openVoiceSocket(wireTarget, settings.socket);
|
|
262
|
+
const now = settings.now !== void 0 ? settings.now : Date.now;
|
|
263
|
+
const session = new ElevenLabsSession(model, context, open(target), now);
|
|
264
|
+
session.start(settings.firstMessage);
|
|
265
|
+
return session;
|
|
266
|
+
}
|
|
267
|
+
export {
|
|
268
|
+
connectElevenLabs,
|
|
269
|
+
specFromContext,
|
|
270
|
+
talkerLlm
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/elevenlabs-agents/session.ts"],
|
|
4
|
+
"sourcesContent": ["import type { JsonObject, ToolResultMessage, Usage } from '@earendil-works/pi-ai';\nimport type { AudioChunk } from '../audio.ts';\nimport { encodeAudioBase64 } from '../convert.ts';\nimport { VoiceRequestError } from '../errors.ts';\nimport type { HostedAgent, HostedAgentSpec, HostedCache } from '../hosted-agent.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 { pushQueue } from '../queue.ts';\nimport type {\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n} from '../realtime.ts';\nimport { planTruncation, RealtimeLedger, realtimeSocket } from '../realtime.ts';\nimport { mintRealtimeToken } from '../tokens.ts';\nimport { priceAudio } from '../usage.ts';\nimport { ingestElevenLabs, type TalkTurn } from './events.ts';\nimport { type ProvisionOptions, provisionAgent } from './provision.ts';\n\nexport type VoiceWireFactory = (target: VoiceWireTarget) => VoiceWire;\n\nexport interface ElevenLabsOptions {\n wire?: VoiceWireFactory;\n socket?: VoiceSocketConstructor;\n cache?: HostedCache;\n agentId?: string;\n now?: () => number;\n firstMessage?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\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 injectMessage(value: string, delivery: Delivery): JsonObject {\n if (delivery === 'silent') {\n return { type: 'contextual_update', text: value, context_id: 'pi-audio' };\n }\n const text = delivery === 'when_idle' ? `[agent update] ${value}` : value;\n return { type: 'user_message', text };\n}\n\nfunction toolResult(callId: string, result: ToolResultMessage): JsonObject {\n return {\n type: 'client_tool_result',\n tool_call_id: callId,\n result: resultText(result),\n is_error: result.isError,\n };\n}\n\nexport function talkerLlm(model: RealtimeModel<string>): string {\n const cascade = model.cascade;\n if (cascade === undefined) return 'gemini-2.5-flash';\n return cascade.llm;\n}\n\nexport function specFromContext(\n model: RealtimeModel<string>,\n context: RealtimeContext\n): HostedAgentSpec {\n return {\n name: model.name,\n instructions: context.instructions,\n tools: context.tools,\n voice: context.voice,\n llm: talkerLlm(model),\n };\n}\n\nfunction hasKey(options: RealtimeOptions | undefined): boolean {\n return typeof options?.apiKey === 'string' && options.apiKey.length > 0;\n}\n\nfunction agentFromToken(value: string): string | undefined {\n if (!value.startsWith('ws')) return undefined;\n const id = new URL(value).searchParams.get('agent_id');\n return id === null ? undefined : id;\n}\n\nfunction conversationUrl(model: RealtimeModel<string>, agentId: string, token?: string): string {\n const url = new URL(model.baseUrl);\n url.searchParams.set('agent_id', agentId);\n if (token !== undefined) url.searchParams.set('token', token);\n return url.toString();\n}\n\nfunction socketHeaders(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 placeholder(agentId: string): HostedAgent {\n return { agentId, specHash: '', publishedAt: 0 };\n}\n\nasync function ensureAgent(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options: RealtimeOptions | undefined,\n settings: ElevenLabsOptions\n): Promise<HostedAgent> {\n const token = options?.token?.value;\n if (typeof token === 'string' && token.length > 0) {\n return placeholder(agentFromToken(token) ?? settings.agentId ?? model.id);\n }\n if (!hasKey(options)) return placeholder(settings.agentId ?? model.id);\n const provision: ProvisionOptions = {\n ...options,\n agentId: settings.agentId,\n cache: settings.cache,\n now: settings.now,\n };\n return provisionAgent(model, specFromContext(model, context), provision);\n}\n\nasync function socketUrl(\n model: RealtimeModel<string>,\n agentId: string,\n context: RealtimeContext,\n options: RealtimeOptions | undefined\n): Promise<string> {\n const token = options?.token?.value;\n if (typeof token === 'string' && token.startsWith('ws')) return token;\n if (typeof token === 'string' && token.length > 0) return conversationUrl(model, agentId, token);\n if (!hasKey(options)) return conversationUrl(model, agentId);\n const minted = await mintRealtimeToken(\n model,\n {\n agentId,\n instructions: context.instructions,\n voice: context.voice,\n tools: context.tools,\n },\n options\n );\n return minted.value;\n}\n\nfunction initiation(context: RealtimeContext, firstMessage: string | undefined): JsonObject {\n const agent: JsonObject = { prompt: { prompt: context.instructions } };\n if (firstMessage !== undefined) agent.first_message = firstMessage;\n return {\n type: 'conversation_initiation_client_data',\n conversation_config_override: { agent, tts: { voice_id: context.voice } },\n dynamic_variables: {},\n custom_llm_extra_body: {},\n };\n}\n\nclass ElevenLabsSession implements RealtimeSession {\n readonly model: RealtimeModel<string>;\n readonly #context: RealtimeContext;\n readonly #wire: VoiceWire;\n readonly #now: () => number;\n readonly #started: number;\n readonly #ledger = new RealtimeLedger();\n readonly #events = pushQueue<RealtimeEvent>();\n readonly #turn: TalkTurn = { vad: false, dropAudio: false, lastInterruptId: -1 };\n #closed = false;\n #usage: Usage | undefined;\n\n constructor(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n wire: VoiceWire,\n now: () => number\n ) {\n this.model = model;\n this.#context = context;\n this.#wire = wire;\n this.#now = now;\n this.#started = now();\n }\n\n start(firstMessage: string | undefined): void {\n this.#send(initiation(this.#context, firstMessage));\n void this.#read();\n }\n\n #assertOpen(): void {\n if (this.#closed) throw new VoiceRequestError('error', 'session is closed');\n }\n\n #send(event: JsonObject): void {\n this.#wire.send(JSON.stringify(event));\n }\n\n #emit(event: RealtimeEvent): void {\n if (this.#closed) return;\n this.#ledger.observe(event);\n this.#events.push(event);\n }\n\n appendAudio(chunk: AudioChunk): void {\n this.#assertOpen();\n this.#ledger.addInput(chunk);\n this.#send({ user_audio_chunk: encodeAudioBase64(chunk.data) });\n }\n\n sendText(value: string): void {\n this.#assertOpen();\n this.#ledger.addText();\n this.#send({ type: 'user_message', text: value });\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 this.#send(injectMessage(value, delivery));\n });\n }\n\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void {\n this.#assertOpen();\n const send = () => {\n this.#send(toolResult(callId, result));\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.#turn.dropAudio = true;\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 === false) {\n throw new VoiceRequestError('error', 'live tool update is unsupported');\n }\n if (patch.tools !== undefined) this.#context.tools = patch.tools;\n if (patch.instructions !== undefined) {\n this.#context.instructions = patch.instructions;\n this.#send({\n type: 'contextual_update',\n text: patch.instructions,\n context_id: 'pi-audio',\n });\n }\n if (patch.voice !== undefined) this.#context.voice = patch.voice;\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.#events.end();\n const inputSeconds = Math.max(0, this.#now() - this.#started) / 1000;\n this.#usage = priceAudio(this.model.audioCost, {\n inputSeconds,\n outputSeconds: this.#ledger.outputSeconds,\n textMessages: this.#ledger.textMessages,\n });\n return { usage: this.#usage };\n }\n\n [Symbol.asyncIterator](): AsyncIterator<RealtimeEvent> {\n return this.#events.iterable[Symbol.asyncIterator]();\n }\n\n #onRaw(raw: string): void {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n this.#emit({ type: 'error', error: 'invalid realtime event', fatal: false });\n return;\n }\n if (!isRecord(parsed)) return;\n const ingested = ingestElevenLabs(this.#turn, parsed, this.model.outputFormat);\n for (const reply of ingested.replies) this.#send(reply);\n for (const event of ingested.events) this.#emit(event);\n }\n\n #fail(error: unknown): void {\n const message = error instanceof Error ? error.message : String(error);\n this.#emit({ type: 'error', error: message, fatal: true });\n }\n\n async #read(): Promise<void> {\n try {\n for await (const raw of this.#wire.incoming) this.#onRaw(raw);\n } catch (error) {\n this.#fail(error);\n } finally {\n this.#closed = true;\n this.#events.end();\n }\n }\n}\n\nexport async function connectElevenLabs(\n model: RealtimeModel<string>,\n context: RealtimeContext,\n options: RealtimeOptions | undefined,\n settings: ElevenLabsOptions = {}\n): Promise<RealtimeSession> {\n if (options?.signal?.aborted === true) throw new VoiceRequestError('aborted', 'session aborted');\n const agent = await ensureAgent(model, context, options, settings);\n const url = await socketUrl(model, agent.agentId, context, options);\n const routed = realtimeSocket(url, options, []);\n const target: VoiceWireTarget = {\n url: routed.url,\n protocols: routed.protocols,\n headers: socketHeaders(options),\n };\n const open =\n settings.wire !== undefined\n ? settings.wire\n : (wireTarget: VoiceWireTarget) => openVoiceSocket(wireTarget, settings.socket);\n const now = settings.now !== undefined ? settings.now : Date.now;\n const session = new ElevenLabsSession(model, context, open(target), now);\n session.start(settings.firstMessage);\n return session;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
function memoryHostedCache() {
|
|
3
|
+
const agents = /* @__PURE__ */ new Map();
|
|
4
|
+
return {
|
|
5
|
+
get(key) {
|
|
6
|
+
return agents.get(key);
|
|
7
|
+
},
|
|
8
|
+
set(key, agent) {
|
|
9
|
+
agents.set(key, agent);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function hostedCacheKey(provider, name) {
|
|
14
|
+
return `${provider}\0${name}`;
|
|
15
|
+
}
|
|
16
|
+
function stable(value) {
|
|
17
|
+
if (Array.isArray(value)) return value.map(stable);
|
|
18
|
+
if (value === null || typeof value !== "object") return value;
|
|
19
|
+
const source = value;
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const key of Object.keys(source).sort()) {
|
|
22
|
+
const next = source[key];
|
|
23
|
+
if (next !== void 0) out[key] = stable(next);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
function hashHostedSpec(spec) {
|
|
28
|
+
return createHash("sha256").update(JSON.stringify(stable(spec))).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
hashHostedSpec,
|
|
32
|
+
hostedCacheKey,
|
|
33
|
+
memoryHostedCache
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=hosted-agent.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/hosted-agent.ts"],
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto';\nimport type { Model, Tool } from '@earendil-works/pi-ai';\n\nexport type CustomLlm = Pick<\n Model<'openai-completions' | 'openai-responses'>,\n 'id' | 'api' | 'baseUrl' | 'headers'\n>;\n\nexport interface HostedAgentSpec {\n name: string;\n instructions: string;\n tools: Tool[];\n voice: string;\n llm: string | CustomLlm;\n firstMessage?: string;\n clientToolTimeoutMs?: number;\n}\n\nexport interface HostedAgent {\n agentId: string;\n specHash: string;\n publishedAt: number;\n}\n\nexport interface HostedCache {\n get(key: string): HostedAgent | undefined;\n set(key: string, agent: HostedAgent): void;\n}\n\nexport function memoryHostedCache(): HostedCache {\n const agents = new Map<string, HostedAgent>();\n return {\n get(key) {\n return agents.get(key);\n },\n set(key, agent) {\n agents.set(key, agent);\n },\n };\n}\n\nexport function hostedCacheKey(provider: string, name: string): string {\n return `${provider}\\0${name}`;\n}\n\nfunction stable(value: unknown): unknown {\n if (Array.isArray(value)) return value.map(stable);\n if (value === null || typeof value !== 'object') return value;\n const source = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(source).sort()) {\n const next = source[key];\n if (next !== undefined) out[key] = stable(next);\n }\n return out;\n}\n\nexport function hashHostedSpec(spec: HostedAgentSpec): string {\n return createHash('sha256')\n .update(JSON.stringify(stable(spec)))\n .digest('hex');\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB;AA6BpB,SAAS,oBAAiC;AAC/C,QAAM,SAAS,oBAAI,IAAyB;AAC5C,SAAO;AAAA,IACL,IAAI,KAAK;AACP,aAAO,OAAO,IAAI,GAAG;AAAA,IACvB;AAAA,IACA,IAAI,KAAK,OAAO;AACd,aAAO,IAAI,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEO,SAAS,eAAe,UAAkB,MAAsB;AACrE,SAAO,GAAG,QAAQ,KAAK,IAAI;AAC7B;AAEA,SAAS,OAAO,OAAyB;AACvC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,MAAM;AACjD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,SAAS;AACf,QAAM,MAA+B,CAAC;AACtC,aAAW,OAAO,OAAO,KAAK,MAAM,EAAE,KAAK,GAAG;AAC5C,UAAM,OAAO,OAAO,GAAG;AACvB,QAAI,SAAS,OAAW,KAAI,GAAG,IAAI,OAAO,IAAI;AAAA,EAChD;AACA,SAAO;AACT;AAEO,SAAS,eAAe,MAA+B;AAC5D,SAAO,WAAW,QAAQ,EACvB,OAAO,KAAK,UAAU,OAAO,IAAI,CAAC,CAAC,EACnC,OAAO,KAAK;AACjB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,15 @@ import {
|
|
|
7
7
|
decodeAudioBase64,
|
|
8
8
|
encodeAudioBase64
|
|
9
9
|
} from "./convert.js";
|
|
10
|
+
import {
|
|
11
|
+
assertNodeEcho,
|
|
12
|
+
calibrateEcho,
|
|
13
|
+
chooseCapture,
|
|
14
|
+
echoRuntime,
|
|
15
|
+
measureEcho,
|
|
16
|
+
micOpen,
|
|
17
|
+
openEchoMic
|
|
18
|
+
} from "./echo.js";
|
|
10
19
|
import { VoiceRequestError } from "./errors.js";
|
|
11
20
|
import {
|
|
12
21
|
getVoiceModel,
|
|
@@ -38,20 +47,27 @@ export {
|
|
|
38
47
|
VoiceRequestError,
|
|
39
48
|
addAudioCounters,
|
|
40
49
|
askAgent,
|
|
50
|
+
assertNodeEcho,
|
|
41
51
|
bindThinker,
|
|
42
52
|
blankUsage,
|
|
53
|
+
calibrateEcho,
|
|
43
54
|
captionsFromHistory,
|
|
55
|
+
chooseCapture,
|
|
44
56
|
connectRealtime,
|
|
45
57
|
convertAudio,
|
|
46
58
|
convertAudioSource,
|
|
47
59
|
decodeAudioBase64,
|
|
60
|
+
echoRuntime,
|
|
48
61
|
encodeAudioBase64,
|
|
49
62
|
energyVad,
|
|
50
63
|
followBed,
|
|
51
64
|
getVoiceModel,
|
|
52
65
|
listVoiceModels,
|
|
53
66
|
listen,
|
|
67
|
+
measureEcho,
|
|
68
|
+
micOpen,
|
|
54
69
|
mintRealtimeToken,
|
|
70
|
+
openEchoMic,
|
|
55
71
|
planDelivery,
|
|
56
72
|
planTruncation,
|
|
57
73
|
priceAudio,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport type { CaptionHistoryEntry } from './captions.ts';\nexport { captionsFromHistory } from './captions.ts';\nexport {\n convertAudio,\n convertAudioSource,\n decodeAudioBase64,\n encodeAudioBase64,\n} from './convert.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n DeliveryPlan,\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeSocket,\n RealtimeToken,\n TruncationPlan,\n} from './realtime.ts';\nexport { planDelivery, planTruncation, RealtimeLedger, realtimeSocket } from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { AudioOut, BedChannel, Cue, CueSource, Soundscape } from './soundscape.ts';\nexport { followBed } from './soundscape.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type { VoiceAgentEvent, VoiceMessageChange } from './spoken.ts';\nexport type {\n BindThinkerOptions,\n BoundThinker,\n Thinker,\n ThinkerCaption,\n ThinkerPhase,\n ThinkerUpdate,\n} from './talker.ts';\nexport { askAgent, bindThinker, talkerTools } from './talker.ts';\nexport type { RealtimeConstraints } from './tokens.ts';\nexport { mintRealtimeToken } from './tokens.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n BargeIn,\n CaptionEvent,\n EchoRecommendation,\n VoiceConversation,\n VoiceCut,\n VoiceModels,\n VoicePhase,\n VoiceSession,\n VoiceTurnMeta,\n VoiceTurnOptions,\n} from './turns.ts';\nexport { voiceTurns } from './turns.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\nexport type { VadHook, VadUpdate } from './vad.ts';\nexport { energyVad } from './vad.ts';\n"],
|
|
5
|
-
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAE3B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYP,SAAS,cAAc,gBAAgB,gBAAgB,sBAAsB;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,iBAAiB;AAE1B,SAAS,yBAAyB;AAUlC,SAAS,UAAU,aAAa,mBAAmB;AAEnD,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;AAazC,SAAS,kBAAkB;AAS3B,SAAS,kBAAkB,kBAAkB;AAE7C,SAAS,iBAAiB;",
|
|
4
|
+
"sourcesContent": ["export type {\n AudioChunk,\n AudioEncoding,\n AudioFormat,\n AudioSink,\n AudioSource,\n AudioTag,\n CharAlignment,\n TextSpan,\n} from './audio.ts';\nexport { resolveVoiceAuth } from './auth.ts';\nexport { blankUsage } from './blank-usage.ts';\nexport type { CaptionHistoryEntry } from './captions.ts';\nexport { captionsFromHistory } from './captions.ts';\nexport {\n convertAudio,\n convertAudioSource,\n decodeAudioBase64,\n encodeAudioBase64,\n} from './convert.ts';\nexport type { EchoCanceller, EchoControl, EchoMode, EchoReport, MicOpen } from './echo.ts';\nexport {\n assertNodeEcho,\n calibrateEcho,\n chooseCapture,\n echoRuntime,\n measureEcho,\n micOpen,\n openEchoMic,\n} from './echo.ts';\nexport { VoiceRequestError } from './errors.ts';\nexport type {\n Delivery,\n DeliverySupport,\n KnownRealtimeApi,\n KnownSpeechApi,\n KnownTranscriptionApi,\n RealtimeApi,\n RealtimeModel,\n SpeechApi,\n SpeechModel,\n TranscriptionApi,\n TranscriptionModel,\n VoiceInfo,\n VoiceModel,\n VoiceModelType,\n} from './models.ts';\nexport {\n getVoiceModel,\n listVoiceModels,\n registerVoiceModel,\n} from './models.ts';\nexport type {\n DeliveryPlan,\n RealtimeApiProvider,\n RealtimeContext,\n RealtimeEvent,\n RealtimeOptions,\n RealtimeSession,\n RealtimeSocket,\n RealtimeToken,\n TruncationPlan,\n} from './realtime.ts';\nexport { planDelivery, planTruncation, RealtimeLedger, realtimeSocket } from './realtime.ts';\nexport {\n connectRealtime,\n listen,\n registerRealtimeApiProvider,\n registerSpeechApiProvider,\n registerTranscriptionApiProvider,\n speak,\n transcribe,\n} from './registry.ts';\nexport type { AudioOut, BedChannel, Cue, CueSource, Soundscape } from './soundscape.ts';\nexport { followBed } from './soundscape.ts';\nexport type { SpeechApiProvider, SpeechEvent, SpeechInput } from './speech.ts';\nexport { SpeechEventStream } from './speech.ts';\nexport type { VoiceAgentEvent, VoiceMessageChange } from './spoken.ts';\nexport type {\n BindThinkerOptions,\n BoundThinker,\n Thinker,\n ThinkerCaption,\n ThinkerPhase,\n ThinkerUpdate,\n} from './talker.ts';\nexport { askAgent, bindThinker, talkerTools } from './talker.ts';\nexport type { RealtimeConstraints } from './tokens.ts';\nexport { mintRealtimeToken } from './tokens.ts';\nexport type {\n AudioInput,\n ListenOptions,\n Transcript,\n TranscriptionApiProvider,\n TranscriptionEvent,\n TranscriptSegment,\n} from './transcription.ts';\nexport { TranscriptionEventStream } from './transcription.ts';\nexport type {\n BargeIn,\n CaptionEvent,\n EchoRecommendation,\n VoiceConversation,\n VoiceCut,\n VoiceModels,\n VoicePhase,\n VoiceSession,\n VoiceTurnMeta,\n VoiceTurnOptions,\n} from './turns.ts';\nexport { voiceTurns } from './turns.ts';\nexport type {\n AudioCost,\n AudioCounters,\n AudioUsage,\n RealtimeAudioCost,\n SpeechAudioCost,\n TranscriptionAudioCost,\n} from './usage.ts';\nexport { addAudioCounters, priceAudio } from './usage.ts';\nexport type { VadHook, VadUpdate } from './vad.ts';\nexport { energyVad } from './vad.ts';\n"],
|
|
5
|
+
"mappings": "AAUA,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAE3B,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAiBlC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAYP,SAAS,cAAc,gBAAgB,gBAAgB,sBAAsB;AAC7E;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,iBAAiB;AAE1B,SAAS,yBAAyB;AAUlC,SAAS,UAAU,aAAa,mBAAmB;AAEnD,SAAS,yBAAyB;AASlC,SAAS,gCAAgC;AAazC,SAAS,kBAAkB;AAS3B,SAAS,kBAAkB,kBAAkB;AAE7C,SAAS,iBAAiB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/turns.js
CHANGED
|
@@ -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;
|
package/dist/turns.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/turns.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n AssistantMessageEvent,\n JsonObject,\n ProviderRequestOptions,\n ToolCall,\n UserMessage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioSink,\n type AudioSource,\n type AudioTag,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { type SpokenClock, spokenOffset, utteranceIndex } from './captions.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel, TranscriptionModel } from './models.ts';\nimport { pushQueue } from './queue.ts';\nimport {\n type AudioOut,\n type BedFollower,\n followBed,\n isAudioOut,\n type Soundscape,\n} from './soundscape.ts';\nimport type { SpeechEvent, SpeechInput } from './speech.ts';\nimport {\n createSpokenState,\n type SpokenPiece,\n spokenPieces,\n type VoiceAgentEvent,\n} from './spoken.ts';\nimport type { ListenOptions, TranscriptionEvent } from './transcription.ts';\n\nexport interface VoiceTurnMeta {\n source: 'voice';\n transcriptionModel: string;\n confidence?: number;\n}\n\nexport interface VoiceCut {\n entryId: string;\n contentIndex: number;\n heardUpTo: number;\n heardMs: number;\n}\n\nexport interface VoiceConversation {\n prompt(\n message: UserMessage,\n meta: VoiceTurnMeta,\n signal: AbortSignal\n ): AsyncIterable<VoiceAgentEvent>;\n truncate(cut: VoiceCut): Promise<void>;\n}\n\nexport type BargeIn = 'interrupt' | 'onWords' | 'ignore' | 'auto';\n\nexport type EchoRecommendation = 'interrupt' | 'onWords' | 'headphones';\n\nexport interface VoiceTurnOptions {\n ears: TranscriptionModel<string>;\n mouth: SpeechModel<string>;\n voice: string;\n speakable?: (event: AssistantMessageEvent) => string | undefined;\n announce?: (call: ToolCall) => string | undefined;\n bargeIn?: BargeIn;\n echoRecommendation?: EchoRecommendation;\n soundscape?: Soundscape;\n}\n\nexport interface VoiceModels {\n listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n ): AsyncIterable<TranscriptionEvent>;\n speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n ): AsyncIterable<SpeechEvent>;\n}\n\nexport type VoicePhase =\n | { phase: 'listening' }\n | { phase: 'heard'; text: string }\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'retrying'; attempt: number; at: number }\n | { phase: 'compacting' }\n | { phase: 'speaking' }\n | { phase: 'idle' };\n\nexport type CaptionEvent =\n | { type: 'user_partial'; turnId: string; segmentId: string; text: string }\n | { type: 'user_final'; turnId: string; segmentId: string; text: string; confidence?: number }\n | { type: 'agent_generated'; turnId: string; utteranceId: string; text: string }\n | { type: 'agent_spoken'; turnId: string; utteranceId: string; upTo: number }\n | { type: 'agent_cut'; turnId: string; utteranceId: string; heardUpTo: number }\n | { type: 'announce'; turnId: string; text: string }\n | { type: 'thinker_progress'; turnId: string; callId: string; text: string }\n | { type: 'thinker_result'; turnId: string; callId: string; summary: string; isError: boolean };\n\nexport interface VoiceSession {\n phases: AsyncIterable<VoicePhase>;\n captions: AsyncIterable<CaptionEvent>;\n done: Promise<void>;\n}\n\ninterface SpokenMap {\n spokenStart: number;\n spokenEnd: number;\n contentIndex: number;\n start: number;\n end: number;\n entryId?: string;\n kind: SpokenPiece['kind'];\n}\n\ninterface ActiveTurn {\n controller: AbortController;\n unlink: () => void;\n done: Promise<void>;\n turnId: string;\n utteranceId: string;\n spokenUpTo: number;\n cut?: VoiceCut;\n}\n\nexport function voiceTurns(\n models: VoiceModels,\n conversation: VoiceConversation,\n mic: AudioSource,\n out: AudioSink | AudioOut,\n options: VoiceTurnOptions,\n signal: AbortSignal\n): VoiceSession {\n const phases = pushQueue<VoicePhase>();\n const captions = pushQueue<CaptionEvent>();\n const session: Session = {\n models,\n conversation,\n mic,\n out: speechOf(out),\n options,\n signal,\n phases,\n captions,\n maps: [],\n bed: attachBed(out, options.soundscape),\n };\n const done = runSession(session).finally(() => {\n phases.end();\n captions.end();\n });\n return { phases: phases.iterable, captions: captions.iterable, done };\n}\n\ninterface Session {\n models: VoiceModels;\n conversation: VoiceConversation;\n mic: AudioSource;\n out: AudioSink;\n options: VoiceTurnOptions;\n signal: AbortSignal;\n phases: ReturnType<typeof pushQueue<VoicePhase>>;\n captions: ReturnType<typeof pushQueue<CaptionEvent>>;\n maps: SpokenMap[];\n playing?: SpokenClock;\n bed: BedFollower;\n}\n\nasync function runSession(session: Session): Promise<void> {\n const mode = bargeMode(session.options);\n let active: ActiveTurn | undefined;\n let confidence: number | undefined;\n let reserved = 0;\n let openId = '';\n const openTurn = () => {\n if (!openId) {\n reserved += 1;\n openId = String(reserved);\n }\n return openId;\n };\n const stopPlayback = session.out.onPlayback((tag, offsetMs) => {\n if (!active) return;\n notePlayback(session, active, tag, offsetMs);\n });\n const setPhase = phaseSetter(session.phases, session.bed);\n try {\n if (session.signal.aborted) return;\n setPhase({ phase: 'listening' });\n for await (const event of session.models.listen(session.options.ears, session.mic, {\n signal: session.signal,\n })) {\n if (event.type === 'error') {\n if (event.reason === 'aborted' || session.signal.aborted) return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n const captionId =\n event.type === 'partial' || event.type === 'final' || event.type === 'turn_end'\n ? openTurn()\n : openId;\n confidence = noteUser(session, event, captionId, confidence);\n if (shouldBarge(mode, event)) active = await interrupt(session, active);\n if (event.type !== 'turn_end') continue;\n const turnId = openId || openTurn();\n openId = '';\n if (mode === 'ignore') active = await finishTurn(active);\n else active = await interrupt(session, active);\n setPhase({ phase: 'heard', text: event.text });\n const meta = turnMeta(session.options, confidence);\n confidence = undefined;\n active = startTurn(session, setPhase, turnId, event, meta);\n }\n } finally {\n if (session.signal.aborted && active) await interrupt(session, active);\n else if (active) await active.done;\n stopPlayback();\n setPhase({ phase: 'idle' });\n session.bed.stop();\n }\n}\n\nfunction turnMeta(options: VoiceTurnOptions, confidence: number | undefined): VoiceTurnMeta {\n return {\n source: 'voice',\n transcriptionModel: options.ears.id,\n ...(confidence === undefined ? {} : { confidence }),\n };\n}\n\nfunction noteUser(\n session: Session,\n event: TranscriptionEvent,\n turnId: string,\n confidence: number | undefined\n): number | undefined {\n if (event.type === 'partial') {\n session.captions.push({\n type: 'user_partial',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n });\n }\n if (event.type === 'final') {\n session.captions.push({\n type: 'user_final',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n ...(event.confidence === undefined ? {} : { confidence: event.confidence }),\n });\n return event.confidence ?? confidence;\n }\n if (event.type === 'turn_end') {\n session.captions.push({ type: 'user_final', turnId, segmentId: 'turn', text: event.text });\n }\n return confidence;\n}\n\nasync function finishTurn(active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n await active.done;\n return undefined;\n}\n\nfunction startTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n turnId: string,\n event: Extract<TranscriptionEvent, { type: 'turn_end' }>,\n meta: VoiceTurnMeta\n): ActiveTurn {\n const linked = linkSignal(session.signal);\n const active: ActiveTurn = {\n controller: linked.controller,\n unlink: linked.unlink,\n turnId,\n utteranceId: `${turnId}:agent`,\n spokenUpTo: 0,\n done: Promise.resolve(),\n };\n active.done = performTurn(session, setPhase, active, event.text, event.at, meta).finally(() => {\n linked.unlink();\n });\n return active;\n}\n\nasync function performTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n text: string,\n at: number,\n meta: VoiceTurnMeta\n): Promise<void> {\n const signal = active.controller.signal;\n try {\n setPhase({ phase: 'thinking' });\n const message: UserMessage = { role: 'user', content: text, timestamp: at };\n const feed = textFeed(signal);\n const maps: SpokenMap[] = [];\n session.maps = maps;\n const producing = readAgent(session, setPhase, active, message, meta, feed, maps);\n const audio = session.models.speak(\n session.options.mouth,\n { text: feed.iterable, voice: session.options.voice },\n { signal }\n );\n await Promise.all([producing, pipeSpeech(session, setPhase, active, audio, maps)]);\n if (!signal.aborted) setPhase({ phase: 'listening' });\n } catch (error) {\n if (signal.aborted || session.signal.aborted) return;\n throw error;\n }\n}\n\nasync function readAgent(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n message: UserMessage,\n meta: VoiceTurnMeta,\n feed: TextFeed,\n maps: SpokenMap[]\n): Promise<void> {\n const state = createSpokenState();\n let spokenChars = 0;\n let generated = '';\n try {\n for await (const event of session.conversation.prompt(\n message,\n meta,\n active.controller.signal\n )) {\n if (active.controller.signal.aborted) break;\n notePhase(setPhase, event);\n for (const piece of spokenPieces(event, state, session.options)) {\n spokenChars += piece.text.length;\n generated = rememberPiece(\n session,\n active,\n piece,\n maps,\n spokenChars,\n generated,\n state.entryId\n );\n feed.push(piece.text);\n }\n if (state.entryId) bindEntry(maps, state.entryId);\n }\n } finally {\n feed.end();\n }\n}\n\nfunction rememberPiece(\n session: Session,\n active: ActiveTurn,\n piece: SpokenPiece,\n maps: SpokenMap[],\n spokenEnd: number,\n generated: string,\n entryId: string | undefined\n): string {\n const bound = piece.entryId ?? entryId;\n maps.push({\n spokenStart: spokenEnd - piece.text.length,\n spokenEnd,\n contentIndex: piece.contentIndex,\n start: piece.start,\n end: piece.end,\n kind: piece.kind,\n ...(bound ? { entryId: bound } : {}),\n });\n if (piece.kind === 'announce') {\n session.captions.push({ type: 'announce', turnId: active.turnId, text: piece.text });\n return generated;\n }\n const text = generated + piece.text;\n session.captions.push({\n type: 'agent_generated',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n text,\n });\n return text;\n}\n\nasync function pipeSpeech(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n audio: AsyncIterable<SpeechEvent>,\n maps: SpokenMap[]\n): Promise<void> {\n for await (const event of audio) {\n if (active.controller.signal.aborted) return;\n if (event.type === 'error') {\n if (event.reason === 'aborted') return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n if (event.type !== 'audio') continue;\n setPhase({ phase: 'speaking' });\n const span = translateSpan(event.span, maps);\n const playing: SpokenClock = {\n start: event.span.start,\n end: event.span.end,\n durationMs: Math.round(chunkSeconds(event.chunk) * 1000),\n };\n if (event.alignment) playing.alignment = event.alignment;\n session.playing = playing;\n const tag: AudioTag = { span };\n if (event.alignment) tag.alignment = event.alignment;\n session.out.write(event.chunk, tag);\n }\n}\n\nfunction notePlayback(session: Session, active: ActiveTurn, tag: AudioTag, offsetMs: number): void {\n const playing = session.playing;\n if (playing) {\n const upTo = utteranceIndex(spokenOffset(playing, offsetMs), session.maps);\n if (upTo > active.spokenUpTo) {\n active.spokenUpTo = upTo;\n session.captions.push({\n type: 'agent_spoken',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n upTo,\n });\n }\n }\n if (!('entryId' in tag.span)) return;\n active.cut = {\n entryId: tag.span.entryId,\n contentIndex: tag.span.contentIndex,\n heardUpTo: tag.span.end,\n heardMs: session.out.playedMs,\n };\n}\n\nfunction translateSpan(span: TextSpan, maps: SpokenMap[]): TextSpan {\n if ('entryId' in span) return span;\n const map = answerMap(span, maps);\n if (!map?.entryId) return span;\n const spokenLen = map.spokenEnd - map.spokenStart;\n const sourceLen = map.end - map.start;\n const start = map.start + scale(Math.max(0, span.start - map.spokenStart), spokenLen, sourceLen);\n const end =\n map.start + scale(Math.min(spokenLen, span.end - map.spokenStart), spokenLen, sourceLen);\n return { entryId: map.entryId, contentIndex: map.contentIndex, start, end };\n}\n\nfunction answerMap(span: { start: number; end: number }, maps: SpokenMap[]): SpokenMap | undefined {\n for (let index = maps.length - 1; index >= 0; index -= 1) {\n const item = maps[index];\n if (item?.kind !== 'answer' || !item.entryId) continue;\n if (item.spokenStart < span.end && item.spokenEnd > span.start) return item;\n }\n return undefined;\n}\n\nfunction scale(offset: number, spokenLen: number, sourceLen: number): number {\n if (spokenLen <= 0) return 0;\n return Math.round((offset * sourceLen) / spokenLen);\n}\n\nfunction bindEntry(maps: SpokenMap[], entryId: string): void {\n for (const map of maps) {\n if (map.kind === 'answer' && map.entryId === undefined) map.entryId = entryId;\n }\n}\n\nfunction notePhase(setPhase: (phase: VoicePhase) => void, event: VoiceAgentEvent): void {\n if (event.type === 'run_start' || event.type === 'turn_start') setPhase({ phase: 'thinking' });\n if (event.type === 'compaction_start') setPhase({ phase: 'compacting' });\n if (event.type === 'auto_retry_start' && event.attempt !== undefined && event.at !== undefined) {\n setPhase({ phase: 'retrying', attempt: event.attempt, at: event.at });\n }\n if (event.type === 'tool_execution_start' && event.toolName && event.toolCallId) {\n setPhase({\n phase: 'tool',\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n args: event.args ?? {},\n });\n }\n}\n\nasync function interrupt(session: Session, active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n active.controller.abort();\n session.out.flush();\n await active.done;\n if (active.cut) {\n session.captions.push({\n type: 'agent_cut',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n heardUpTo: active.cut.heardUpTo,\n });\n await session.conversation.truncate(active.cut);\n }\n return undefined;\n}\n\nfunction bargeMode(options: VoiceTurnOptions): 'interrupt' | 'onWords' | 'ignore' {\n const mode = options.bargeIn ?? 'interrupt';\n if (mode !== 'auto') return mode;\n if (options.echoRecommendation === 'interrupt') return 'interrupt';\n if (options.echoRecommendation === 'headphones') return 'ignore';\n return 'onWords';\n}\n\nfunction shouldBarge(mode: 'interrupt' | 'onWords' | 'ignore', event: TranscriptionEvent): boolean {\n if (mode === 'ignore') return false;\n if (mode === 'interrupt') return event.type === 'speech_start';\n return event.type === 'partial' && event.text.trim().length > 0;\n}\n\nfunction speechOf(out: AudioSink | AudioOut): AudioSink {\n if (isAudioOut(out)) return out.speech;\n return out;\n}\n\nfunction attachBed(out: AudioSink | AudioOut, soundscape: Soundscape | undefined): BedFollower {\n if (!soundscape) return idleBed();\n if (!isAudioOut(out)) return idleBed();\n return followBed(out.bed, soundscape);\n}\n\nfunction idleBed(): BedFollower {\n return {\n observe() {},\n stop() {},\n };\n}\n\nfunction phaseSetter(phases: Session['phases'], bed: BedFollower): (phase: VoicePhase) => void {\n let previous = '';\n return (phase) => {\n const key = JSON.stringify(phase);\n if (key === previous) return;\n previous = key;\n phases.push(phase);\n bed.observe(phase);\n };\n}\n\nfunction linkSignal(parent: AbortSignal): { controller: AbortController; unlink: () => void } {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (parent.aborted) controller.abort();\n else parent.addEventListener('abort', abort);\n return {\n controller,\n unlink: () => parent.removeEventListener('abort', abort),\n };\n}\n\ninterface TextFeed {\n push(text: string): void;\n end(): void;\n iterable: AsyncIterable<string>;\n}\n\nfunction textFeed(signal: AbortSignal): TextFeed {\n const queue: string[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n const notify = () => {\n const current = wake;\n wake = undefined;\n current?.();\n };\n const onAbort = () => notify();\n signal.addEventListener('abort', onAbort);\n return {\n push(text) {\n if (ended || signal.aborted || text.length === 0) return;\n queue.push(text);\n notify();\n },\n end() {\n ended = true;\n notify();\n },\n iterable: {\n async *[Symbol.asyncIterator]() {\n try {\n while (!signal.aborted) {\n const next = queue.shift();\n if (next !== undefined) {\n yield next;\n continue;\n }\n if (ended) return;\n await new Promise<void>((resolve) => {\n if (queue.length > 0 || ended || signal.aborted) resolve();\n else wake = resolve;\n });\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n },\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "AAOA;AAAA,EAIE;AAAA,OAEK;AACP,SAA2B,cAAc,sBAAsB;AAC/D,SAAS,yBAAyB;AAElC,SAAS,iBAAiB;AAC1B;AAAA,EAGE;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EAEA;AAAA,OAEK;
|
|
4
|
+
"sourcesContent": ["import type {\n AssistantMessageEvent,\n JsonObject,\n ProviderRequestOptions,\n ToolCall,\n UserMessage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioSink,\n type AudioSource,\n type AudioTag,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { type SpokenClock, spokenOffset, utteranceIndex } from './captions.ts';\nimport { type EchoControl, openEchoMic } from './echo.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { SpeechModel, TranscriptionModel } from './models.ts';\nimport { pushQueue } from './queue.ts';\nimport {\n type AudioOut,\n type BedFollower,\n followBed,\n isAudioOut,\n type Soundscape,\n} from './soundscape.ts';\nimport type { SpeechEvent, SpeechInput } from './speech.ts';\nimport {\n createSpokenState,\n type SpokenPiece,\n spokenPieces,\n type VoiceAgentEvent,\n} from './spoken.ts';\nimport type { ListenOptions, TranscriptionEvent } from './transcription.ts';\n\nexport interface VoiceTurnMeta {\n source: 'voice';\n transcriptionModel: string;\n confidence?: number;\n}\n\nexport interface VoiceCut {\n entryId: string;\n contentIndex: number;\n heardUpTo: number;\n heardMs: number;\n}\n\nexport interface VoiceConversation {\n prompt(\n message: UserMessage,\n meta: VoiceTurnMeta,\n signal: AbortSignal\n ): AsyncIterable<VoiceAgentEvent>;\n truncate(cut: VoiceCut): Promise<void>;\n}\n\nexport type BargeIn = 'interrupt' | 'onWords' | 'ignore' | 'auto';\n\nexport type EchoRecommendation = 'interrupt' | 'onWords' | 'headphones';\n\nexport interface VoiceTurnOptions {\n ears: TranscriptionModel<string>;\n mouth: SpeechModel<string>;\n voice: string;\n speakable?: (event: AssistantMessageEvent) => string | undefined;\n announce?: (call: ToolCall) => string | undefined;\n bargeIn?: BargeIn;\n echoRecommendation?: EchoRecommendation;\n echo?: EchoControl;\n soundscape?: Soundscape;\n}\n\nexport interface VoiceModels {\n listen(\n model: TranscriptionModel<string>,\n audio: AudioSource,\n options?: ListenOptions\n ): AsyncIterable<TranscriptionEvent>;\n speak(\n model: SpeechModel<string>,\n input: SpeechInput,\n options?: ProviderRequestOptions\n ): AsyncIterable<SpeechEvent>;\n}\n\nexport type VoicePhase =\n | { phase: 'listening' }\n | { phase: 'heard'; text: string }\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'retrying'; attempt: number; at: number }\n | { phase: 'compacting' }\n | { phase: 'speaking' }\n | { phase: 'idle' };\n\nexport type CaptionEvent =\n | { type: 'user_partial'; turnId: string; segmentId: string; text: string }\n | { type: 'user_final'; turnId: string; segmentId: string; text: string; confidence?: number }\n | { type: 'agent_generated'; turnId: string; utteranceId: string; text: string }\n | { type: 'agent_spoken'; turnId: string; utteranceId: string; upTo: number }\n | { type: 'agent_cut'; turnId: string; utteranceId: string; heardUpTo: number }\n | { type: 'announce'; turnId: string; text: string }\n | { type: 'thinker_progress'; turnId: string; callId: string; text: string }\n | { type: 'thinker_result'; turnId: string; callId: string; summary: string; isError: boolean };\n\nexport interface VoiceSession {\n phases: AsyncIterable<VoicePhase>;\n captions: AsyncIterable<CaptionEvent>;\n done: Promise<void>;\n}\n\ninterface SpokenMap {\n spokenStart: number;\n spokenEnd: number;\n contentIndex: number;\n start: number;\n end: number;\n entryId?: string;\n kind: SpokenPiece['kind'];\n}\n\ninterface ActiveTurn {\n controller: AbortController;\n unlink: () => void;\n done: Promise<void>;\n turnId: string;\n utteranceId: string;\n spokenUpTo: number;\n cut?: VoiceCut;\n}\n\nexport function voiceTurns(\n models: VoiceModels,\n conversation: VoiceConversation,\n mic: AudioSource,\n out: AudioSink | AudioOut,\n options: VoiceTurnOptions,\n signal: AbortSignal\n): VoiceSession {\n const phases = pushQueue<VoicePhase>();\n const captions = pushQueue<CaptionEvent>();\n const session: Session = {\n models,\n conversation,\n mic: heardMic(options, mic),\n out: speechOf(out),\n options,\n signal,\n phases,\n captions,\n maps: [],\n bed: attachBed(out, options.soundscape),\n };\n const done = runSession(session).finally(() => {\n phases.end();\n captions.end();\n });\n return { phases: phases.iterable, captions: captions.iterable, done };\n}\n\ninterface Session {\n models: VoiceModels;\n conversation: VoiceConversation;\n mic: AudioSource;\n out: AudioSink;\n options: VoiceTurnOptions;\n signal: AbortSignal;\n phases: ReturnType<typeof pushQueue<VoicePhase>>;\n captions: ReturnType<typeof pushQueue<CaptionEvent>>;\n maps: SpokenMap[];\n playing?: SpokenClock;\n bed: BedFollower;\n}\n\nasync function runSession(session: Session): Promise<void> {\n const mode = bargeMode(session.options);\n let active: ActiveTurn | undefined;\n let confidence: number | undefined;\n let reserved = 0;\n let openId = '';\n const openTurn = () => {\n if (!openId) {\n reserved += 1;\n openId = String(reserved);\n }\n return openId;\n };\n const stopPlayback = session.out.onPlayback((tag, offsetMs) => {\n if (!active) return;\n notePlayback(session, active, tag, offsetMs);\n });\n const setPhase = phaseSetter(session.phases, session.bed);\n try {\n if (session.signal.aborted) return;\n setPhase({ phase: 'listening' });\n for await (const event of session.models.listen(session.options.ears, session.mic, {\n signal: session.signal,\n })) {\n if (event.type === 'error') {\n if (event.reason === 'aborted' || session.signal.aborted) return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n const captionId =\n event.type === 'partial' || event.type === 'final' || event.type === 'turn_end'\n ? openTurn()\n : openId;\n confidence = noteUser(session, event, captionId, confidence);\n if (shouldBarge(mode, event)) active = await interrupt(session, active);\n if (event.type !== 'turn_end') continue;\n const turnId = openId || openTurn();\n openId = '';\n if (mode === 'ignore') active = await finishTurn(active);\n else active = await interrupt(session, active);\n setPhase({ phase: 'heard', text: event.text });\n const meta = turnMeta(session.options, confidence);\n confidence = undefined;\n active = startTurn(session, setPhase, turnId, event, meta);\n }\n } finally {\n if (session.signal.aborted && active) await interrupt(session, active);\n else if (active) await active.done;\n stopPlayback();\n setPhase({ phase: 'idle' });\n session.bed.stop();\n }\n}\n\nfunction turnMeta(options: VoiceTurnOptions, confidence: number | undefined): VoiceTurnMeta {\n return {\n source: 'voice',\n transcriptionModel: options.ears.id,\n ...(confidence === undefined ? {} : { confidence }),\n };\n}\n\nfunction noteUser(\n session: Session,\n event: TranscriptionEvent,\n turnId: string,\n confidence: number | undefined\n): number | undefined {\n if (event.type === 'partial') {\n session.captions.push({\n type: 'user_partial',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n });\n }\n if (event.type === 'final') {\n session.captions.push({\n type: 'user_final',\n turnId,\n segmentId: event.segmentId,\n text: event.text,\n ...(event.confidence === undefined ? {} : { confidence: event.confidence }),\n });\n return event.confidence ?? confidence;\n }\n if (event.type === 'turn_end') {\n session.captions.push({ type: 'user_final', turnId, segmentId: 'turn', text: event.text });\n }\n return confidence;\n}\n\nasync function finishTurn(active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n await active.done;\n return undefined;\n}\n\nfunction startTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n turnId: string,\n event: Extract<TranscriptionEvent, { type: 'turn_end' }>,\n meta: VoiceTurnMeta\n): ActiveTurn {\n const linked = linkSignal(session.signal);\n const active: ActiveTurn = {\n controller: linked.controller,\n unlink: linked.unlink,\n turnId,\n utteranceId: `${turnId}:agent`,\n spokenUpTo: 0,\n done: Promise.resolve(),\n };\n active.done = performTurn(session, setPhase, active, event.text, event.at, meta).finally(() => {\n linked.unlink();\n });\n return active;\n}\n\nasync function performTurn(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n text: string,\n at: number,\n meta: VoiceTurnMeta\n): Promise<void> {\n const signal = active.controller.signal;\n try {\n setPhase({ phase: 'thinking' });\n const message: UserMessage = { role: 'user', content: text, timestamp: at };\n const feed = textFeed(signal);\n const maps: SpokenMap[] = [];\n session.maps = maps;\n const producing = readAgent(session, setPhase, active, message, meta, feed, maps);\n const audio = session.models.speak(\n session.options.mouth,\n { text: feed.iterable, voice: session.options.voice },\n { signal }\n );\n await Promise.all([producing, pipeSpeech(session, setPhase, active, audio, maps)]);\n if (!signal.aborted) setPhase({ phase: 'listening' });\n } catch (error) {\n if (signal.aborted || session.signal.aborted) return;\n throw error;\n }\n}\n\nasync function readAgent(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n message: UserMessage,\n meta: VoiceTurnMeta,\n feed: TextFeed,\n maps: SpokenMap[]\n): Promise<void> {\n const state = createSpokenState();\n let spokenChars = 0;\n let generated = '';\n try {\n for await (const event of session.conversation.prompt(\n message,\n meta,\n active.controller.signal\n )) {\n if (active.controller.signal.aborted) break;\n notePhase(setPhase, event);\n for (const piece of spokenPieces(event, state, session.options)) {\n spokenChars += piece.text.length;\n generated = rememberPiece(\n session,\n active,\n piece,\n maps,\n spokenChars,\n generated,\n state.entryId\n );\n feed.push(piece.text);\n }\n if (state.entryId) bindEntry(maps, state.entryId);\n }\n } finally {\n feed.end();\n }\n}\n\nfunction rememberPiece(\n session: Session,\n active: ActiveTurn,\n piece: SpokenPiece,\n maps: SpokenMap[],\n spokenEnd: number,\n generated: string,\n entryId: string | undefined\n): string {\n const bound = piece.entryId ?? entryId;\n maps.push({\n spokenStart: spokenEnd - piece.text.length,\n spokenEnd,\n contentIndex: piece.contentIndex,\n start: piece.start,\n end: piece.end,\n kind: piece.kind,\n ...(bound ? { entryId: bound } : {}),\n });\n if (piece.kind === 'announce') {\n session.captions.push({ type: 'announce', turnId: active.turnId, text: piece.text });\n return generated;\n }\n const text = generated + piece.text;\n session.captions.push({\n type: 'agent_generated',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n text,\n });\n return text;\n}\n\nasync function pipeSpeech(\n session: Session,\n setPhase: (phase: VoicePhase) => void,\n active: ActiveTurn,\n audio: AsyncIterable<SpeechEvent>,\n maps: SpokenMap[]\n): Promise<void> {\n for await (const event of audio) {\n if (active.controller.signal.aborted) return;\n if (event.type === 'error') {\n if (event.reason === 'aborted') return;\n throw new VoiceRequestError(event.reason, event.error);\n }\n if (event.type !== 'audio') continue;\n setPhase({ phase: 'speaking' });\n const span = translateSpan(event.span, maps);\n const playing: SpokenClock = {\n start: event.span.start,\n end: event.span.end,\n durationMs: Math.round(chunkSeconds(event.chunk) * 1000),\n };\n if (event.alignment) playing.alignment = event.alignment;\n session.playing = playing;\n const tag: AudioTag = { span };\n if (event.alignment) tag.alignment = event.alignment;\n session.out.write(event.chunk, tag);\n }\n}\n\nfunction notePlayback(session: Session, active: ActiveTurn, tag: AudioTag, offsetMs: number): void {\n const playing = session.playing;\n if (playing) {\n const upTo = utteranceIndex(spokenOffset(playing, offsetMs), session.maps);\n if (upTo > active.spokenUpTo) {\n active.spokenUpTo = upTo;\n session.captions.push({\n type: 'agent_spoken',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n upTo,\n });\n }\n }\n if (!('entryId' in tag.span)) return;\n active.cut = {\n entryId: tag.span.entryId,\n contentIndex: tag.span.contentIndex,\n heardUpTo: tag.span.end,\n heardMs: session.out.playedMs,\n };\n}\n\nfunction translateSpan(span: TextSpan, maps: SpokenMap[]): TextSpan {\n if ('entryId' in span) return span;\n const map = answerMap(span, maps);\n if (!map?.entryId) return span;\n const spokenLen = map.spokenEnd - map.spokenStart;\n const sourceLen = map.end - map.start;\n const start = map.start + scale(Math.max(0, span.start - map.spokenStart), spokenLen, sourceLen);\n const end =\n map.start + scale(Math.min(spokenLen, span.end - map.spokenStart), spokenLen, sourceLen);\n return { entryId: map.entryId, contentIndex: map.contentIndex, start, end };\n}\n\nfunction answerMap(span: { start: number; end: number }, maps: SpokenMap[]): SpokenMap | undefined {\n for (let index = maps.length - 1; index >= 0; index -= 1) {\n const item = maps[index];\n if (item?.kind !== 'answer' || !item.entryId) continue;\n if (item.spokenStart < span.end && item.spokenEnd > span.start) return item;\n }\n return undefined;\n}\n\nfunction scale(offset: number, spokenLen: number, sourceLen: number): number {\n if (spokenLen <= 0) return 0;\n return Math.round((offset * sourceLen) / spokenLen);\n}\n\nfunction bindEntry(maps: SpokenMap[], entryId: string): void {\n for (const map of maps) {\n if (map.kind === 'answer' && map.entryId === undefined) map.entryId = entryId;\n }\n}\n\nfunction notePhase(setPhase: (phase: VoicePhase) => void, event: VoiceAgentEvent): void {\n if (event.type === 'run_start' || event.type === 'turn_start') setPhase({ phase: 'thinking' });\n if (event.type === 'compaction_start') setPhase({ phase: 'compacting' });\n if (event.type === 'auto_retry_start' && event.attempt !== undefined && event.at !== undefined) {\n setPhase({ phase: 'retrying', attempt: event.attempt, at: event.at });\n }\n if (event.type === 'tool_execution_start' && event.toolName && event.toolCallId) {\n setPhase({\n phase: 'tool',\n toolName: event.toolName,\n toolCallId: event.toolCallId,\n args: event.args ?? {},\n });\n }\n}\n\nasync function interrupt(session: Session, active: ActiveTurn | undefined): Promise<undefined> {\n if (!active) return undefined;\n active.controller.abort();\n session.out.flush();\n await active.done;\n if (active.cut) {\n session.captions.push({\n type: 'agent_cut',\n turnId: active.turnId,\n utteranceId: active.utteranceId,\n heardUpTo: active.cut.heardUpTo,\n });\n await session.conversation.truncate(active.cut);\n }\n return undefined;\n}\n\nfunction bargeMode(options: VoiceTurnOptions): 'interrupt' | 'onWords' | 'ignore' {\n const mode = options.bargeIn ?? 'interrupt';\n if (mode !== 'auto') return mode;\n if (options.echoRecommendation === 'interrupt') return 'interrupt';\n if (options.echoRecommendation === 'headphones') return 'ignore';\n return 'onWords';\n}\n\nfunction shouldBarge(mode: 'interrupt' | 'onWords' | 'ignore', event: TranscriptionEvent): boolean {\n if (mode === 'ignore') return false;\n if (mode === 'interrupt') return event.type === 'speech_start';\n return event.type === 'partial' && event.text.trim().length > 0;\n}\n\nfunction heardMic(options: VoiceTurnOptions, mic: AudioSource): AudioSource {\n const echo = options.echo;\n if (echo === undefined) return mic;\n return openEchoMic(echo, mic);\n}\n\nfunction speechOf(out: AudioSink | AudioOut): AudioSink {\n if (isAudioOut(out)) return out.speech;\n return out;\n}\n\nfunction attachBed(out: AudioSink | AudioOut, soundscape: Soundscape | undefined): BedFollower {\n if (!soundscape) return idleBed();\n if (!isAudioOut(out)) return idleBed();\n return followBed(out.bed, soundscape);\n}\n\nfunction idleBed(): BedFollower {\n return {\n observe() {},\n stop() {},\n };\n}\n\nfunction phaseSetter(phases: Session['phases'], bed: BedFollower): (phase: VoicePhase) => void {\n let previous = '';\n return (phase) => {\n const key = JSON.stringify(phase);\n if (key === previous) return;\n previous = key;\n phases.push(phase);\n bed.observe(phase);\n };\n}\n\nfunction linkSignal(parent: AbortSignal): { controller: AbortController; unlink: () => void } {\n const controller = new AbortController();\n const abort = () => controller.abort();\n if (parent.aborted) controller.abort();\n else parent.addEventListener('abort', abort);\n return {\n controller,\n unlink: () => parent.removeEventListener('abort', abort),\n };\n}\n\ninterface TextFeed {\n push(text: string): void;\n end(): void;\n iterable: AsyncIterable<string>;\n}\n\nfunction textFeed(signal: AbortSignal): TextFeed {\n const queue: string[] = [];\n let ended = false;\n let wake: (() => void) | undefined;\n const notify = () => {\n const current = wake;\n wake = undefined;\n current?.();\n };\n const onAbort = () => notify();\n signal.addEventListener('abort', onAbort);\n return {\n push(text) {\n if (ended || signal.aborted || text.length === 0) return;\n queue.push(text);\n notify();\n },\n end() {\n ended = true;\n notify();\n },\n iterable: {\n async *[Symbol.asyncIterator]() {\n try {\n while (!signal.aborted) {\n const next = queue.shift();\n if (next !== undefined) {\n yield next;\n continue;\n }\n if (ended) return;\n await new Promise<void>((resolve) => {\n if (queue.length > 0 || ended || signal.aborted) resolve();\n else wake = resolve;\n });\n }\n } finally {\n signal.removeEventListener('abort', onAbort);\n }\n },\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|