realtime-voice-agents 2.0.1 → 2.2.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 +58 -2
- package/dist/{BaseRealtimeProvider-BehPNT1r.d.cts → BaseRealtimeProvider-BL75_HHh.d.cts} +53 -3
- package/dist/{BaseRealtimeProvider-DI4pKtOb.cjs → BaseRealtimeProvider-C2mRn1V_.cjs} +10 -0
- package/dist/{BaseRealtimeProvider-BQigr5mB.mjs → BaseRealtimeProvider-C9C3s8jx.mjs} +10 -0
- package/dist/{BaseRealtimeProvider-ClP8Wx1X.d.mts → BaseRealtimeProvider-CWJ81HIt.d.mts} +53 -3
- package/dist/{GeminiLiveProvider-DvkTgzjG.d.cts → GeminiLiveProvider-Cc1dkxW6.d.cts} +1 -1
- package/dist/{GeminiLiveProvider-x2nyx5aO.d.mts → GeminiLiveProvider-TvY_cQQZ.d.mts} +1 -1
- package/dist/{OpenAICompatibleProvider-Bdtl-UXH.mjs → OpenAICompatibleProvider-D-2OOVBU.mjs} +98 -11
- package/dist/{OpenAICompatibleProvider-NS4cKVQj.cjs → OpenAICompatibleProvider-Mp0Mefbh.cjs} +98 -11
- package/dist/audio.cjs +9 -6
- package/dist/audio.d.cts +51 -1
- package/dist/audio.d.mts +51 -1
- package/dist/audio.mjs +2 -2
- package/dist/gemini.cjs +16 -6
- package/dist/gemini.d.cts +8 -3
- package/dist/gemini.d.mts +8 -3
- package/dist/gemini.mjs +16 -6
- package/dist/index.cjs +356 -16
- package/dist/index.d.cts +211 -2
- package/dist/index.d.mts +211 -2
- package/dist/index.mjs +355 -16
- package/dist/{BackgroundAudioPlayer-iMcivjis.mjs → noise-CJ789zzj.mjs} +94 -1
- package/dist/{BackgroundAudioPlayer-jfRULWKC.cjs → noise-D9jKottW.cjs} +111 -0
- package/dist/openai.cjs +22 -8
- package/dist/openai.d.cts +31 -3
- package/dist/openai.d.mts +31 -3
- package/dist/openai.mjs +22 -8
- package/dist/{session-config-BVLl7-ha.mjs → session-config-CbifLlkV.mjs} +1 -1
- package/dist/{session-config-c8sOw1XL.cjs → session-config-CqJm2Kxz.cjs} +1 -1
- package/dist/testing.cjs +29 -2
- package/dist/testing.d.cts +31 -1
- package/dist/testing.d.mts +31 -1
- package/dist/testing.mjs +29 -2
- package/dist/xai.cjs +23 -9
- package/dist/xai.d.cts +7 -2
- package/dist/xai.d.mts +7 -2
- package/dist/xai.mjs +23 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,26 @@ geminiLive({ model: 'gemini-2.5-flash-native-audio-preview-12-2025', voice: 'Aoe
|
|
|
104
104
|
|
|
105
105
|
One `SessionOptions` surface configures all three; where a provider can't honor a knob, the fallback is documented and pinned by the parity test suite.
|
|
106
106
|
|
|
107
|
+
## Provider fallbacks
|
|
108
|
+
|
|
109
|
+
One bad API key, an exhausted quota, or a provider outage should not send your calls to dead air. Give the bridge backup providers and it tries them in order while the call is being established:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const bridge = new TwilioRealtimeBridge({
|
|
113
|
+
agent,
|
|
114
|
+
provider: openaiRealtime(), // primary
|
|
115
|
+
fallbacks: [xaiRealtime(), geminiLive()], // tried in order if it fails to come up
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- **Covers the real failure modes.** A missing API key (the factories defer their credential check to call time precisely so the chain can absorb it), an expired/revoked key (HTTP 401), exhausted credits/quota (403/429), a provider internal error (5xx or a dropped socket), and a hung endpoint (connect timeout) all walk the chain — anything that keeps a provider from coming up.
|
|
120
|
+
- **Connect-time only.** A dead provider is dropped and the next one is tried immediately — no backoff between attempts. Once a provider answers, the call stays with it: mid-call reconnects reuse the same provider (per the `session.reconnect` policy), and a mid-call death past that budget fails the call rather than switching voices mid-conversation.
|
|
121
|
+
- **Observable.** Each advance emits `provider.fallback` (`{ from, to, error }`) on the session — count these to alarm on a degraded primary.
|
|
122
|
+
- **Voices don't cross vendors.** Configure the voice per factory (`openaiRealtime({ voice: 'marin' })`, `xaiRealtime({ voice: 'eve' })`) rather than on the `Agent` — an OpenAI voice name would fail the xAI/Gemini connect and the chain would skip past a healthy provider.
|
|
123
|
+
- **Latency.** Each dead provider costs up to its `connectTimeoutMs` (default 10s) before the next is tried — set a tighter one on the primary if its endpoint tends to hang rather than refuse. A [pre-synthesized greeting](#pre-synthesized-greeting-15s-to-first-word) bursts onto the line before any handshake, so the caller hears a voice while the chain walks.
|
|
124
|
+
|
|
125
|
+
Testing it: `FakeOpenAIServer.start({ refuseConnections: true })` gives you a provider that is "down", and `{ rejectUpgrade: { status: 401, body: 'invalid_api_key' } }` one that rejects like a real auth/quota failure (both flippable at runtime to script recoveries) — see `src/bridge/fallback.test.ts` for ready-made scenarios.
|
|
126
|
+
|
|
107
127
|
## Tools: Zod schemas + execution strategies
|
|
108
128
|
|
|
109
129
|
```ts
|
|
@@ -193,6 +213,41 @@ session: {
|
|
|
193
213
|
|
|
194
214
|
Blocked attempts emit `interruption.blocked` with a cause (`guard` | `rate_limit` | `tool_running` | …). Honored ones flush Twilio, truncate the model's context to the heard milliseconds, and emit `playback.interrupted` with exactly how much the caller heard.
|
|
195
215
|
|
|
216
|
+
## Noise-adaptive VAD (opt-in)
|
|
217
|
+
|
|
218
|
+
Server VAD tuned for quiet rooms misfires on noisy lines — street, car, speakerphone — as phantom barge-ins and chopped replies. `noiseAdaptiveVad` measures the line itself: a per-frame μ-law meter estimates the caller's noise floor (a low percentile over a sliding window, so speech doesn't read as noise), and when it stays high, the bridge escalates turn detection mid-call.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
session: {
|
|
222
|
+
vad: { type: 'server', threshold: 0.5 },
|
|
223
|
+
noiseAdaptiveVad: {}, // {} = defaults below
|
|
224
|
+
// mode: 'auto', // 'suggest' = events only, you apply
|
|
225
|
+
// noiseFloorDb: -45, // trigger floor, dBFS
|
|
226
|
+
// windowMs: 5000, sustainMs: 3000, // how much/how long analyzed audio
|
|
227
|
+
// cooldownMs: 15_000, maxSteps: 1, // escalation pacing (per call)
|
|
228
|
+
// thresholdStep: 0.1, maxThreshold: 0.9,
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
How a step lands, per provider:
|
|
233
|
+
|
|
234
|
+
| Provider | Escalation |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| OpenAI | `threshold` +0.1/step, auto-applied via ack-gated `session.update`; baseline = your `vad.threshold`, else OpenAI's documented 0.5 |
|
|
237
|
+
| xAI | same, from xAI's documented **0.85** default, clamped to its 0.1–0.9 range |
|
|
238
|
+
| Gemini | `vad.suggestion` event only (`startSensitivity: 'low'` analog) — the Live API has no mid-session config updates |
|
|
239
|
+
| semantic VAD | `vad.suggestion` event only: `eagerness: 'low'` trades **end-of-turn latency** (waits up to ~8s) for stability, so that call is yours |
|
|
240
|
+
|
|
241
|
+
The baseline comes from the provider's ACKnowledged effective config plus its declared `capabilities.vadTuning` profile — never guessed (assuming 0.5 on xAI would *lower* its 0.85 default). Escalation is one-way per call: steps up, never back down; `maxSteps` and `cooldownMs` bound the blast radius.
|
|
242
|
+
|
|
243
|
+
Every decision emits `vad.suggestion` (recommended config + `noiseFloorDb`/`analyzedMs`/`elapsedMs` metrics); an applied-and-acknowledged step also emits `vad.adjusted`. In `mode: 'suggest'` nothing is applied automatically — accept with `session.updateVad(info.suggested)`, which persists across reconnects and rebases future escalation on top of it. `updateVad(null)` disables turn detection AND suspends adaptation (it never re-enables VAD by itself).
|
|
244
|
+
|
|
245
|
+
Fine print:
|
|
246
|
+
|
|
247
|
+
- Caller speech, agent playback (speakerphone bleed), and the pre-synthesized greeting are excluded from the floor estimate — a long monologue in a quiet room never escalates. `windowMs` counts **analyzed** idle-line audio, so warmup can span 30–60s of real conversation; the metrics exist to tune this from field data.
|
|
248
|
+
- Enabling the feature also serializes ALL mid-call session updates for that session: one in flight, acknowledged before the next; an ack timeout reconnects into known-good state. Disabled = the legacy fire-and-forget behavior, untouched.
|
|
249
|
+
- Complementary knobs: OpenAI's native `audio.input.noise_reduction` (reachable via the provider's `sessionOptions`) runs before VAD and may fix much of the problem upstream; `interruptions.rateLimit` reacts to barge-in churn after the fact, while this reacts to the audio itself. All three coexist.
|
|
250
|
+
|
|
196
251
|
## Pre-synthesized greeting (~1.5s to first word)
|
|
197
252
|
|
|
198
253
|
The slowest part of answering is the provider handshake. Pre-record the greeting once, and the bridge burst-writes it onto the call **while the session is still connecting** — then keeps the model from greeting twice (instruction reinforcement + assistant-turn seeding + suppressed auto-greet) and gates caller audio until Twilio's mark confirms playout.
|
|
@@ -217,7 +272,7 @@ Bundled presets (all synthesized, license-free, seamless loops): `elevator-jazz`
|
|
|
217
272
|
|
|
218
273
|
## Events (session)
|
|
219
274
|
|
|
220
|
-
`call.started/ended/failed` · `provider.connected/reconnecting/reconnected/closed` · `agent.speech.started/ended` (generation) · **`playback.started/finished/interrupted`** (what the caller heard, mark-confirmed) · `user.speech.started/ended` · `transcript.user/agent` · `tool.started/completed/failed` · `tool.approval.required` · `agent.handoff` · `interruption` / `interruption.blocked` · `background_audio.started/stopped` · `dtmf` · `usage.updated` · `error`.
|
|
275
|
+
`call.started/ended/failed` · `provider.connected/fallback/reconnecting/reconnected/closed` · `agent.speech.started/ended` (generation) · **`playback.started/finished/interrupted`** (what the caller heard, mark-confirmed) · `user.speech.started/ended` · `transcript.user/agent` · `tool.started/completed/failed` · `tool.approval.required` · `agent.handoff` · `interruption` / `interruption.blocked` · `vad.suggestion` / `vad.adjusted` (noise-adaptive VAD) · `background_audio.started/stopped` · `dtmf` · `usage.updated` · `error`.
|
|
221
276
|
|
|
222
277
|
```ts
|
|
223
278
|
bridge.on('session.started', (session) => {
|
|
@@ -244,6 +299,7 @@ session: {
|
|
|
244
299
|
reconnect: { maxAttempts: 5, initialDelayMs: 250, maxDelayMs: 8000, jitter: true },
|
|
245
300
|
hangup: { markTimeoutMs: 7000 }, // goodbye watchdog
|
|
246
301
|
vad: undefined, // normalized VAD, mapped per provider
|
|
302
|
+
noiseAdaptiveVad: undefined, // opt-in noise → VAD escalation ({} enables; see its section)
|
|
247
303
|
toolResultDelivery: 'afterPlayback', // or 'immediate'
|
|
248
304
|
toolBackgroundAudio: undefined, // default hold audio for tools
|
|
249
305
|
handoffVoicePolicy: 'keep', // or 'reconnect' to switch voices
|
|
@@ -260,7 +316,7 @@ Outbound calls: the greeting waits for a human — feed your status callback int
|
|
|
260
316
|
`realtime-voice-agents/testing` ships the harness this package is tested with:
|
|
261
317
|
|
|
262
318
|
- **`FakeTwilioMediaStream`** — a scripted caller with an exact playout simulation: marks echo only after the media before them "plays"; `clear` discards buffered audio and echoes pending marks, like real Twilio.
|
|
263
|
-
- **`FakeOpenAIServer`** — a real-WebSocket GA-protocol server you script (`sendAudioResponse`, `sendToolCall`, `sendSpeechStarted`, drops).
|
|
319
|
+
- **`FakeOpenAIServer`** — a real-WebSocket GA-protocol server you script (`sendAudioResponse`, `sendToolCall`, `sendSpeechStarted`, drops, `refuseConnections` for down-provider/fallback scenarios).
|
|
264
320
|
- **`FakeGeminiLive`** — a scripted `@google/genai` seam for the Gemini provider.
|
|
265
321
|
|
|
266
322
|
```ts
|
|
@@ -17,6 +17,21 @@ declare const noopLogger: Logger;
|
|
|
17
17
|
declare function consoleLogger(context?: Record<string, unknown>): Logger;
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/providers/base/capabilities.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* The provider's server-VAD tuning envelope. Declared by provider factories
|
|
22
|
+
* (never by the generic OpenAI-compatible base — an arbitrary compatible
|
|
23
|
+
* service must not silently inherit OpenAI's defaults). Noise-adaptive VAD
|
|
24
|
+
* refuses to invent a threshold baseline: absent both this profile and an
|
|
25
|
+
* explicit configured threshold, the numeric ladder stays unavailable.
|
|
26
|
+
*/
|
|
27
|
+
interface VadTuningProfile {
|
|
28
|
+
/** The provider's effective server-VAD threshold when none is configured. */
|
|
29
|
+
defaultServerThreshold?: number;
|
|
30
|
+
/** Lowest threshold the provider accepts (clamp target). */
|
|
31
|
+
minServerThreshold?: number;
|
|
32
|
+
/** Highest threshold the provider accepts (clamp target). */
|
|
33
|
+
maxServerThreshold?: number;
|
|
34
|
+
}
|
|
20
35
|
/**
|
|
21
36
|
* Capability flags per provider. The engine branches on these — never on a
|
|
22
37
|
* provider's name — so new providers slot in without touching session logic.
|
|
@@ -43,6 +58,11 @@ interface ProviderCapabilities {
|
|
|
43
58
|
* Optional so existing third-party providers keep compiling (absent = false).
|
|
44
59
|
*/
|
|
45
60
|
vadInterruptControl?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Server-VAD threshold envelope for noise-adaptive tuning. Optional; absent
|
|
63
|
+
* means the provider declared none (see VadTuningProfile).
|
|
64
|
+
*/
|
|
65
|
+
vadTuning?: VadTuningProfile;
|
|
46
66
|
}
|
|
47
67
|
//#endregion
|
|
48
68
|
//#region src/providers/base/events.d.ts
|
|
@@ -182,6 +202,13 @@ interface ProviderSessionInit {
|
|
|
182
202
|
* (documented fallback: guard protects buffered audio only).
|
|
183
203
|
*/
|
|
184
204
|
bridgeOwnsInterruptions?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Serialize mid-session updates: one session.update in flight at a time,
|
|
207
|
+
* each acknowledged (or timed out) before the next is sent. Set by the
|
|
208
|
+
* bridge when noise-adaptive VAD is configured; absent/false keeps the
|
|
209
|
+
* legacy fire-and-forget update behavior exactly as before.
|
|
210
|
+
*/
|
|
211
|
+
serializedSessionUpdates?: boolean;
|
|
185
212
|
/** Provider-native session options, deep-merged last (escape hatch). */
|
|
186
213
|
providerOptions?: Record<string, unknown>;
|
|
187
214
|
}
|
|
@@ -194,6 +221,15 @@ interface SendTextOptions {
|
|
|
194
221
|
interface SendToolResultOptions {
|
|
195
222
|
triggerResponse?: boolean;
|
|
196
223
|
}
|
|
224
|
+
interface SessionUpdateOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Resolve with the provider's acknowledgement of THIS update (`true`) rather
|
|
227
|
+
* than at send time. Meaningful only with serialized session updates (see
|
|
228
|
+
* `ProviderSessionInit.serializedSessionUpdates`); providers without ack
|
|
229
|
+
* support resolve `false`/`void`, which callers treat as not-acknowledged.
|
|
230
|
+
*/
|
|
231
|
+
awaitAck?: boolean;
|
|
232
|
+
}
|
|
197
233
|
/**
|
|
198
234
|
* The provider contract: μ-law in, μ-law out, normalized events.
|
|
199
235
|
* Transcoding (when the provider speaks PCM) lives inside the provider, so
|
|
@@ -217,8 +253,22 @@ declare abstract class BaseRealtimeProvider extends TypedEmitter<ProviderEvents>
|
|
|
217
253
|
abstract createResponse(options?: {
|
|
218
254
|
instructions?: string;
|
|
219
255
|
}): void;
|
|
220
|
-
/**
|
|
221
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Mid-session config change (instructions/tools/voice/vad) where supported.
|
|
258
|
+
* Return `true` to signal the provider ACKNOWLEDGED the update — the
|
|
259
|
+
* noise-adaptive VAD auto-commit requires it. The `boolean | void` return
|
|
260
|
+
* keeps existing third-party `Promise<void>` implementations compiling;
|
|
261
|
+
* anything other than `true` is treated as not-acknowledged.
|
|
262
|
+
*/
|
|
263
|
+
abstract updateSession(patch: Partial<ProviderSessionInit>, options?: SessionUpdateOptions): Promise<boolean | void>;
|
|
264
|
+
/**
|
|
265
|
+
* The last ACKNOWLEDGED turn-detection config on the live session — what was
|
|
266
|
+
* actually sent and confirmed, never desired/pending state. Undefined until
|
|
267
|
+
* the provider reports one (third-party providers may never set it).
|
|
268
|
+
*/
|
|
269
|
+
getEffectiveVad(): VadConfig | null | undefined;
|
|
270
|
+
/** Set by subclasses when a config carrying `vad` is acknowledged. */
|
|
271
|
+
protected effectiveVadValue: VadConfig | null | undefined;
|
|
222
272
|
/** Interrupt in-flight generation (where the wire protocol supports it). */
|
|
223
273
|
cancelResponse(): void;
|
|
224
274
|
/** Trim the last assistant item to what the caller actually heard. */
|
|
@@ -236,4 +286,4 @@ interface ProviderFactoryContext {
|
|
|
236
286
|
/** A fresh provider per call; connection config is captured in the factory closure. */
|
|
237
287
|
type ProviderFactory = (context: ProviderFactoryContext) => BaseRealtimeProvider;
|
|
238
288
|
//#endregion
|
|
239
|
-
export {
|
|
289
|
+
export { Logger as _, ProviderToolSchema as a, SessionUpdateOptions as c, ProviderCloseInfo as d, ProviderEvents as f, VadTuningProfile as g, ProviderCapabilities as h, ProviderSessionInit as i, VadConfig as l, ProviderUsage as m, ProviderFactory as n, SendTextOptions as o, ProviderToolCall as p, ProviderFactoryContext as r, SendToolResultOptions as s, BaseRealtimeProvider as t, ProviderAudioDelta as u, consoleLogger as v, noopLogger as y };
|
|
@@ -46,6 +46,16 @@ function deepMerge(base, patch) {
|
|
|
46
46
|
* instance — the session layer owns reconnect policy and context re-injection.
|
|
47
47
|
*/
|
|
48
48
|
var BaseRealtimeProvider = class extends require_events.TypedEmitter {
|
|
49
|
+
/**
|
|
50
|
+
* The last ACKNOWLEDGED turn-detection config on the live session — what was
|
|
51
|
+
* actually sent and confirmed, never desired/pending state. Undefined until
|
|
52
|
+
* the provider reports one (third-party providers may never set it).
|
|
53
|
+
*/
|
|
54
|
+
getEffectiveVad() {
|
|
55
|
+
return this.effectiveVadValue;
|
|
56
|
+
}
|
|
57
|
+
/** Set by subclasses when a config carrying `vad` is acknowledged. */
|
|
58
|
+
effectiveVadValue = void 0;
|
|
49
59
|
/** Interrupt in-flight generation (where the wire protocol supports it). */
|
|
50
60
|
cancelResponse() {}
|
|
51
61
|
/** Trim the last assistant item to what the caller actually heard. */
|
|
@@ -46,6 +46,16 @@ function deepMerge(base, patch) {
|
|
|
46
46
|
* instance — the session layer owns reconnect policy and context re-injection.
|
|
47
47
|
*/
|
|
48
48
|
var BaseRealtimeProvider = class extends TypedEmitter {
|
|
49
|
+
/**
|
|
50
|
+
* The last ACKNOWLEDGED turn-detection config on the live session — what was
|
|
51
|
+
* actually sent and confirmed, never desired/pending state. Undefined until
|
|
52
|
+
* the provider reports one (third-party providers may never set it).
|
|
53
|
+
*/
|
|
54
|
+
getEffectiveVad() {
|
|
55
|
+
return this.effectiveVadValue;
|
|
56
|
+
}
|
|
57
|
+
/** Set by subclasses when a config carrying `vad` is acknowledged. */
|
|
58
|
+
effectiveVadValue = void 0;
|
|
49
59
|
/** Interrupt in-flight generation (where the wire protocol supports it). */
|
|
50
60
|
cancelResponse() {}
|
|
51
61
|
/** Trim the last assistant item to what the caller actually heard. */
|
|
@@ -17,6 +17,21 @@ declare const noopLogger: Logger;
|
|
|
17
17
|
declare function consoleLogger(context?: Record<string, unknown>): Logger;
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/providers/base/capabilities.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* The provider's server-VAD tuning envelope. Declared by provider factories
|
|
22
|
+
* (never by the generic OpenAI-compatible base — an arbitrary compatible
|
|
23
|
+
* service must not silently inherit OpenAI's defaults). Noise-adaptive VAD
|
|
24
|
+
* refuses to invent a threshold baseline: absent both this profile and an
|
|
25
|
+
* explicit configured threshold, the numeric ladder stays unavailable.
|
|
26
|
+
*/
|
|
27
|
+
interface VadTuningProfile {
|
|
28
|
+
/** The provider's effective server-VAD threshold when none is configured. */
|
|
29
|
+
defaultServerThreshold?: number;
|
|
30
|
+
/** Lowest threshold the provider accepts (clamp target). */
|
|
31
|
+
minServerThreshold?: number;
|
|
32
|
+
/** Highest threshold the provider accepts (clamp target). */
|
|
33
|
+
maxServerThreshold?: number;
|
|
34
|
+
}
|
|
20
35
|
/**
|
|
21
36
|
* Capability flags per provider. The engine branches on these — never on a
|
|
22
37
|
* provider's name — so new providers slot in without touching session logic.
|
|
@@ -43,6 +58,11 @@ interface ProviderCapabilities {
|
|
|
43
58
|
* Optional so existing third-party providers keep compiling (absent = false).
|
|
44
59
|
*/
|
|
45
60
|
vadInterruptControl?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Server-VAD threshold envelope for noise-adaptive tuning. Optional; absent
|
|
63
|
+
* means the provider declared none (see VadTuningProfile).
|
|
64
|
+
*/
|
|
65
|
+
vadTuning?: VadTuningProfile;
|
|
46
66
|
}
|
|
47
67
|
//#endregion
|
|
48
68
|
//#region src/providers/base/events.d.ts
|
|
@@ -182,6 +202,13 @@ interface ProviderSessionInit {
|
|
|
182
202
|
* (documented fallback: guard protects buffered audio only).
|
|
183
203
|
*/
|
|
184
204
|
bridgeOwnsInterruptions?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Serialize mid-session updates: one session.update in flight at a time,
|
|
207
|
+
* each acknowledged (or timed out) before the next is sent. Set by the
|
|
208
|
+
* bridge when noise-adaptive VAD is configured; absent/false keeps the
|
|
209
|
+
* legacy fire-and-forget update behavior exactly as before.
|
|
210
|
+
*/
|
|
211
|
+
serializedSessionUpdates?: boolean;
|
|
185
212
|
/** Provider-native session options, deep-merged last (escape hatch). */
|
|
186
213
|
providerOptions?: Record<string, unknown>;
|
|
187
214
|
}
|
|
@@ -194,6 +221,15 @@ interface SendTextOptions {
|
|
|
194
221
|
interface SendToolResultOptions {
|
|
195
222
|
triggerResponse?: boolean;
|
|
196
223
|
}
|
|
224
|
+
interface SessionUpdateOptions {
|
|
225
|
+
/**
|
|
226
|
+
* Resolve with the provider's acknowledgement of THIS update (`true`) rather
|
|
227
|
+
* than at send time. Meaningful only with serialized session updates (see
|
|
228
|
+
* `ProviderSessionInit.serializedSessionUpdates`); providers without ack
|
|
229
|
+
* support resolve `false`/`void`, which callers treat as not-acknowledged.
|
|
230
|
+
*/
|
|
231
|
+
awaitAck?: boolean;
|
|
232
|
+
}
|
|
197
233
|
/**
|
|
198
234
|
* The provider contract: μ-law in, μ-law out, normalized events.
|
|
199
235
|
* Transcoding (when the provider speaks PCM) lives inside the provider, so
|
|
@@ -217,8 +253,22 @@ declare abstract class BaseRealtimeProvider extends TypedEmitter<ProviderEvents>
|
|
|
217
253
|
abstract createResponse(options?: {
|
|
218
254
|
instructions?: string;
|
|
219
255
|
}): void;
|
|
220
|
-
/**
|
|
221
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Mid-session config change (instructions/tools/voice/vad) where supported.
|
|
258
|
+
* Return `true` to signal the provider ACKNOWLEDGED the update — the
|
|
259
|
+
* noise-adaptive VAD auto-commit requires it. The `boolean | void` return
|
|
260
|
+
* keeps existing third-party `Promise<void>` implementations compiling;
|
|
261
|
+
* anything other than `true` is treated as not-acknowledged.
|
|
262
|
+
*/
|
|
263
|
+
abstract updateSession(patch: Partial<ProviderSessionInit>, options?: SessionUpdateOptions): Promise<boolean | void>;
|
|
264
|
+
/**
|
|
265
|
+
* The last ACKNOWLEDGED turn-detection config on the live session — what was
|
|
266
|
+
* actually sent and confirmed, never desired/pending state. Undefined until
|
|
267
|
+
* the provider reports one (third-party providers may never set it).
|
|
268
|
+
*/
|
|
269
|
+
getEffectiveVad(): VadConfig | null | undefined;
|
|
270
|
+
/** Set by subclasses when a config carrying `vad` is acknowledged. */
|
|
271
|
+
protected effectiveVadValue: VadConfig | null | undefined;
|
|
222
272
|
/** Interrupt in-flight generation (where the wire protocol supports it). */
|
|
223
273
|
cancelResponse(): void;
|
|
224
274
|
/** Trim the last assistant item to what the caller actually heard. */
|
|
@@ -236,4 +286,4 @@ interface ProviderFactoryContext {
|
|
|
236
286
|
/** A fresh provider per call; connection config is captured in the factory closure. */
|
|
237
287
|
type ProviderFactory = (context: ProviderFactoryContext) => BaseRealtimeProvider;
|
|
238
288
|
//#endregion
|
|
239
|
-
export {
|
|
289
|
+
export { Logger as _, ProviderToolSchema as a, SessionUpdateOptions as c, ProviderCloseInfo as d, ProviderEvents as f, VadTuningProfile as g, ProviderCapabilities as h, ProviderSessionInit as i, VadConfig as l, ProviderUsage as m, ProviderFactory as n, SendTextOptions as o, ProviderToolCall as p, ProviderFactoryContext as r, SendToolResultOptions as s, BaseRealtimeProvider as t, ProviderAudioDelta as u, consoleLogger as v, noopLogger as y };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as Logger, h as ProviderCapabilities, i as ProviderSessionInit, l as VadConfig, o as SendTextOptions, s as SendToolResultOptions, t as BaseRealtimeProvider } from "./BaseRealtimeProvider-BL75_HHh.cjs";
|
|
2
2
|
//#region src/providers/gemini/GeminiLiveProvider.d.ts
|
|
3
3
|
/** Structural slice of @google/genai's live session (also what fakes implement). */
|
|
4
4
|
interface GeminiLiveSessionLike {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as Logger, h as ProviderCapabilities, i as ProviderSessionInit, l as VadConfig, o as SendTextOptions, s as SendToolResultOptions, t as BaseRealtimeProvider } from "./BaseRealtimeProvider-CWJ81HIt.mjs";
|
|
2
2
|
//#region src/providers/gemini/GeminiLiveProvider.d.ts
|
|
3
3
|
/** Structural slice of @google/genai's live session (also what fakes implement). */
|
|
4
4
|
interface GeminiLiveSessionLike {
|
package/dist/{OpenAICompatibleProvider-Bdtl-UXH.mjs → OpenAICompatibleProvider-D-2OOVBU.mjs}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as noopLogger, t as BaseRealtimeProvider } from "./BaseRealtimeProvider-
|
|
2
|
-
import { t as buildSessionUpdate } from "./session-config-
|
|
1
|
+
import { a as noopLogger, t as BaseRealtimeProvider } from "./BaseRealtimeProvider-C9C3s8jx.mjs";
|
|
2
|
+
import { t as buildSessionUpdate } from "./session-config-CbifLlkV.mjs";
|
|
3
3
|
import WebSocket$1 from "ws";
|
|
4
4
|
//#region src/providers/openai-compatible/OpenAICompatibleProvider.ts
|
|
5
5
|
/**
|
|
@@ -41,6 +41,10 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
41
41
|
responseActive = false;
|
|
42
42
|
pendingCreate = null;
|
|
43
43
|
lastCreateSent = null;
|
|
44
|
+
/** Serialized-update pipeline (enabled via init.serializedSessionUpdates). */
|
|
45
|
+
serializedUpdates = false;
|
|
46
|
+
updateQueue = [];
|
|
47
|
+
updateInFlight = null;
|
|
44
48
|
constructor(config, logger = noopLogger) {
|
|
45
49
|
super();
|
|
46
50
|
this.config = config;
|
|
@@ -62,14 +66,11 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
62
66
|
}
|
|
63
67
|
async connect(init) {
|
|
64
68
|
if (this.ws) await this.close();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
interruptResponse: false,
|
|
68
|
-
...vad ?? { type: "server" }
|
|
69
|
-
};
|
|
69
|
+
this.failPendingUpdates();
|
|
70
|
+
this.serializedUpdates = init.serializedSessionUpdates === true;
|
|
70
71
|
this.sessionInit = {
|
|
71
72
|
...init,
|
|
72
|
-
vad,
|
|
73
|
+
vad: this.resolveVad(init.vad, init.bridgeOwnsInterruptions),
|
|
73
74
|
transcription: init.transcription !== void 0 ? init.transcription : this.config.defaultTranscription
|
|
74
75
|
};
|
|
75
76
|
this.intentionalClose = false;
|
|
@@ -148,6 +149,7 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
148
149
|
ws.on("close", (code, reasonBuf) => {
|
|
149
150
|
const reason = reasonBuf?.toString();
|
|
150
151
|
this.ready = false;
|
|
152
|
+
this.failPendingUpdates();
|
|
151
153
|
if (!settled) {
|
|
152
154
|
fail(/* @__PURE__ */ new Error(`${this.name} socket closed during setup (${code} ${reason ?? ""})`));
|
|
153
155
|
return;
|
|
@@ -159,6 +161,7 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
159
161
|
});
|
|
160
162
|
});
|
|
161
163
|
});
|
|
164
|
+
this.effectiveVadValue = this.sessionInit.vad;
|
|
162
165
|
}
|
|
163
166
|
async close() {
|
|
164
167
|
this.intentionalClose = true;
|
|
@@ -239,13 +242,87 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
239
242
|
...create.instructions ? { response: { instructions: create.instructions } } : {}
|
|
240
243
|
});
|
|
241
244
|
}
|
|
242
|
-
async updateSession(patch) {
|
|
245
|
+
async updateSession(patch, options = {}) {
|
|
243
246
|
if (!this.sessionInit) throw new Error("updateSession before connect");
|
|
247
|
+
let resolved = patch;
|
|
248
|
+
if ("vad" in patch) resolved = {
|
|
249
|
+
...patch,
|
|
250
|
+
vad: this.resolveVad(patch.vad, this.sessionInit.bridgeOwnsInterruptions)
|
|
251
|
+
};
|
|
244
252
|
this.sessionInit = {
|
|
245
253
|
...this.sessionInit,
|
|
246
|
-
...
|
|
254
|
+
...resolved
|
|
255
|
+
};
|
|
256
|
+
if (!this.serializedUpdates) {
|
|
257
|
+
const open = this.ws?.readyState === WebSocket$1.OPEN;
|
|
258
|
+
this.send(this.buildSessionPayload());
|
|
259
|
+
return open;
|
|
260
|
+
}
|
|
261
|
+
let resolveSent;
|
|
262
|
+
let resolveAck;
|
|
263
|
+
const sent = new Promise((resolve) => resolveSent = resolve);
|
|
264
|
+
const acked = new Promise((resolve) => resolveAck = resolve);
|
|
265
|
+
this.updateQueue.push({
|
|
266
|
+
payload: this.buildSessionPayload(),
|
|
267
|
+
touchesVad: "vad" in resolved,
|
|
268
|
+
resolvedVad: resolved.vad,
|
|
269
|
+
resolveSent,
|
|
270
|
+
resolveAck,
|
|
271
|
+
ackTimer: null
|
|
272
|
+
});
|
|
273
|
+
this.pumpUpdateQueue();
|
|
274
|
+
return options.awaitAck ? acked : sent;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Bridge-owned barge-in: the server must NOT auto-cancel the active
|
|
278
|
+
* response on speech onset, or a guard-blocked interruption still kills
|
|
279
|
+
* the sentence mid-air. An explicit vad.interruptResponse wins (spread
|
|
280
|
+
* order). Applied on connect AND on every vad patch.
|
|
281
|
+
*/
|
|
282
|
+
resolveVad(vad, bridgeOwnsInterruptions) {
|
|
283
|
+
let resolved = vad !== void 0 ? vad : this.config.defaultVad;
|
|
284
|
+
if (bridgeOwnsInterruptions && this.capabilities.vadInterruptControl && resolved !== null) resolved = {
|
|
285
|
+
interruptResponse: false,
|
|
286
|
+
...resolved ?? { type: "server" }
|
|
247
287
|
};
|
|
248
|
-
|
|
288
|
+
return resolved;
|
|
289
|
+
}
|
|
290
|
+
/** Send the next queued update once nothing is awaiting its ack. */
|
|
291
|
+
pumpUpdateQueue() {
|
|
292
|
+
if (this.updateInFlight || this.updateQueue.length === 0) return;
|
|
293
|
+
if (this.ws?.readyState !== WebSocket$1.OPEN) {
|
|
294
|
+
this.failPendingUpdates();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const entry = this.updateQueue.shift();
|
|
298
|
+
this.updateInFlight = entry;
|
|
299
|
+
this.send(entry.payload);
|
|
300
|
+
entry.resolveSent(true);
|
|
301
|
+
const timeoutMs = this.config.sessionUpdateAckTimeoutMs ?? 3e3;
|
|
302
|
+
entry.ackTimer = setTimeout(() => {
|
|
303
|
+
entry.ackTimer = null;
|
|
304
|
+
if (this.updateInFlight !== entry) return;
|
|
305
|
+
this.updateInFlight = null;
|
|
306
|
+
entry.resolveAck(false);
|
|
307
|
+
this.logger.warn("session.update not acknowledged in time — desyncing the connection", { timeoutMs });
|
|
308
|
+
this.ws?.terminate();
|
|
309
|
+
}, timeoutMs);
|
|
310
|
+
entry.ackTimer.unref?.();
|
|
311
|
+
}
|
|
312
|
+
/** Settle every queued/in-flight update as not-acked (socket gone/replaced). */
|
|
313
|
+
failPendingUpdates() {
|
|
314
|
+
const inFlight = this.updateInFlight;
|
|
315
|
+
this.updateInFlight = null;
|
|
316
|
+
if (inFlight) {
|
|
317
|
+
if (inFlight.ackTimer) clearTimeout(inFlight.ackTimer);
|
|
318
|
+
inFlight.resolveAck(false);
|
|
319
|
+
}
|
|
320
|
+
const queued = this.updateQueue;
|
|
321
|
+
this.updateQueue = [];
|
|
322
|
+
for (const entry of queued) {
|
|
323
|
+
entry.resolveSent(false);
|
|
324
|
+
entry.resolveAck(false);
|
|
325
|
+
}
|
|
249
326
|
}
|
|
250
327
|
buildSessionPayload() {
|
|
251
328
|
return (this.config.buildSession ?? buildSessionUpdate)(this.sessionInit, {
|
|
@@ -267,6 +344,16 @@ var OpenAICompatibleProvider = class extends BaseRealtimeProvider {
|
|
|
267
344
|
}
|
|
268
345
|
handleEvent(event) {
|
|
269
346
|
switch (event.type) {
|
|
347
|
+
case "session.updated": {
|
|
348
|
+
const entry = this.updateInFlight;
|
|
349
|
+
if (!entry) break;
|
|
350
|
+
this.updateInFlight = null;
|
|
351
|
+
if (entry.ackTimer) clearTimeout(entry.ackTimer);
|
|
352
|
+
if (entry.touchesVad) this.effectiveVadValue = entry.resolvedVad;
|
|
353
|
+
entry.resolveAck(true);
|
|
354
|
+
this.pumpUpdateQueue();
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
270
357
|
case "response.created": {
|
|
271
358
|
const responseId = event.response?.id ?? `resp_${Date.now()}`;
|
|
272
359
|
this.currentResponseId = responseId;
|
package/dist/{OpenAICompatibleProvider-NS4cKVQj.cjs → OpenAICompatibleProvider-Mp0Mefbh.cjs}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.cjs");
|
|
2
|
-
const require_BaseRealtimeProvider = require("./BaseRealtimeProvider-
|
|
3
|
-
const require_session_config = require("./session-config-
|
|
2
|
+
const require_BaseRealtimeProvider = require("./BaseRealtimeProvider-C2mRn1V_.cjs");
|
|
3
|
+
const require_session_config = require("./session-config-CqJm2Kxz.cjs");
|
|
4
4
|
let ws = require("ws");
|
|
5
5
|
ws = require_rolldown_runtime.__toESM(ws, 1);
|
|
6
6
|
//#region src/providers/openai-compatible/OpenAICompatibleProvider.ts
|
|
@@ -43,6 +43,10 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
43
43
|
responseActive = false;
|
|
44
44
|
pendingCreate = null;
|
|
45
45
|
lastCreateSent = null;
|
|
46
|
+
/** Serialized-update pipeline (enabled via init.serializedSessionUpdates). */
|
|
47
|
+
serializedUpdates = false;
|
|
48
|
+
updateQueue = [];
|
|
49
|
+
updateInFlight = null;
|
|
46
50
|
constructor(config, logger = require_BaseRealtimeProvider.noopLogger) {
|
|
47
51
|
super();
|
|
48
52
|
this.config = config;
|
|
@@ -64,14 +68,11 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
64
68
|
}
|
|
65
69
|
async connect(init) {
|
|
66
70
|
if (this.ws) await this.close();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
interruptResponse: false,
|
|
70
|
-
...vad ?? { type: "server" }
|
|
71
|
-
};
|
|
71
|
+
this.failPendingUpdates();
|
|
72
|
+
this.serializedUpdates = init.serializedSessionUpdates === true;
|
|
72
73
|
this.sessionInit = {
|
|
73
74
|
...init,
|
|
74
|
-
vad,
|
|
75
|
+
vad: this.resolveVad(init.vad, init.bridgeOwnsInterruptions),
|
|
75
76
|
transcription: init.transcription !== void 0 ? init.transcription : this.config.defaultTranscription
|
|
76
77
|
};
|
|
77
78
|
this.intentionalClose = false;
|
|
@@ -150,6 +151,7 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
150
151
|
ws$1.on("close", (code, reasonBuf) => {
|
|
151
152
|
const reason = reasonBuf?.toString();
|
|
152
153
|
this.ready = false;
|
|
154
|
+
this.failPendingUpdates();
|
|
153
155
|
if (!settled) {
|
|
154
156
|
fail(/* @__PURE__ */ new Error(`${this.name} socket closed during setup (${code} ${reason ?? ""})`));
|
|
155
157
|
return;
|
|
@@ -161,6 +163,7 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
161
163
|
});
|
|
162
164
|
});
|
|
163
165
|
});
|
|
166
|
+
this.effectiveVadValue = this.sessionInit.vad;
|
|
164
167
|
}
|
|
165
168
|
async close() {
|
|
166
169
|
this.intentionalClose = true;
|
|
@@ -241,13 +244,87 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
241
244
|
...create.instructions ? { response: { instructions: create.instructions } } : {}
|
|
242
245
|
});
|
|
243
246
|
}
|
|
244
|
-
async updateSession(patch) {
|
|
247
|
+
async updateSession(patch, options = {}) {
|
|
245
248
|
if (!this.sessionInit) throw new Error("updateSession before connect");
|
|
249
|
+
let resolved = patch;
|
|
250
|
+
if ("vad" in patch) resolved = {
|
|
251
|
+
...patch,
|
|
252
|
+
vad: this.resolveVad(patch.vad, this.sessionInit.bridgeOwnsInterruptions)
|
|
253
|
+
};
|
|
246
254
|
this.sessionInit = {
|
|
247
255
|
...this.sessionInit,
|
|
248
|
-
...
|
|
256
|
+
...resolved
|
|
257
|
+
};
|
|
258
|
+
if (!this.serializedUpdates) {
|
|
259
|
+
const open = this.ws?.readyState === ws.default.OPEN;
|
|
260
|
+
this.send(this.buildSessionPayload());
|
|
261
|
+
return open;
|
|
262
|
+
}
|
|
263
|
+
let resolveSent;
|
|
264
|
+
let resolveAck;
|
|
265
|
+
const sent = new Promise((resolve) => resolveSent = resolve);
|
|
266
|
+
const acked = new Promise((resolve) => resolveAck = resolve);
|
|
267
|
+
this.updateQueue.push({
|
|
268
|
+
payload: this.buildSessionPayload(),
|
|
269
|
+
touchesVad: "vad" in resolved,
|
|
270
|
+
resolvedVad: resolved.vad,
|
|
271
|
+
resolveSent,
|
|
272
|
+
resolveAck,
|
|
273
|
+
ackTimer: null
|
|
274
|
+
});
|
|
275
|
+
this.pumpUpdateQueue();
|
|
276
|
+
return options.awaitAck ? acked : sent;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Bridge-owned barge-in: the server must NOT auto-cancel the active
|
|
280
|
+
* response on speech onset, or a guard-blocked interruption still kills
|
|
281
|
+
* the sentence mid-air. An explicit vad.interruptResponse wins (spread
|
|
282
|
+
* order). Applied on connect AND on every vad patch.
|
|
283
|
+
*/
|
|
284
|
+
resolveVad(vad, bridgeOwnsInterruptions) {
|
|
285
|
+
let resolved = vad !== void 0 ? vad : this.config.defaultVad;
|
|
286
|
+
if (bridgeOwnsInterruptions && this.capabilities.vadInterruptControl && resolved !== null) resolved = {
|
|
287
|
+
interruptResponse: false,
|
|
288
|
+
...resolved ?? { type: "server" }
|
|
249
289
|
};
|
|
250
|
-
|
|
290
|
+
return resolved;
|
|
291
|
+
}
|
|
292
|
+
/** Send the next queued update once nothing is awaiting its ack. */
|
|
293
|
+
pumpUpdateQueue() {
|
|
294
|
+
if (this.updateInFlight || this.updateQueue.length === 0) return;
|
|
295
|
+
if (this.ws?.readyState !== ws.default.OPEN) {
|
|
296
|
+
this.failPendingUpdates();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const entry = this.updateQueue.shift();
|
|
300
|
+
this.updateInFlight = entry;
|
|
301
|
+
this.send(entry.payload);
|
|
302
|
+
entry.resolveSent(true);
|
|
303
|
+
const timeoutMs = this.config.sessionUpdateAckTimeoutMs ?? 3e3;
|
|
304
|
+
entry.ackTimer = setTimeout(() => {
|
|
305
|
+
entry.ackTimer = null;
|
|
306
|
+
if (this.updateInFlight !== entry) return;
|
|
307
|
+
this.updateInFlight = null;
|
|
308
|
+
entry.resolveAck(false);
|
|
309
|
+
this.logger.warn("session.update not acknowledged in time — desyncing the connection", { timeoutMs });
|
|
310
|
+
this.ws?.terminate();
|
|
311
|
+
}, timeoutMs);
|
|
312
|
+
entry.ackTimer.unref?.();
|
|
313
|
+
}
|
|
314
|
+
/** Settle every queued/in-flight update as not-acked (socket gone/replaced). */
|
|
315
|
+
failPendingUpdates() {
|
|
316
|
+
const inFlight = this.updateInFlight;
|
|
317
|
+
this.updateInFlight = null;
|
|
318
|
+
if (inFlight) {
|
|
319
|
+
if (inFlight.ackTimer) clearTimeout(inFlight.ackTimer);
|
|
320
|
+
inFlight.resolveAck(false);
|
|
321
|
+
}
|
|
322
|
+
const queued = this.updateQueue;
|
|
323
|
+
this.updateQueue = [];
|
|
324
|
+
for (const entry of queued) {
|
|
325
|
+
entry.resolveSent(false);
|
|
326
|
+
entry.resolveAck(false);
|
|
327
|
+
}
|
|
251
328
|
}
|
|
252
329
|
buildSessionPayload() {
|
|
253
330
|
return (this.config.buildSession ?? require_session_config.buildSessionUpdate)(this.sessionInit, {
|
|
@@ -269,6 +346,16 @@ var OpenAICompatibleProvider = class extends require_BaseRealtimeProvider.BaseRe
|
|
|
269
346
|
}
|
|
270
347
|
handleEvent(event) {
|
|
271
348
|
switch (event.type) {
|
|
349
|
+
case "session.updated": {
|
|
350
|
+
const entry = this.updateInFlight;
|
|
351
|
+
if (!entry) break;
|
|
352
|
+
this.updateInFlight = null;
|
|
353
|
+
if (entry.ackTimer) clearTimeout(entry.ackTimer);
|
|
354
|
+
if (entry.touchesVad) this.effectiveVadValue = entry.resolvedVad;
|
|
355
|
+
entry.resolveAck(true);
|
|
356
|
+
this.pumpUpdateQueue();
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
272
359
|
case "response.created": {
|
|
273
360
|
const responseId = event.response?.id ?? `resp_${Date.now()}`;
|
|
274
361
|
this.currentResponseId = responseId;
|