@kuralle-syrinx/cf-agents 4.4.1 → 4.5.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.
@@ -0,0 +1,303 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // R2-backed implementation of the transport EdgeRecorder. Taps inbound caller
4
+ // audio and outbound TTS audio and writes to an R2 bucket:
5
+ // - user.wav / assistant.wav : the per-speaker stems, time-aligned by wall-clock
6
+ // (user chunks at their wall offset; assistant re-anchored to
7
+ // playout) with silence filling the gaps — the durable artifacts.
8
+ // - conversation.wav : best-effort stereo mix (user = left, assistant = right),
9
+ // written only for short calls that stayed wholly in DO RAM;
10
+ // omitted (flagged in the manifest) once a stem streams out.
11
+ // - manifest.json : durations / byte lengths / truncation flags.
12
+ // Mirrors the Node `voice-recorder` conversation-track approach (wall-clock byte
13
+ // offsets + stereo interleave) but stays edge-safe (no node:fs).
14
+ //
15
+ // Memory: the DO has ~128 MB. Rather than buffer the whole call and gap-fill the full
16
+ // wall-clock length at finalize (which OOMs long/mostly-silent calls), each stem is
17
+ // gap-filled INCREMENTALLY and streamed to R2 via multipart upload: bytes accumulate
18
+ // into a bounded buffer and flush as 5 MiB parts, so retained memory stays ~O(part size)
19
+ // per stem regardless of call length. Short calls (< one part) never open a multipart and
20
+ // are written with a single put, exactly as before.
21
+ import { interleaveStereoPcm16, pcm16ToWav } from "@kuralle-syrinx/recorder/wav";
22
+ // R2 requires every multipart part except the last to be at least 5 MiB. We flush at
23
+ // exactly that: bigger parts waste RAM, smaller ones are rejected.
24
+ const PART_SIZE_BYTES = 5 * 1024 * 1024;
25
+ // Emit long silence gaps in small slices so a multi-minute gap never allocates its full
26
+ // wall-clock length at once (that was the OOM).
27
+ const SILENCE_SLICE_BYTES = 64 * 1024;
28
+ /** A FIFO byte queue that hands back exact-length runs without holding the whole call. */
29
+ class PartBuffer {
30
+ #chunks = [];
31
+ #size = 0;
32
+ push(bytes) {
33
+ if (bytes.byteLength === 0)
34
+ return;
35
+ this.#chunks.push(bytes);
36
+ this.#size += bytes.byteLength;
37
+ }
38
+ get size() {
39
+ return this.#size;
40
+ }
41
+ /** Remove and return the first `n` bytes (caller guarantees n <= size). */
42
+ take(n) {
43
+ const out = new Uint8Array(n);
44
+ let filled = 0;
45
+ while (filled < n) {
46
+ const head = this.#chunks[0];
47
+ const need = n - filled;
48
+ if (head.byteLength <= need) {
49
+ out.set(head, filled);
50
+ filled += head.byteLength;
51
+ this.#chunks.shift();
52
+ }
53
+ else {
54
+ out.set(head.subarray(0, need), filled);
55
+ this.#chunks[0] = head.subarray(need);
56
+ filled += need;
57
+ }
58
+ }
59
+ this.#size -= n;
60
+ return out;
61
+ }
62
+ drain() {
63
+ return this.take(this.#size);
64
+ }
65
+ }
66
+ export class R2EdgeRecorder {
67
+ opts;
68
+ #prefix;
69
+ #user;
70
+ #assistant;
71
+ #maxBytes;
72
+ #now;
73
+ #finalized = false;
74
+ constructor(opts) {
75
+ this.opts = opts;
76
+ this.#prefix = `${opts.keyPrefix ?? "recordings"}/${opts.sessionId}/${opts.startedAtMs}`;
77
+ this.#user = this.#emptyStem(`${this.#prefix}/user.wav`);
78
+ this.#assistant = this.#emptyStem(`${this.#prefix}/assistant.wav`);
79
+ this.#maxBytes = opts.maxBytesPerStream;
80
+ this.#now = opts.now ?? Date.now;
81
+ }
82
+ onUserAudio(_contextId, audio, sampleRateHz) {
83
+ this.#append(this.#user, audio, sampleRateHz);
84
+ }
85
+ onAssistantAudio(_contextId, audio, sampleRateHz) {
86
+ this.#append(this.#assistant, audio, sampleRateHz);
87
+ }
88
+ async finalize(meta) {
89
+ if (this.#finalized)
90
+ return;
91
+ this.#finalized = true;
92
+ if (this.#user.dataBytes === 0 && this.#assistant.dataBytes === 0)
93
+ return;
94
+ const rate = this.#user.sampleRateHz; // conversation timeline runs at the user (input) rate
95
+ // Close each stem sequentially (never hold both full WAV copies at once). A stem that
96
+ // never streamed hands back its full mono PCM so a best-effort stereo mix can be built.
97
+ const userMono = await this.#closeStem(this.#user);
98
+ const assistantMono = await this.#closeStem(this.#assistant);
99
+ const conversation = userMono && assistantMono
100
+ ? await this.#writeStereo(userMono, assistantMono, rate)
101
+ : {
102
+ path: `${this.#prefix}/conversation.wav`,
103
+ sampleRateHz: rate,
104
+ channels: 2,
105
+ encoding: "pcm_s16le",
106
+ byteLength: 0,
107
+ durationMs: 0,
108
+ omitted: true,
109
+ };
110
+ const manifest = {
111
+ schemaVersion: 1,
112
+ sessionId: meta.sessionId,
113
+ startedAtMs: this.opts.startedAtMs,
114
+ closedAtMs: meta.closedAtMs,
115
+ conversation,
116
+ user: this.#describe(this.#user),
117
+ assistant: this.#describe(this.#assistant),
118
+ };
119
+ await this.opts.bucket.put(`${this.#prefix}/manifest.json`, JSON.stringify(manifest, null, 2), {
120
+ httpMetadata: { contentType: "application/json" },
121
+ });
122
+ }
123
+ #emptyStem(key) {
124
+ return {
125
+ key,
126
+ buf: new PartBuffer(),
127
+ head: null,
128
+ multipart: false,
129
+ uploadId: null,
130
+ parts: [],
131
+ partSeq: 2,
132
+ tail: Promise.resolve(),
133
+ cursorBytes: 0,
134
+ dataBytes: 0,
135
+ sampleRateHz: 16000,
136
+ truncated: false,
137
+ };
138
+ }
139
+ #append(stem, audio, sampleRateHz) {
140
+ stem.sampleRateHz = sampleRateHz;
141
+ if (this.#maxBytes !== undefined && stem.dataBytes + audio.byteLength > this.#maxBytes) {
142
+ stem.truncated = true;
143
+ return;
144
+ }
145
+ // Anchor each chunk at its wall-clock position so the two speakers line up on a shared
146
+ // timeline; never overlap the previous chunk. Emit the intervening silence + the chunk
147
+ // incrementally so the timeline is never materialised whole.
148
+ const wallOffset = this.#wallOffsetBytes(sampleRateHz);
149
+ const offsetBytes = Math.max(stem.cursorBytes, wallOffset);
150
+ const gap = offsetBytes - stem.cursorBytes;
151
+ if (gap > 0)
152
+ this.#emitSilence(stem, gap);
153
+ this.#emit(stem, audio.slice());
154
+ stem.cursorBytes = offsetBytes + audio.byteLength;
155
+ stem.dataBytes += audio.byteLength;
156
+ }
157
+ #emitSilence(stem, bytes) {
158
+ let remaining = bytes;
159
+ while (remaining > 0) {
160
+ const slice = Math.min(remaining, SILENCE_SLICE_BYTES);
161
+ this.#emit(stem, new Uint8Array(slice)); // zero = PCM16 silence
162
+ remaining -= slice;
163
+ }
164
+ }
165
+ #emit(stem, bytes) {
166
+ if (bytes.byteLength === 0)
167
+ return;
168
+ stem.buf.push(bytes);
169
+ if (!stem.multipart && stem.buf.size >= PART_SIZE_BYTES) {
170
+ // Commit to multipart: retain the first part's worth as the deferred part 1 (its WAV
171
+ // header needs the final total length, known only at finalize), then stream the rest.
172
+ stem.head = stem.buf.take(PART_SIZE_BYTES);
173
+ stem.multipart = true;
174
+ this.#enqueueCreate(stem);
175
+ }
176
+ if (stem.multipart) {
177
+ while (stem.buf.size >= PART_SIZE_BYTES) {
178
+ this.#enqueueUpload(stem, stem.partSeq++, stem.buf.take(PART_SIZE_BYTES));
179
+ }
180
+ }
181
+ }
182
+ #enqueueCreate(stem) {
183
+ stem.tail = stem.tail.then(async () => {
184
+ const mpu = await this.opts.bucket.createMultipartUpload(stem.key, {
185
+ httpMetadata: { contentType: "audio/wav" },
186
+ });
187
+ stem.uploadId = mpu.uploadId;
188
+ });
189
+ }
190
+ #enqueueUpload(stem, partNumber, body) {
191
+ stem.tail = stem.tail.then(async () => {
192
+ const mpu = this.opts.bucket.resumeMultipartUpload(stem.key, stem.uploadId);
193
+ stem.parts.push(await mpu.uploadPart(partNumber, body));
194
+ });
195
+ }
196
+ /** Flush a stem to R2. Returns its full mono PCM if it stayed in RAM, else null. */
197
+ async #closeStem(stem) {
198
+ if (!stem.multipart) {
199
+ const mono = stem.buf.drain();
200
+ await this.opts.bucket.put(stem.key, pcm16ToWav(mono, stem.sampleRateHz, 1), {
201
+ httpMetadata: { contentType: "audio/wav" },
202
+ });
203
+ return mono;
204
+ }
205
+ await stem.tail; // drain the queued create + middle-part uploads
206
+ const mpu = this.opts.bucket.resumeMultipartUpload(stem.key, stem.uploadId);
207
+ // Part 1 = the WAV header (now that the total data length is known) + the retained head.
208
+ const head = stem.head ?? new Uint8Array(0);
209
+ const part1 = concat(wavHeader(stem.cursorBytes, stem.sampleRateHz, 1), head);
210
+ stem.parts.push(await mpu.uploadPart(1, part1));
211
+ const remainder = stem.buf.drain();
212
+ if (remainder.byteLength > 0)
213
+ stem.parts.push(await mpu.uploadPart(stem.partSeq++, remainder));
214
+ stem.parts.sort((a, b) => a.partNumber - b.partNumber);
215
+ await mpu.complete(stem.parts);
216
+ return null;
217
+ }
218
+ async #writeStereo(userPcm, assistantMono, rate) {
219
+ const assistantPcm = this.#assistant.sampleRateHz === rate
220
+ ? assistantMono
221
+ : resamplePcm16(assistantMono, this.#assistant.sampleRateHz, rate);
222
+ const stereo = interleaveStereoPcm16(userPcm, assistantPcm);
223
+ await this.opts.bucket.put(`${this.#prefix}/conversation.wav`, pcm16ToWav(stereo, rate, 2), {
224
+ httpMetadata: { contentType: "audio/wav" },
225
+ });
226
+ return {
227
+ path: `${this.#prefix}/conversation.wav`,
228
+ sampleRateHz: rate,
229
+ channels: 2,
230
+ encoding: "pcm_s16le",
231
+ byteLength: stereo.byteLength,
232
+ durationMs: rate > 0 ? Math.round((stereo.byteLength / 4 / rate) * 1000) : 0,
233
+ omitted: false,
234
+ };
235
+ }
236
+ #wallOffsetBytes(sampleRateHz) {
237
+ const elapsedMs = Math.max(0, this.#now() - this.opts.startedAtMs);
238
+ const bytes = Math.floor((elapsedMs * sampleRateHz * 2) / 1000);
239
+ return bytes - (bytes % 2);
240
+ }
241
+ #describe(stem) {
242
+ return {
243
+ path: stem.key,
244
+ sampleRateHz: stem.sampleRateHz,
245
+ encoding: "pcm_s16le",
246
+ channels: 1,
247
+ byteLength: stem.cursorBytes,
248
+ durationMs: stem.sampleRateHz > 0 ? Math.round((stem.cursorBytes / 2 / stem.sampleRateHz) * 1000) : 0,
249
+ truncated: stem.truncated,
250
+ };
251
+ }
252
+ }
253
+ function concat(a, b) {
254
+ const out = new Uint8Array(a.byteLength + b.byteLength);
255
+ out.set(a, 0);
256
+ out.set(b, a.byteLength);
257
+ return out;
258
+ }
259
+ /** Build the 44-byte canonical WAV (RIFF/PCM) header for a stream of `dataBytes` PCM16LE. */
260
+ function wavHeader(dataBytes, sampleRateHz, channels) {
261
+ const blockAlign = channels * 2; // 16-bit samples
262
+ const byteRate = sampleRateHz * blockAlign;
263
+ const out = new Uint8Array(44);
264
+ const view = new DataView(out.buffer);
265
+ writeAscii(view, 0, "RIFF");
266
+ view.setUint32(4, 36 + dataBytes, true);
267
+ writeAscii(view, 8, "WAVE");
268
+ writeAscii(view, 12, "fmt ");
269
+ view.setUint32(16, 16, true);
270
+ view.setUint16(20, 1, true); // PCM
271
+ view.setUint16(22, channels, true);
272
+ view.setUint32(24, sampleRateHz, true);
273
+ view.setUint32(28, byteRate, true);
274
+ view.setUint16(32, blockAlign, true);
275
+ view.setUint16(34, 16, true);
276
+ writeAscii(view, 36, "data");
277
+ view.setUint32(40, dataBytes, true);
278
+ return out;
279
+ }
280
+ function writeAscii(view, offset, text) {
281
+ for (let i = 0; i < text.length; i += 1)
282
+ view.setUint8(offset + i, text.charCodeAt(i));
283
+ }
284
+ function resamplePcm16(pcm, fromHz, toHz) {
285
+ if (fromHz === toHz || pcm.byteLength === 0)
286
+ return pcm;
287
+ const src = new DataView(pcm.buffer, pcm.byteOffset, pcm.byteLength);
288
+ const inSamples = pcm.byteLength >> 1;
289
+ const outSamples = Math.max(1, Math.round((inSamples * toHz) / fromHz));
290
+ const out = new Uint8Array(outSamples * 2);
291
+ const ov = new DataView(out.buffer);
292
+ const ratio = (inSamples - 1) / Math.max(1, outSamples - 1);
293
+ for (let i = 0; i < outSamples; i += 1) {
294
+ const x = i * ratio;
295
+ const i0 = Math.floor(x);
296
+ const i1 = Math.min(inSamples - 1, i0 + 1);
297
+ const frac = x - i0;
298
+ const s = src.getInt16(i0 * 2, true) * (1 - frac) + src.getInt16(i1 * 2, true) * frac;
299
+ ov.setInt16(i * 2, Math.round(s), true);
300
+ }
301
+ return out;
302
+ }
303
+ //# sourceMappingURL=r2-recorder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"r2-recorder.js","sourceRoot":"","sources":["../src/r2-recorder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { Agent } from "agents";
2
+ interface Env extends Record<string, unknown> {
3
+ GEMINI_API_KEY: string;
4
+ }
5
+ declare const RealtimeVoiceAgent_base: typeof Agent<Env> & (new (...args: any[]) => import("./with-voice.js").WithVoiceMembers);
6
+ export declare class RealtimeVoiceAgent extends RealtimeVoiceAgent_base {
7
+ }
8
+ declare const CascadedVoiceAgent_base: typeof Agent<Env> & (new (...args: any[]) => import("./with-voice.js").WithVoiceMembers);
9
+ export declare class CascadedVoiceAgent extends CascadedVoiceAgent_base {
10
+ }
11
+ export {};
12
+ //# sourceMappingURL=real-agent.compile-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-agent.compile-check.d.ts","sourceRoot":"","sources":["../src/real-agent.compile-check.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAK/B,UAAU,GAAI,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC3C,cAAc,EAAE,MAAM,CAAC;CACxB;uCAGsD,OAAO,KAAK,CAAC,GAAG,CAAC;AAAxE,qBAAa,kBAAmB,SAAQ,uBAMtC;CAAG;uCAKkD,OAAO,KAAK,CAAC,GAAG,CAAC;AAAxE,qBAAa,kBAAmB,SAAQ,uBAYtC;CAAG"}
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: MIT
2
+ //
3
+ // Compile-only proof that `withVoice` composes with the REAL `agents` SDK Agent
4
+ // (not just the test's fake base). Type-checked by `tsc --noEmit`; never run.
5
+ // If the agents SDK changes the Agent / Connection / ConnectionContext surface
6
+ // the mixin relies on, this file fails to compile.
7
+ import { Agent } from "agents";
8
+ import { fromGeminiLive } from "@kuralle-syrinx/realtime";
9
+ import { withVoice } from "./with-voice.js";
10
+ // Realtime front: the agent's own kuralle runtime is the brain (default reasoner).
11
+ export class RealtimeVoiceAgent extends withVoice((Agent), {
12
+ pipeline: {
13
+ kind: "realtime",
14
+ front: (env) => fromGeminiLive({ apiKey: env.GEMINI_API_KEY }),
15
+ delegateToolName: "consult_knowledge",
16
+ },
17
+ }) {
18
+ }
19
+ // Cascaded: explicit reasoner + discrete stt/tts stages.
20
+ const noopPlugin = { initialize: async () => { }, close: async () => { } };
21
+ export class CascadedVoiceAgent extends withVoice((Agent), {
22
+ pipeline: {
23
+ kind: "cascaded",
24
+ stt: () => ({ plugin: noopPlugin, config: { model: "nova-3" } }),
25
+ tts: () => ({ plugin: noopPlugin, config: { voice_id: "v" } }),
26
+ },
27
+ reasoner: () => ({
28
+ // eslint-disable-next-line require-yield
29
+ stream: async function* () {
30
+ return;
31
+ },
32
+ }),
33
+ }) {
34
+ }
35
+ //# sourceMappingURL=real-agent.compile-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"real-agent.compile-check.js","sourceRoot":"","sources":["../src/real-agent.compile-check.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,+EAA+E;AAC/E,mDAAmD;AAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAM5C,mFAAmF;AACnF,MAAM,OAAO,kBAAmB,SAAQ,SAAS,CAAyB,CAAA,KAAU,CAAA,EAAE;IACpF,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;QAC9D,gBAAgB,EAAE,mBAAmB;KACtC;CACF,CAAC;CAAG;AAEL,yDAAyD;AACzD,MAAM,UAAU,GAAgB,EAAE,UAAU,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC,EAAE,CAAC;AAEtF,MAAM,OAAO,kBAAmB,SAAQ,SAAS,CAAyB,CAAA,KAAU,CAAA,EAAE;IACpF,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC;QAChE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,CAAC;KAC/D;IACD,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACf,yCAAyC;QACzC,MAAM,EAAE,KAAK,SAAS,CAAC;YACrB,OAAO;QACT,CAAC;KACF,CAAC;CACH,CAAC;CAAG"}
@@ -0,0 +1,150 @@
1
+ import type { Agent } from "agents";
2
+ import { type BackgroundAudioConfig, type EdgeRecorder } from "@kuralle-syrinx/server-websocket/edge";
3
+ import type { IdleTimeoutConfig, Reasoner } from "@kuralle-syrinx/core";
4
+ import { type VoiceConnection } from "./connection-socket.js";
5
+ import { type VoicePipeline, type VoicePipelineContext } from "./build-session.js";
6
+ type Constructor<T = object> = new (...args: any[]) => T;
7
+ /** The Agent surface the voice mixin relies on. (`env`/`name`/`runtime` are read via VoiceHostSurface.) */
8
+ type AgentLike = Constructor<Pick<Agent<Record<string, unknown>>, "sql" | "getConnections" | "keepAlive">>;
9
+ /**
10
+ * Fired the instant the front model invokes the delegate tool — BEFORE the reasoner runs.
11
+ * Lets an app emit a deterministic, in-language preamble or a "thinking" earcon that masks the
12
+ * reasoner's wait, instead of relying on the realtime front LLM to remember to speak one (cf.
13
+ * Vapi/Pipecat `on_function_calls_started`). Use `connection.send(...)` to signal the client
14
+ * (the idiomatic agents-SDK pattern) — e.g. trigger a cached client-side earcon/preamble.
15
+ */
16
+ export interface ToolCallStartContext {
17
+ readonly toolName: string;
18
+ readonly args: Record<string, unknown>;
19
+ readonly sessionId: string;
20
+ /** The live agents-SDK connection — `connection.send(json)` to message the client. */
21
+ readonly connection: VoiceConnection;
22
+ }
23
+ /**
24
+ * Delegate (Responder-Thinker) observability — fired when the bridge hands a query to
25
+ * the Reasoner (G2, RFC bimodel-delegate-seam). Replaces the consumer-side pattern of
26
+ * wrapping the Reasoner just to log the query. `toolId`/`toolName` are present on
27
+ * realtime delegate turns, absent on cascade turns.
28
+ */
29
+ export interface DelegateQueryContext<Env = unknown> {
30
+ readonly query: string;
31
+ readonly toolId?: string;
32
+ readonly toolName?: string;
33
+ readonly turnId: string;
34
+ readonly sessionId: string;
35
+ readonly connection: VoiceConnection;
36
+ /** The Worker env — bindings for logging/persistence (e.g. an R2 bucket). */
37
+ readonly env: Env;
38
+ }
39
+ /**
40
+ * Fired when the Reasoner produced the turn's final answer. Self-contained (carries the
41
+ * query again) so a consumer can log/persist the grounded Q&A pair or message its client.
42
+ */
43
+ export interface DelegateResultContext<Env = unknown> {
44
+ readonly query: string;
45
+ readonly answer: string;
46
+ readonly durationMs: number;
47
+ readonly grounded: boolean;
48
+ readonly toolId?: string;
49
+ readonly toolName?: string;
50
+ readonly control?: {
51
+ name: string;
52
+ payload: unknown;
53
+ };
54
+ readonly blocked?: {
55
+ userFacingMessage: string;
56
+ payload?: unknown;
57
+ };
58
+ readonly turnId: string;
59
+ readonly sessionId: string;
60
+ /** The connection that initiated this delegate; use `send(...)` for an app message to that client. */
61
+ readonly connection: VoiceConnection;
62
+ /** The Worker env — bindings for logging/persistence (e.g. an R2 bucket). */
63
+ readonly env: Env;
64
+ }
65
+ export interface WithVoiceOptions<Env> {
66
+ /**
67
+ * The connection wire protocol this host speaks. `"edge"` (default) is the Syrinx
68
+ * browser/edge JSON+envelope protocol (`runVoiceEdgeWebSocketConnection`). `"twilio"`
69
+ * speaks the Twilio Media Streams protocol (μ-law 8 kHz both ways,
70
+ * `runTwilioEdgeWebSocketConnection`) for a PSTN leg. `"telnyx"` speaks the Telnyx
71
+ * Media Streaming protocol (PCMU/PCMA/G722/L16, `runTelnyxEdgeWebSocketConnection`).
72
+ * One transport per Agent class — route `/ws` to an `"edge"` agent, `/twilio` to a
73
+ * `"twilio"` agent, and `/telnyx` to a `"telnyx"` agent.
74
+ */
75
+ readonly transport?: "edge" | "twilio" | "telnyx";
76
+ /** The voice pipeline: `{ kind: "realtime", ... }` or `{ kind: "cascaded", ... }`. */
77
+ readonly pipeline: VoicePipeline<Env>;
78
+ /**
79
+ * The brain. Defaults to `fromKuralleRuntime(this.runtime, { sessionId })` when
80
+ * the Agent exposes a kuralle `runtime`. Required for cascaded pipelines on
81
+ * agents without a `runtime`.
82
+ */
83
+ readonly reasoner?: (env: Env, ctx: VoicePipelineContext) => Reasoner | Promise<Reasoner>;
84
+ /** Optional per-call recorder (e.g. an R2-backed `EdgeRecorder`). Applies to the `"edge"` transport. */
85
+ readonly recorder?: (env: Env, ctx: VoicePipelineContext) => EdgeRecorder | undefined;
86
+ /**
87
+ * Ambient/thinking bed mixed (ducked) under assistant speech on both transports.
88
+ * On the `"twilio"` transport the bed also fills the gaps between turns as
89
+ * comfort noise — pure digital silence on a phone line reads as "the call died".
90
+ * The thinking loop follows the G3 tool-call cues. Sources are raw mono PCM16
91
+ * (e.g. `ffmpeg -i office.mp3 -ac 1 -ar 16000 -f s16le office.pcm`).
92
+ */
93
+ readonly backgroundAudio?: BackgroundAudioConfig;
94
+ /**
95
+ * Fired when the front model starts a delegate tool call, before the reasoner runs — the seam
96
+ * for a deterministic latency-masking preamble / "thinking" earcon. Throwing here never affects
97
+ * the call. See {@link ToolCallStartContext}.
98
+ */
99
+ readonly onToolCallStart?: (ctx: ToolCallStartContext) => void | Promise<void>;
100
+ /**
101
+ * Delegate observability (G2): fired when the bridge hands a query to the Reasoner.
102
+ * Subscribe instead of wrapping the Reasoner to log. Throwing here never affects the call.
103
+ */
104
+ readonly onDelegateQuery?: (ctx: DelegateQueryContext<Env>) => void | Promise<void>;
105
+ /**
106
+ * Delegate observability (G2): fired when the Reasoner produced the turn's final answer —
107
+ * the hook for logging/persisting the grounded Q&A pair or sending a post-result app message
108
+ * through `ctx.connection` (query + answer + durationMs + grounded). Throwing here never affects
109
+ * the call.
110
+ */
111
+ readonly onDelegateResult?: (ctx: DelegateResultContext<Env>) => void | Promise<void>;
112
+ /**
113
+ * G4 durable session state (default on). Persists the reasoner conversation to the
114
+ * Agent's DO-SQLite so a session resumes with the same context after eviction/
115
+ * hibernation: cascaded pipelines re-seed the ReasoningBridge; realtime pipelines
116
+ * record the transcript, feed it to delegate turns as prior context, and expose it
117
+ * (plus any provider-native resume handle) to the `front()` factory via
118
+ * `ctx.resume`. Set false for the pre-G4 ephemeral behavior.
119
+ */
120
+ readonly durableHistory?: boolean;
121
+ /**
122
+ * G3: ms a pending tool call may run before the time-triggered `tool_call_delayed`
123
+ * ("still working") wire cue fires. 0 disables the delayed phase. Default: 2000.
124
+ */
125
+ readonly delayCueAfterMs?: number;
126
+ /**
127
+ * Idle-timeout override for this host. Omitted → the 15s telephony re-engagement
128
+ * ("Are you still there?" then disconnect). Browser/edge hosts should pass
129
+ * `{ durationMs: 0 }` to disable it: a demo/playground user granting mic or reading
130
+ * the UI must not be nagged or hung up on. Leave the default on `"twilio"` hosts.
131
+ */
132
+ readonly idleTimeout?: Partial<IdleTimeoutConfig>;
133
+ readonly inputSampleRateHz?: number;
134
+ readonly outputSampleRateHz?: number;
135
+ readonly resumeWindowMs?: number;
136
+ /**
137
+ * Derive the Syrinx session id. When supplied, this resolver is authoritative;
138
+ * otherwise the `?sessionId=` query param is used, then a per-connection random id.
139
+ * The Agent instance `name` is not used because concurrent connections to one DO
140
+ * must not silently share a session.
141
+ */
142
+ readonly sessionId?: (request: Request, agentName: string) => string;
143
+ }
144
+ export interface WithVoiceMembers {
145
+ /** Force-end the voice session on a connection (e.g. moderation, takeover). */
146
+ forceEndVoice(connection: VoiceConnection): void;
147
+ }
148
+ export declare function withVoice<Env, TBase extends AgentLike>(Base: TBase, options: WithVoiceOptions<Env>): TBase & Constructor<WithVoiceMembers>;
149
+ export {};
150
+ //# sourceMappingURL=with-voice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"with-voice.d.ts","sourceRoot":"","sources":["../src/with-voice.ts"],"names":[],"mappings":"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"}