@urun-sh/openai 0.5.5 → 0.6.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.
Files changed (56) hide show
  1. package/dist/{ResponsesClient-BYx3YLGo.d.ts → ResponsesClient-CSSrOYD8.d.ts} +7 -1
  2. package/dist/{ResponsesClient-Dft3bg3b.d.cts → ResponsesClient-_OZERUjH.d.cts} +11 -1
  3. package/dist/chunk-3JWYIKHM.js +2 -0
  4. package/dist/chunk-45FJ5GTP.js +7 -0
  5. package/dist/chunk-6M5JK4YC.js +1 -0
  6. package/dist/chunk-QTVTCMJU.js +1 -0
  7. package/dist/chunk-TDRZZGUK.js +1 -0
  8. package/dist/chunk-TPH77ZOW.js +58 -0
  9. package/dist/chunk-WCMX5VOF.js +4 -0
  10. package/dist/chunk-YXK42SKC.js +1 -0
  11. package/dist/gemini-live.cjs +2 -2
  12. package/dist/gemini-live.d.cts +79 -7
  13. package/dist/gemini-live.d.ts +32 -7
  14. package/dist/gemini-live.js +1 -1
  15. package/dist/hosted/bin.cjs +43 -27
  16. package/dist/hosted/bin.js +4 -4
  17. package/dist/hosted/index.cjs +41 -26
  18. package/dist/hosted/index.d.cts +710 -17
  19. package/dist/hosted/index.d.ts +171 -6
  20. package/dist/hosted/index.js +1 -1
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.d.cts +5 -5
  23. package/dist/index.d.ts +5 -5
  24. package/dist/index.js +1 -1
  25. package/dist/{models-NYMZrklp.d.cts → models-DUdx_Y6X.d.cts} +1 -1
  26. package/dist/pi-extension/index.cjs +7 -7
  27. package/dist/pi-extension/index.d.cts +2 -2
  28. package/dist/pi-extension/index.d.ts +2 -2
  29. package/dist/pi-extension/index.js +1 -1
  30. package/dist/pi-extension/standalone.cjs +53 -53
  31. package/dist/proxy/cli.cjs +55 -43
  32. package/dist/proxy/cli.js +14 -13
  33. package/dist/proxy/index.cjs +31 -28
  34. package/dist/proxy/index.d.cts +132 -18
  35. package/dist/proxy/index.d.ts +28 -11
  36. package/dist/proxy/index.js +1 -6
  37. package/dist/responses-turn-BfdBbey8.d.cts +2394 -0
  38. package/dist/responses-turn-CdhIre_a.d.ts +652 -0
  39. package/dist/{translator-CcDBEfvm.d.cts → translator-CO8W_hmJ.d.cts} +3 -3
  40. package/dist/{translator-C9uPKypK.d.ts → translator-Ddd65sXR.d.ts} +1 -1
  41. package/dist/{types-lsVTbNcH.d.cts → types-CHPtJx6d.d.cts} +13 -0
  42. package/dist/{types-lsVTbNcH.d.ts → types-CHPtJx6d.d.ts} +4 -0
  43. package/dist/{video-out-D20UuJ8G.d.cts → video-out-BsuLqlID.d.cts} +6 -6
  44. package/dist/{video-out-CWesbk12.d.ts → video-out-Cawtm7SF.d.ts} +1 -1
  45. package/package.json +14 -11
  46. package/dist/chunk-5BZCM3RS.js +0 -4
  47. package/dist/chunk-5NXM4IO3.js +0 -2
  48. package/dist/chunk-CWJRDBDC.js +0 -1
  49. package/dist/chunk-DMD5UENK.js +0 -1
  50. package/dist/chunk-GBBY3PCZ.js +0 -1
  51. package/dist/chunk-I2Q3B3OG.js +0 -6
  52. package/dist/chunk-OI2OY32M.js +0 -1
  53. package/dist/chunk-QVF7NF7G.js +0 -44
  54. package/dist/responses-turn-KOAoIqZ-.d.ts +0 -200
  55. package/dist/responses-turn-OrO4euEN.d.cts +0 -513
  56. /package/dist/{models-NYMZrklp.d.ts → models-DUdx_Y6X.d.ts} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { Server, IncomingMessage } from 'node:http';
2
- import { a as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-OrO4euEN.cjs';
3
- export { b as ProxyIdentity, c as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-OrO4euEN.cjs';
4
- export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-CcDBEfvm.cjs';
2
+ import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-BfdBbey8.cjs';
3
+ export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-BfdBbey8.cjs';
4
+ export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-CO8W_hmJ.cjs';
5
5
  import { WebSocket } from 'ws';
6
- import '../models-NYMZrklp.cjs';
7
- import '../video-out-D20UuJ8G.cjs';
8
- import '../types-lsVTbNcH.cjs';
6
+ import '../models-DUdx_Y6X.cjs';
7
+ import '../video-out-BsuLqlID.cjs';
8
+ import '../types-CHPtJx6d.cjs';
9
9
 
10
10
  /**
11
11
  * The local OpenAI-compatible proxy — `npx @urun-sh/openai proxy`.
@@ -253,9 +253,19 @@ declare function serveGeminiLiveSocket(ws: WebSocket, call: GeminiLiveCall, regi
253
253
  * `next()` listener, so sibling lanes (Gemini Live, Responses WS) and the
254
254
  * hosted composition keep their own upgrade hooks.
255
255
  * - Clients are resolved/authenticated ASYNCHRONOUSLY BEFORE the upgrade is
256
- * accepted: no 101 is written until `resolver.resolve(req)` settles. A
257
- * thrown error maps `err.status` (default 502) to the HTTP reject;
258
- * 401/403 keep their status.
256
+ * accepted: no 101 is written until `resolver.resolve(req, signal)`
257
+ * settles. A thrown error maps `err.status` (default 502) to the HTTP
258
+ * reject; 401/403 keep their status.
259
+ * - THE CLIENT SOCKET'S OWN CLOSE is the cancellation signal for the wait:
260
+ * a `close`/`error` on the raw pre-upgrade socket aborts the signal the
261
+ * resolver rides. A resolver that settles AFTER the client vanished never
262
+ * upgrades — its binding is released through the binding's own `release`
263
+ * seam (the native detach; a still-queued admission ticket is cancelled
264
+ * underneath it by the backhaul's abort path). A client that closes after
265
+ * admission tears down through the connection's own ws-close wiring.
266
+ * - ONE structured log line per upgrade lifecycle event (`realtime_upgrade`:
267
+ * accept → admit → close, with the binding's session id and the client
268
+ * close reason) — the lane was previously invisible in the process log.
259
269
  * - The resolved binding carries a PRIVATE native uRun session (fresh or
260
270
  * explicitly-resumed) and, iff voice, a PRIVATE audio bridge — never the
261
271
  * pooled tenant lane. The connection's cross-caller claim is invariant
@@ -271,11 +281,16 @@ declare const OPENAI_REALTIME_PATH = "/v1/realtime";
271
281
  * The resolver seam — the ONLY credential, modality, and session-allocation
272
282
  * authority this adapter recognizes. Implemented by Main's composition (and
273
283
  * by tests with an injected fake). Accepted in EITHER form: a bare
274
- * `(req) => Promise<binding>` (the {@link ProxyClientsFor}-style convention)
275
- * or a `{ resolve(req) }` object.
284
+ * `(req, signal) => Promise<binding>` (the {@link ProxyClientsFor}-style
285
+ * convention) or a `{ resolve(req, signal) }` object. `signal` aborts when
286
+ * the CLIENT SOCKET closes/errors before the upgrade is accepted — the
287
+ * resolver SHOULD abandon its admission wait on it (the backhaul's
288
+ * `openAudio` rides the same signal into core `whenLive`'s own abort
289
+ * support); a resolver that ignores it still cannot admit an upgrade to a
290
+ * vanished client: the settled binding is released, never upgraded.
276
291
  */
277
- type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage) => Promise<RealtimeBinding<S>>) | {
278
- resolve(req: IncomingMessage): Promise<RealtimeBinding<S>>;
292
+ type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage, signal?: AbortSignal) => Promise<RealtimeBinding<S>>) | {
293
+ resolve(req: IncomingMessage, signal?: AbortSignal): Promise<RealtimeBinding<S>>;
279
294
  };
280
295
  interface RealtimeBinding<S = unknown> {
281
296
  /**
@@ -291,7 +306,7 @@ interface RealtimeBinding<S = unknown> {
291
306
  * A binding with neither is rejected at upgrade time, LOUD.
292
307
  */
293
308
  session?: S & {
294
- sendResponseCreate?(params: unknown, input: unknown[], requestId: string): AsyncIterable<unknown>;
309
+ sendResponseCreate?(params: unknown, input: unknown[], requestId: string): Promise<AsyncIterable<unknown>>;
295
310
  };
296
311
  clients?: Pick<ProxyClients, 'createResponse'>;
297
312
  /**
@@ -305,6 +320,15 @@ interface RealtimeBinding<S = unknown> {
305
320
  audio: {
306
321
  appendInputAudio(base64Pcm16: string): void;
307
322
  onOutputAudio(handler: (b64: string) => void): () => void;
323
+ /**
324
+ * The pooled uRun session this lane rides (backhaul `openAudio` reports
325
+ * core `Session.id`), when the backhaul knows it — the field the
326
+ * `realtime_upgrade` log line puts on every lifecycle event so an
327
+ * orphaned session is traceable from the proxy log alone. Null is the
328
+ * honest absence (an id-less session reports no id); sessionIdOf
329
+ * normalizes it to the omitted field below.
330
+ */
331
+ sessionId?: string | null;
308
332
  };
309
333
  /** Cross-caller reuse registry (invariant validation; see connection.ts). */
310
334
  openaiRealtimeClaims?: Set<RealtimeBinding['audioSession']>;
@@ -329,9 +353,28 @@ interface RealtimeBinding<S = unknown> {
329
353
  confidence?: number;
330
354
  startMs?: number;
331
355
  }>;
356
+ /**
357
+ * Release THIS attachment when the socket closes (the hosted
358
+ * conversation-factory seam — the twin of the Gemini Live binding's
359
+ * `release`): per-attachment release, native detach underneath, the named
360
+ * session itself stays live. Absent (the local fixed-clients lane) = the
361
+ * pooled sessions are the process's own and outlive the socket.
362
+ */
363
+ release?: () => Promise<void> | void;
332
364
  }
333
365
  interface OpenAIRealtimeOptions<S = unknown> {
334
366
  resolver: OpenAIRealtimeResolver<S>;
367
+ /**
368
+ * Where the per-upgrade structured `realtime_upgrade` lines go (ONE
369
+ * newline-terminated JSON object per lifecycle event: accept → admit →
370
+ * close). Absent, the line goes to this process's stdout through the repo's
371
+ * canonical guarded write — the hosted container's collected log stream.
372
+ * Emitted unconditionally, never behind a verbosity flag: this lane's
373
+ * lifecycle was previously invisible in the process log, which is exactly
374
+ * why an orphaned queued admission could only be diagnosed from the
375
+ * session-server side.
376
+ */
377
+ log?: (line: string) => void;
335
378
  }
336
379
  /**
337
380
  * Attach the OpenAI Realtime WS endpoint to `server`. Upgrades on any other
@@ -390,7 +433,10 @@ declare class OpenAIRealtimeConnection<S = unknown> {
390
433
  constructor(opts: {
391
434
  ws: WebSocket;
392
435
  binding: RealtimeBinding<S>;
393
- onClosed?: (conn: OpenAIRealtimeConnection<S>) => void;
436
+ /** Fired once at teardown, with the reason (the surface's structured close log rides it). */
437
+ onClosed?: (conn: OpenAIRealtimeConnection<S>, info: {
438
+ reason: string;
439
+ }) => void;
394
440
  });
395
441
  /**
396
442
  * Consume the backend's OWN turn/transcript lane (§5 stt stream): the
@@ -422,11 +468,29 @@ declare class OpenAIRealtimeConnection<S = unknown> {
422
468
  * SdkTransport-style) OR `clients.createResponse(params)` (the canonical
423
469
  * ProxyClients seam the shared conversation factory hands out). A binding
424
470
  * with neither was already rejected at upgrade time; this is the loud
425
- * backstop.
471
+ * backstop. The ProxyClients seam is PROMISE-VALUED
472
+ * (`rehomingCreateResponse` dials inside an async function — its
473
+ * private-backhaul cold wake can park the promise for seconds), so it is
474
+ * awaited here; awaiting the SdkTransport generator is a no-op. The cast
475
+ * states the seam's REAL union: typing it sync-iterable is what let the
476
+ * unawaited promise reach `for await` as "R is not async iterable" (the
477
+ * prod-usw2 speak-turn kill, 2026-09-14).
426
478
  */
427
479
  private upstreamStream;
428
480
  private startResponse;
429
- /** Bridge voice OUT frame → `response.output_audio.delta` on the open turn. */
481
+ /**
482
+ * Bridge voice OUT frame → `response.output_audio.delta` on the open turn.
483
+ *
484
+ * OUT-OF-TURN frames split by CONTENT, per the platform's own liveness
485
+ * model (urun-python #1812/#1827): the runtime's egress holds a DECLARED
486
+ * quiet track — `CtxMediaTransport`'s silence keepalive emits Opus silence
487
+ * whenever the app is silent, including before the first response.create —
488
+ * so an all-quiet frame with no open turn is that muted egress, not voice.
489
+ * It is dropped quietly (the same reading as a negotiated muted WebRTC
490
+ * track). A NON-quiet frame with no open turn is real voice the adapter
491
+ * cannot attribute — still the loud, once-per-connection
492
+ * `out_of_turn_audio` error that caught the speak-lane bug class.
493
+ */
430
494
  private onOutputAudioFrame;
431
495
  /** One assistant message item per turn (text and audio parts share it). */
432
496
  private messageItemAdded;
@@ -468,4 +532,54 @@ declare class OpenAIRealtimeConnection<S = unknown> {
468
532
  private teardown;
469
533
  }
470
534
 
471
- export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, serveGeminiLiveSocket };
535
+ /**
536
+ * THE REALTIME BINDING COMPOSITION — the ONE seam every composition rides to
537
+ * turn an upgraded `/v1/realtime` request into a {@link RealtimeBinding}.
538
+ *
539
+ * The modality is read from the CATALOG (`task` stt | tts, invariant across
540
+ * placements) through the SAME router-backed gate seam the Images lane uses
541
+ * (`ProxyClients.supportsAudio` — `ModelRouter.supportsAudio`). A non-audio
542
+ * model is refused LOUDLY with a 404 before anything is opened; a voice
543
+ * binding acquires its PRIVATE audio bridge from `ProxyClients.openAudio`
544
+ * (the pooled session's native `audio` lane at 24 kHz — the ONE canonical
545
+ * session-audio wiring, `transport/media.ts` AUDIO_STREAM). There is no env-var
546
+ * switch and no chat-default fall-through: an absent model resolves through
547
+ * the router exactly like every other lane, and a chat default is refused
548
+ * exactly like an explicitly named chat model.
549
+ *
550
+ * Used by BOTH compositions — the local `urun compat proxy` (fixed clients,
551
+ * optional fixed apiKey) and the hosted multi-tenant endpoint (per-request
552
+ * org resolution + per-conversation private backhauls) — so there is exactly
553
+ * ONE binding implementation and no copy-pasted second handler.
554
+ */
555
+
556
+ /**
557
+ * The realtime lane's model name — the GA `?model=` query parameter
558
+ * (`wss://…/v1/realtime?model=…`). Absent = the router's configured default
559
+ * resolves it (locally the startup app; hosted, a loud 404 — there IS no
560
+ * default app on the hosted lane).
561
+ */
562
+ declare function realtimeModelOf(req: IncomingMessage): string | undefined;
563
+ /**
564
+ * Build the binding for `model` against `clients`:
565
+ * - NO `supportsAudio` seam ⇒ 501 (loud, never a silent allow-all).
566
+ * - {@link UnknownModelError} ⇒ 404 with the router's own loud message.
567
+ * - A non-audio verdict ⇒ 404 — the realtime lane never falls through to
568
+ * the chat lane, in either direction.
569
+ * - A voice verdict with no `openAudio` seam ⇒ 501 (a voice binding cannot
570
+ * be served without its bridge; a voiceless downgrade is never silent).
571
+ */
572
+ declare function realtimeBindingFor(clients: Pick<ProxyClients, 'createResponse'> & Partial<Pick<ProxyClients, 'supportsAudio' | 'openAudio'>>, model: string | undefined, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
573
+ /**
574
+ * The LOCAL lane resolver: fixed pooled clients, optional fixed apiKey (the
575
+ * same shared-secret rule the local Gemini Live lane applies — a mismatched
576
+ * or missing Bearer is a 401; with no configured key the header is not
577
+ * consulted). The HOSTED lane builds its own resolver per request (org
578
+ * resolution + per-conversation private backhaul) and reuses
579
+ * {@link realtimeBindingFor} for the binding itself.
580
+ */
581
+ declare function fixedClientsResolver(clients: ProxyClients, apiKey?: string): {
582
+ resolve(req: IncomingMessage, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
583
+ };
584
+
585
+ export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, fixedClientsResolver, realtimeBindingFor, realtimeModelOf, serveGeminiLiveSocket };
@@ -1,11 +1,11 @@
1
1
  import { Server, IncomingMessage } from 'node:http';
2
- import { a as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-KOAoIqZ-.js';
3
- export { b as ProxyIdentity, c as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-KOAoIqZ-.js';
4
- export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-C9uPKypK.js';
2
+ import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-CdhIre_a.js';
3
+ export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-CdhIre_a.js';
4
+ export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-Ddd65sXR.js';
5
5
  import { WebSocket } from 'ws';
6
- import '../models-NYMZrklp.js';
7
- import '../video-out-CWesbk12.js';
8
- import '../types-lsVTbNcH.js';
6
+ import '../models-DUdx_Y6X.js';
7
+ import '../video-out-Cawtm7SF.js';
8
+ import '../types-CHPtJx6d.js';
9
9
 
10
10
  interface OpenAIProxyOptions extends ProxyHandlerOptions {
11
11
  clients: ProxyClients;
@@ -56,13 +56,13 @@ declare function serveGeminiLiveSocket(ws: WebSocket, call: GeminiLiveCall, regi
56
56
 
57
57
  declare const OPENAI_REALTIME_PATH = "/v1/realtime";
58
58
 
59
- type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage) => Promise<RealtimeBinding<S>>) | {
60
- resolve(req: IncomingMessage): Promise<RealtimeBinding<S>>;
59
+ type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage, signal?: AbortSignal) => Promise<RealtimeBinding<S>>) | {
60
+ resolve(req: IncomingMessage, signal?: AbortSignal): Promise<RealtimeBinding<S>>;
61
61
  };
62
62
  interface RealtimeBinding<S = unknown> {
63
63
 
64
64
  session?: S & {
65
- sendResponseCreate?(params: unknown, input: unknown[], requestId: string): AsyncIterable<unknown>;
65
+ sendResponseCreate?(params: unknown, input: unknown[], requestId: string): Promise<AsyncIterable<unknown>>;
66
66
  };
67
67
  clients?: Pick<ProxyClients, 'createResponse'>;
68
68
 
@@ -72,6 +72,8 @@ interface RealtimeBinding<S = unknown> {
72
72
  audio: {
73
73
  appendInputAudio(base64Pcm16: string): void;
74
74
  onOutputAudio(handler: (b64: string) => void): () => void;
75
+
76
+ sessionId?: string | null;
75
77
  };
76
78
 
77
79
  openaiRealtimeClaims?: Set<RealtimeBinding['audioSession']>;
@@ -87,9 +89,13 @@ interface RealtimeBinding<S = unknown> {
87
89
  confidence?: number;
88
90
  startMs?: number;
89
91
  }>;
92
+
93
+ release?: () => Promise<void> | void;
90
94
  }
91
95
  interface OpenAIRealtimeOptions<S = unknown> {
92
96
  resolver: OpenAIRealtimeResolver<S>;
97
+
98
+ log?: (line: string) => void;
93
99
  }
94
100
 
95
101
  declare function attachOpenAIRealtime<S = unknown>(server: Server, options: OpenAIRealtimeOptions<S>): () => Promise<void>;
@@ -128,7 +134,10 @@ declare class OpenAIRealtimeConnection<S = unknown> {
128
134
  constructor(opts: {
129
135
  ws: WebSocket;
130
136
  binding: RealtimeBinding<S>;
131
- onClosed?: (conn: OpenAIRealtimeConnection<S>) => void;
137
+
138
+ onClosed?: (conn: OpenAIRealtimeConnection<S>, info: {
139
+ reason: string;
140
+ }) => void;
132
141
  });
133
142
 
134
143
  private startTurnLane;
@@ -173,4 +182,12 @@ declare class OpenAIRealtimeConnection<S = unknown> {
173
182
  private teardown;
174
183
  }
175
184
 
176
- export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, serveGeminiLiveSocket };
185
+ declare function realtimeModelOf(req: IncomingMessage): string | undefined;
186
+
187
+ declare function realtimeBindingFor(clients: Pick<ProxyClients, 'createResponse'> & Partial<Pick<ProxyClients, 'supportsAudio' | 'openAudio'>>, model: string | undefined, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
188
+
189
+ declare function fixedClientsResolver(clients: ProxyClients, apiKey?: string): {
190
+ resolve(req: IncomingMessage, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
191
+ };
192
+
193
+ export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, fixedClientsResolver, realtimeBindingFor, realtimeModelOf, serveGeminiLiveSocket };
@@ -1,6 +1 @@
1
- import{a as ee,b as te,c as ne,g as ie}from"../chunk-QVF7NF7G.js";import{b as H,c as K}from"../chunk-DMD5UENK.js";import{a as Z,h as Q}from"../chunk-5NXM4IO3.js";import"../chunk-CWJRDBDC.js";import{c as g}from"../chunk-YSFSRI3D.js";g();g();g();import{WebSocketServer as ce}from"ws";g();g();import{randomUUID as se}from"crypto";function r(a){return`${a}_${se().replace(/-/g,"").slice(0,24)}`}var F=4*1024*1024,$=8*1024*1024,U=512,P=20*1024*1024;function f(a,e,t){return{type:"invalid_request_error",code:a,message:e,param:t??null}}function E(a,e){return{type:"server_error",code:a,message:e}}function m(a){return{type:"error",event_id:r("event"),error:a}}function D(){return{outputModalities:["text"],turnDetection:null,inputAudioTranscription:!1}}var u=class extends Error{constructor(t,i,n="invalid_value"){super(t);this.param=i;this.code=n;this.name="SessionUpdateError"}param;code},oe={model:!0,instructions:!0,modalities:!0,voice:!0,input_audio_format:!0,output_audio_format:!0,temperature:!0,max_output_tokens:!0,max_response_output_tokens:!0,tool_choice:!0,tools:!0,metadata:!0,turn_detection:!0,input_audio_transcription:!0,input_audio_noise_reduction:!0,audio:!0},re={format:!0,transcription:!0,turn_detection:!0,noise_reduction:!0},ae={format:!0,voice:!0,speed:!0};function W(a,e){let t=a;if(!t||typeof t!="object"||t.type!=="audio/pcm"||t.rate!==void 0&&t.rate!==24e3)throw new u(`${e} must be {"type":"audio/pcm","rate":24000} \u2014 the session audio lane is 24 kHz mono PCM16 only`,e)}function S(a,e,t){if(!a||typeof a!="object"||Array.isArray(a))throw new u("session must be an object","session","invalid_request");let i=a,n=Object.keys(i).filter(p=>!(p in oe));if(n.length>0)throw new u(`session field(s) not supported by this backend: ${n.join(", ")}`,n[0],"unsupported_field");let s={...e,outputModalities:[...e.outputModalities]};for(let[p,o]of Object.entries(i))switch(p){case"model":if(o!==null&&typeof o!="string")throw new u("model must be a string or null","model");s.model=o??void 0;break;case"instructions":if(o!==null&&typeof o!="string")throw new u("instructions must be a string or null","instructions");s.instructions=o??void 0;break;case"modalities":{if(!Array.isArray(o)||o.length===0)throw new u("modalities must be a non-empty array","modalities");let l=[];for(let d of o){if(d!=="text"&&d!=="audio")throw new u(`modality ${JSON.stringify(d)} is not "text" or "audio"`,"modalities");if(d==="audio"&&!t.voice)throw new u('modality "audio" refused: the resolver did not provide an authoritative voice binding for the resolved backend \u2014 this model cannot speak on this session',"modalities","audio_not_supported");l.includes(d)||l.push(d)}s.outputModalities=l;break}case"voice":if(o===null){delete s.voice;break}if(typeof o!="string")throw new u("voice must be a string or null","voice");if(!t.voice)throw new u("voice refused: no authoritative voice binding for the resolved backend","voice","audio_not_supported");s.voice=o;break;case"input_audio_format":case"output_audio_format":if(o!=="pcm16")throw new u(`${p} must be "pcm16" \u2014 the session audio lane is 24 kHz mono PCM16 only`,p);break;case"temperature":if(o!==null&&(typeof o!="number"||!Number.isFinite(o)))throw new u("temperature must be a finite number or null","temperature");s.temperature=o??void 0;break;case"max_output_tokens":case"max_response_output_tokens":if(o!==null&&o!=="inf"&&(typeof o!="number"||!Number.isInteger(o)||o<1))throw new u(`${p} must be a positive integer, "inf" or null`,p);s.maxOutputTokens=o===null?void 0:o;break;case"tools":if(!Array.isArray(o))throw new u("tools must be an array","tools");s.tools=o;break;case"tool_choice":s.toolChoice=o;break;case"metadata":if(o!==null&&(!o||typeof o!="object"||Array.isArray(o)))throw new u("metadata must be an object or null","metadata");s.metadata=o??null;break;case"turn_detection":{if(o===null){s.turnDetection=null;break}if(!t.turns)throw new u("turn_detection is refused: the resolved backend does not wire its own turn boundary lane (stt stream with turn_start/turn_end) \u2014 there is no native VAD source to drive responses from. Drive turns manually: input_audio_buffer.commit + response.create.","turn_detection","unsupported_field");let l=o;if(!l||typeof l!="object"||Array.isArray(l))throw new u("turn_detection must be an object or null","turn_detection");let d=String(l.type??"");if(d!=="server_vad"&&d!=="semantic_vad")throw new u(`turn_detection.type ${JSON.stringify(l.type)} is not "server_vad"/"semantic_vad"`,"turn_detection.type");s.turnDetection={createResponse:l.create_response!==!1,interruptResponse:l.interrupt_response!==!1};break}case"input_audio_transcription":{if(o===null){s.inputAudioTranscription=!1;break}if(!t.turns)throw new u("input_audio_transcription is refused: the resolved backend does not wire a transcript lane \u2014 no native transcription source exists behind this session","input_audio_transcription","unsupported_field");if(!o||typeof o!="object"||Array.isArray(o))throw new u("input_audio_transcription must be an object or null","input_audio_transcription");for(let l of Object.keys(o))if(!["model","language","languages","prompt"].includes(l))throw new u(`input_audio_transcription field not honored by this backend: ${l} (transcription comes from the voice engine itself)`,`input_audio_transcription.${l}`,"unsupported_field");s.inputAudioTranscription=!0;break}case"input_audio_noise_reduction":if(o!==null)throw new u("input_audio_noise_reduction must be null on this backend: no noise-reduction primitive exists behind the session audio bridge","input_audio_noise_reduction","unsupported_field");break;case"audio":{if(o===null)break;let l=o;if(!l||typeof l!="object"||Array.isArray(l))throw new u("audio must be an object or null","audio");if(l.input!==void 0&&l.input!==null){let d=l.input;if(!d||typeof d!="object"||Array.isArray(d))throw new u("audio.input must be an object","audio.input");for(let c of Object.keys(d))if(!(c in re))throw new u(`audio.input field not supported by this backend: ${c}`,`audio.input.${c}`,"unsupported_field");if(d.format!==void 0&&W(d.format,"audio.input.format"),d.transcription!==null&&d.transcription!==void 0){let c=S({input_audio_transcription:d.transcription},s,t);s.inputAudioTranscription=c.inputAudioTranscription}if(d.turn_detection!==null&&d.turn_detection!==void 0){let c=S({turn_detection:d.turn_detection},s,t);s.turnDetection=c.turnDetection}if(d.noise_reduction!==null&&d.noise_reduction!==void 0)throw new u("audio.input.noise_reduction must be null on this backend: no noise-reduction primitive","audio.input.noise_reduction","unsupported_field")}if(l.output!==void 0&&l.output!==null){let d=l.output;if(!d||typeof d!="object"||Array.isArray(d))throw new u("audio.output must be an object","audio.output");for(let c of Object.keys(d))if(!(c in ae))throw new u(`audio.output field not supported by this backend: ${c}`,`audio.output.${c}`,"unsupported_field");if(d.format!==void 0&&W(d.format,"audio.output.format"),d.speed!==void 0)throw new u("audio.output.speed is not supported on this backend: playback rate is fixed","audio.output.speed","unsupported_field");if(d.voice!==void 0)if(d.voice===null)delete s.voice;else{if(typeof d.voice!="string")throw new u("audio.output.voice must be a string or null","audio.output.voice");if(!t.voice)throw new u("audio.output.voice refused: no authoritative voice binding for the resolved backend","audio.output.voice","audio_not_supported");s.voice=d.voice}d.format!==void 0&&!s.outputModalities.includes("audio")&&(s.outputModalities=["text","audio"])}break}default:break}return s}function j(a,e){return{id:e,object:"realtime.session",model:a.model??null,instructions:a.instructions??"",modalities:[...a.outputModalities],voice:a.voice??null,input_audio_format:"pcm16",output_audio_format:"pcm16",input_audio_transcription:a.inputAudioTranscription?{}:null,input_audio_noise_reduction:null,turn_detection:a.turnDetection?{type:"server_vad",create_response:a.turnDetection.createResponse,interrupt_response:a.turnDetection.interruptResponse}:null,temperature:a.temperature??null,max_response_output_tokens:a.maxOutputTokens??"inf",tool_choice:a.toolChoice??"auto",tools:a.tools??[],metadata:a.metadata??null}}function q(a,e){let t=a;if(!t||typeof t!="object")throw new u("item must be an object","item","invalid_request");if(t.type==="function_call_output"){if(typeof t.call_id!="string"||typeof t.output!="string")throw new u("function_call_output item requires string call_id and output","item.call_id","invalid_request");return{id:typeof t.id=="string"?t.id:r("item"),type:"function_call_output",callId:t.call_id,output:t.output}}if(t.type==="message"){let i=t.role;if(i!=="user"&&i!=="assistant"&&i!=="system")throw new u('message item role must be "user", "assistant" or "system"',"item.role","invalid_request");if(!Array.isArray(t.content))throw new u("message item content must be an array","item.content","invalid_request");let n=[];for(let s of t.content){let p=s;if(!p||typeof p!="object")throw new u("content part must be an object","item.content","invalid_request");if(p.type==="input_text"||i==="assistant"&&p.type==="output_text"){if(typeof p.text!="string")throw new u(`${String(p.type)} part requires string text`,"item.content.text","invalid_request");n.push({type:p.type,text:p.text});continue}if(p.type==="input_audio"){if(!e)throw new u("input_audio content refused: no authoritative voice binding for the resolved backend","item.content.type","audio_not_supported");if(typeof p.audio!="string")throw new u("input_audio part requires base64 audio","item.content.audio","invalid_request");n.push({type:"input_audio",...typeof p.transcript=="string"?{transcript:p.transcript}:{}});continue}throw new u(`content part type ${String(p.type)} is not supported on message items here (input_text / output_text / input_audio)`,"item.content.type","invalid_request")}return{id:typeof t.id=="string"?t.id:r("item"),type:"message",role:i,content:n}}throw new u(`item type ${String(t.type)} is not supported \u2014 message and function_call_output items only`,"item.type","invalid_request")}function L(a){let e=[],t=!1;for(let i of a){if(i.type==="function_call"){e.push({type:"function_call",call_id:i.callId,name:i.name,arguments:i.arguments??"{}"});continue}if(i.type==="function_call_output"){e.push({type:"function_call_output",call_id:i.callId,output:i.output});continue}let n=[];for(let s of i.content??[]){if(s.type==="input_text"||s.type==="output_text"){n.push({type:s.type,text:s.text});continue}if(s.type==="input_audio"){let p=s.transcript;if(typeof p=="string"&&p.trim().length>0){n.push({type:"input_text",text:p});continue}t=!0;continue}}e.push({role:i.role,content:n})}return{input:e,sawAudioOnlyItem:t}}var ue=1001,de=1011,X=a=>a.slice(0,120),pe=/^[A-Za-z0-9+/=]*$/,R=class{id=r("conn");ws;binding;onClosed;config=D();sessionId=r("sess");conversationId=r("conv");items=[];pendingAudioBytes=[];pendingAudioTotalBytes=0;frozenAudio=[];inFlight=null;activeResponseId=null;responseFlushed=!1;cancelled=!1;voiceClaimed=!1;audioLaneOwner=!1;audioUnsubscribe=null;outOfTurnAudioWarned=!1;audioOnlyTranscriptGapWarned=!1;closed=!1;turnLaneAbort=null;turnTranscript={open:!1,text:""};lastCommittedTranscript=null;constructor(e){this.ws=e.ws,this.binding=e.binding,this.onClosed=e.onClosed,this.binding.model&&(this.config.model=this.binding.model),this.ws.on("message",t=>{this.onMessage(t)}),this.ws.on("close",()=>this.teardown("client disconnected")),this.ws.on("error",()=>this.teardown("client socket error")),this.send({type:"session.created",event_id:r("event"),session:j(this.config,this.sessionId)}),this.send({type:"conversation.created",event_id:r("event"),conversation:{id:this.conversationId,object:"realtime.conversation"}}),this.binding.turns&&this.startTurnLane()}startTurnLane(){let e=new AbortController;this.turnLaneAbort=e;let t=this.binding.turns;(async()=>{try{for await(let i of t()){if(this.closed||e.signal.aborted)return;this.onTurnEvent(i)}}catch(i){!this.closed&&!e.signal.aborted&&this.send(m(E("turn_lane_fault",`the backend turn/transcript lane failed: ${i instanceof Error?i.message:String(i)}`)))}})()}onTurnEvent(e){switch(e.kind){case"delta":case"final":{let t=typeof e.text=="string"?e.text:"";if(t.length===0)return;this.turnTranscript.open=!0,this.turnTranscript.text+=t,this.config.inputAudioTranscription&&this.send({type:"conversation.item.input_audio_transcription.delta",event_id:r("event"),item_id:"input_audio_turn",content_index:0,delta:t});return}case"turn_start":{this.turnTranscript={open:!0,text:this.turnTranscript.text},this.send({type:"input_audio_buffer.speech_started",event_id:r("event"),item_id:"input_audio_turn",audio_start_ms:typeof e.startMs=="number"?e.startMs:0}),this.config.turnDetection?.interruptResponse&&this.inFlight&&this.cancelResponse();return}case"turn_end":{let t=this.turnTranscript.text.trim(),i=t.length>0?t:null;this.turnTranscript={open:!1,text:""},this.lastCommittedTranscript=i,this.frozenAudio=this.pendingAudioBytes.splice(0),this.pendingAudioTotalBytes=0;let n=r("item"),s=this.items.length>0?this.items[this.items.length-1].id:null;this.pushItem({id:n,type:"message",role:"user",content:[{type:"input_audio",...i!==null?{transcript:i}:{}}]}),this.send({type:"input_audio_buffer.committed",event_id:r("event"),previous_item_id:s,item_id:n}),this.send({type:"conversation.item.created",event_id:r("event"),previous_item_id:s,item:this.itemToProtocol(this.items[this.items.length-1])}),this.send({type:"input_audio_buffer.speech_stopped",event_id:r("event"),item_id:n,audio_end_ms:typeof e.startMs=="number"?e.startMs:0}),this.config.inputAudioTranscription&&i!==null&&this.send({type:"conversation.item.input_audio_transcription.completed",event_id:r("event"),item_id:n,content_index:0,transcript:i}),this.config.turnDetection?.createResponse&&!this.inFlight&&this.startResponse({});return}case"turn_eager_end":case"turn_resumed":return;default:return}}takeTurnTranscript(){let e=this.lastCommittedTranscript;return this.lastCommittedTranscript=null,e}async onMessage(e){if(this.closed)return;let t;try{t=e.toString()}catch{this.fail(f("decode_error","frame is not decodable text"),"invalid_frame");return}if(Buffer.byteLength(t,"utf8")>F){this.fail(f("frame_too_large",`frame exceeds the ${F}-byte limit`),"invalid_frame");return}let i;try{i=JSON.parse(t)}catch{this.fail(f("invalid_json","frame is not valid JSON"),"invalid_frame");return}if(typeof i.type!="string"||i.type.length===0){this.fail(f("missing_type",'event is missing a string "type"'),"invalid_frame");return}try{await this.handle(i)}catch(n){let s=n;if(s&&typeof s=="object"&&s.name==="SessionUpdateError"){this.send(m({type:"invalid_request_error",code:s.code??"invalid_value",message:s.message,param:s.param??null}));return}this.fail(E(null,n instanceof Error?n.message:String(n)),"handler_fault")}}async handle(e){switch(e.type){case"session.update":this.config=S(e.session,this.config,{voice:this.binding.voice,turns:this.binding.turns!==void 0}),this.send({type:"session.updated",event_id:r("event"),session:j(this.config,this.sessionId)});return;case"conversation.item.create":{let t=q(e.item,this.binding.voice),i=this.items.length>0?this.items[this.items.length-1].id:null;this.pushItem(t),this.send({type:"conversation.item.created",event_id:r("event"),previous_item_id:i,item:this.itemToProtocol(t)});return}case"conversation.item.delete":{let t=this.items.findIndex(i=>i.id===e.item_id);if(t<0){this.send(m(f("item_not_found",`no item with id ${JSON.stringify(e.item_id)}`,"item_id")));return}this.items.splice(t,1),this.send({type:"conversation.item.deleted",event_id:r("event"),item_id:e.item_id});return}case"conversation.item.retrieve":{let t=this.items.find(i=>i.id===e.item_id);if(!t){this.send(m(f("item_not_found",`no item with id ${JSON.stringify(e.item_id)}`,"item_id")));return}this.send({type:"conversation.item.retrieved",event_id:r("event"),item:this.itemToProtocol(t)});return}case"conversation.item.truncate":{let t=this.items.find(i=>i.id===e.item_id);if(!t||t.type!=="message"||t.role!=="assistant"){this.send(m(f("item_not_found",`no assistant message item with id ${JSON.stringify(e.item_id)}`,"item_id")));return}t.truncated=!0,this.send({type:"conversation.item.truncated",event_id:r("event"),item_id:t.id,content_index:typeof e.content_index=="number"?e.content_index:0,audio_end_ms:typeof e.audio_end_ms=="number"?e.audio_end_ms:0});return}case"input_audio_buffer.append":{if(typeof e.audio!="string"||e.audio.length===0){this.send(m(f("invalid_audio",'input_audio_buffer.append requires base64 "audio"',"audio")));return}if(!this.ensureVoice())return;if(!pe.test(e.audio)){this.send(m(f("invalid_audio","input_audio_buffer.append audio must be base64","audio")));return}let t=Buffer.byteLength(e.audio,"base64");if(this.pendingAudioBytes.length+t>P){this.send(m(f("buffer_full",`uncommitted input audio exceeds the ${P}-byte bound \u2014 commit or clear first`,"audio")));return}if(this.inFlight){this.binding.audioSession.audio.appendInputAudio(e.audio);return}this.pendingAudioBytes.push(e.audio),this.pendingAudioTotalBytes+=t;return}case"input_audio_buffer.clear":{if(!this.ensureVoice())return;this.pendingAudioBytes.length=0,this.pendingAudioTotalBytes=0,this.send({type:"input_audio_buffer.cleared",event_id:r("event")});return}case"input_audio_buffer.commit":{if(!this.ensureVoice())return;if(this.pendingAudioTotalBytes===0&&this.takeTurnTranscript()===null){this.send(m(f("empty_buffer","input_audio_buffer.commit with no buffered audio","audio")));return}this.frozenAudio=this.pendingAudioBytes.splice(0),this.pendingAudioTotalBytes=0;let t=this.takeTurnTranscript();t===null&&this.binding.turns===void 0&&this.warnAudioOnlyTranscriptGap();let i=r("item"),n=this.items.length>0?this.items[this.items.length-1].id:null;this.pushItem({id:i,type:"message",role:"user",content:[{type:"input_audio",...t!==null?{transcript:t}:{}}]}),this.send({type:"input_audio_buffer.committed",event_id:r("event"),previous_item_id:n,item_id:i}),this.send({type:"conversation.item.created",event_id:r("event"),previous_item_id:n,item:this.itemToProtocol(this.items[this.items.length-1])});return}case"output_audio_buffer.clear":{this.send(m(f("output_clear_unavailable","output_audio_buffer.clear has no server-side playback queue to flush on this adapter: output audio is delivered as response.output_audio.delta events and interrupted turns are stopped with response.cancel")));return}case"response.create":this.startResponse(e.response??{});return;case"response.cancel":this.cancelResponse();return;default:this.send(m(f("unknown_event",`event type ${e.type} is not supported by this adapter`)))}}ensureVoice(){return!this.binding.voice||!this.binding.audioSession?(this.send(m(f("audio_not_supported","audio events require a voice binding from the resolver \u2014 this session was resolved text-only","audio"))),!1):(this.voiceClaimed||(this.claimVoiceBinding(),this.audioUnsubscribe=this.binding.audioSession.audio.onOutputAudio(e=>{this.onOutputAudioFrame(this.activeResponseId??"",e)}),this.voiceClaimed=!0),!0)}claimVoiceBinding(){let e=this.binding.audioSession,t=e.openaiRealtimeClaims??(e.openaiRealtimeClaims=new Set);if(t.has(e))throw new Error("voice binding already owned by another connection \u2014 the resolver handed one audio bridge to two realtime conversations, which must never happen (independent callers require private bindings)");t.add(e),this.audioLaneOwner=!0}releaseVoiceBinding(){let e=this.binding.audioSession;!e||!this.audioLaneOwner||(e.openaiRealtimeClaims?.delete(e),this.audioLaneOwner=!1)}warnAudioOnlyTranscriptGap(){this.audioOnlyTranscriptGapWarned||(this.audioOnlyTranscriptGapWarned=!0,this.send(m({type:"invalid_request_error",code:"audio_input_transcript_unavailable",message:"committed input audio is forwarded to the runtime audio lane but is NOT model-visible in the text turn lane: the resolved backend wires no turn/transcript lane (the voice engine's own stt stream with turn_start/turn_end + transcripts). Wire the transcribe/turn-detection leg upstream (the resolver supplies binding.turns) and committed audio gains its REAL transcript as the model-visible turn.",param:"input_audio_buffer.commit"})))}upstreamStream(e,t){let i=this.binding.session;if(i&&typeof i.sendResponseCreate=="function")return i.sendResponseCreate(e,e.input??[],t);let n=this.binding.clients;if(n&&typeof n.createResponse=="function")return n.createResponse(e);throw new Error("realtime binding provides neither session.sendResponseCreate nor clients.createResponse \u2014 no upstream turn seam")}startResponse(e){if(this.inFlight){this.send(m(f("response_in_progress","a response is already in progress \u2014 cancel it with response.cancel first","response.create")));return}let t=e??{};if(t.modalities!==void 0){if(!Array.isArray(t.modalities)||t.modalities.some(h=>h!=="text"&&h!=="audio")){this.send(m(f("invalid_modalities",'response.modalities must be "text"/"audio"',"response.modalities")));return}if(t.modalities.includes("audio")&&!this.ensureVoice())return}if(t.input!==void 0){this.send(m(f("response_input_unsupported","response.input is not supported by this adapter: build the context with conversation.item.create, then response.create","response.input")));return}this.cancelled=!1,this.responseFlushed=!1,this.messageItemAdded=!1;let i=r("req"),n=`resp_${i}`;this.activeResponseId=n;let s=this.items.length,{input:p,sawAudioOnlyItem:o}=L(this.items),l=typeof t.instructions=="string"?t.instructions:this.config.instructions,d=typeof t.temperature=="number"?t.temperature:this.config.temperature,c=t.max_output_tokens??this.config.maxOutputTokens,y=c==="inf"||c===void 0?void 0:c,x=t.tools!==void 0?t.tools:this.config.tools,C=t.tool_choice!==void 0?t.tool_choice:this.config.toolChoice,N={input:p,stream:!0,...this.config.model?{model:this.config.model}:{},...l!==void 0?{instructions:l}:{},...d!==void 0?{temperature:d}:{},...y!==void 0?{max_output_tokens:y}:{},...x!==void 0?{tools:x}:{},...C!==void 0?{tool_choice:C}:{}};for(let h of this.frozenAudio.splice(0))this.binding.audioSession?.audio.appendInputAudio(h);this.inFlight=(async()=>{let h=[],A=w=>{this.responseFlushed?this.send(w):h.push(w)},I=()=>{if(!this.responseFlushed){this.send({type:"response.created",event_id:r("event"),response:this.responseObject(n,"in_progress")}),this.send({type:"response.in_progress",event_id:r("event"),response:this.responseObject(n,"in_progress")});for(let w of h)this.send(w);h.length=0,this.responseFlushed=!0}},v={messageOpen:!1,messageText:"",tools:new Map,nextOutputIndex:0},Y=null;try{o&&this.binding.turns===void 0&&this.warnAudioOnlyTranscriptGap();let w;try{w=this.upstreamStream(N,i)}catch(O){this.emitUpstreamFault(O,n);return}try{for await(let O of w){if(this.cancelled)break;let _=O;if(_.type==="error"){let T=this.upstreamErrorBody(_);this.responseFlushed?this.send(m(T)):this.send({type:"response.failed",event_id:r("event"),response:this.failedResponse(n,T)});return}if(_.type==="response.output_text.delta"&&typeof _.delta=="string"){I(),v.messageOpen||(this.beginMessageItem(n,v.nextOutputIndex++),v.messageOpen=!0),A({type:"response.output_text.delta",event_id:r("event"),item_id:`${n}_msg_0`,response_id:n,output_index:0,content_index:0,delta:_.delta}),v.messageText+=_.delta;continue}if(_.type==="response.function_call_arguments.delta"){I();let T=typeof _.delta=="string"?_.delta:"",k=typeof _.call_id=="string"&&_.call_id.length>0?_.call_id:`${n}_fc_${v.nextOutputIndex}`,b=v.tools.get(k);if(!b){let z=typeof _.name=="string"&&_.name.length>0?_.name:void 0;b={outputIndex:v.nextOutputIndex++,name:z,args:""},v.tools.set(k,b),A({type:"response.output_item.added",event_id:r("event"),response_id:n,output_index:b.outputIndex,item:{id:k,object:"realtime.item",type:"function_call",call_id:k,name:z??"",arguments:"",status:"in_progress"}})}typeof _.name=="string"&&_.name.length>0&&(b.name=_.name),b.args+=T,A({type:"response.function_call_arguments.delta",event_id:r("event"),item_id:k,response_id:n,output_index:b.outputIndex,call_id:k,name:b.name??"",delta:T});continue}if(_.type==="response.completed"){Y=_.response??null;continue}}}catch(O){this.emitUpstreamFault(O,n);return}if(this.cancelled)return;this.closeOpenItems(n,v,s),I(),this.send({type:"response.done",event_id:r("event"),response:this.doneResponse(n,s,Y)})}finally{this.inFlight=null,this.activeResponseId=null}})()}onOutputAudioFrame(e,t){if(this.cancelled)return;if(this.activeResponseId!==e){!this.outOfTurnAudioWarned&&!this.closed&&(this.outOfTurnAudioWarned=!0,this.send(m({type:"invalid_request_error",code:"out_of_turn_audio",message:"the runtime produced voice OUT outside a response turn; this adapter delivers output audio only within response.output_audio.delta of an open turn \u2014 wire server-driven turns (VAD) upstream or keep speech inside response turns"})));return}this.responseFlushed||(this.send({type:"response.created",event_id:r("event"),response:this.responseObject(e,"in_progress")}),this.send({type:"response.in_progress",event_id:r("event"),response:this.responseObject(e,"in_progress")}),this.responseFlushed=!0);let i=`${e}_msg_0`;this.messageItemOpenForAudio||(this.beginAudioMessageItem(e),this.messageItemOpenForAudio=!0),this.send({type:"response.output_audio.delta",event_id:r("event"),item_id:i,response_id:e,output_index:0,content_index:0,delta:t})}messageItemAdded=!1;messageItemOpenForAudio=!1;beginMessageItem(e,t){let i=`${e}_msg_0`;this.messageItemAdded||(this.messageItemAdded=!0,this.send({type:"response.output_item.added",event_id:r("event"),response_id:e,output_index:t,item:{id:i,object:"realtime.item",type:"message",role:"assistant",status:"in_progress",content:[]}})),this.send({type:"response.content_part.added",event_id:r("event"),item_id:i,response_id:e,output_index:t,content_index:0,part:{type:"output_text",text:""}})}beginAudioMessageItem(e){let t=`${e}_msg_0`;this.messageItemAdded||(this.messageItemAdded=!0,this.send({type:"response.output_item.added",event_id:r("event"),response_id:e,output_index:0,item:{id:t,object:"realtime.item",type:"message",role:"assistant",status:"in_progress",content:[]}})),this.send({type:"response.content_part.added",event_id:r("event"),item_id:t,response_id:e,output_index:0,content_index:0,part:{type:"output_audio",transcript:""}})}closeOpenItems(e,t,i){if(t.messageOpen||this.messageItemOpenForAudio){let n=`${e}_msg_0`,s=this.messageItemOpenForAudio;s&&this.send({type:"response.output_audio.done",event_id:r("event"),item_id:n,response_id:e,output_index:0,content_index:0}),t.messageText.length>0&&this.send({type:"response.output_text.done",event_id:r("event"),item_id:n,response_id:e,output_index:0,content_index:0,text:t.messageText}),this.send({type:"response.content_part.done",event_id:r("event"),item_id:n,response_id:e,output_index:0,content_index:0,part:s?{type:"output_audio",transcript:""}:{type:"output_text",text:t.messageText}});let p=[];s&&p.push({type:"output_audio"}),t.messageText.length>0&&p.push({type:"output_text",text:t.messageText});let o={id:n,type:"message",role:"assistant",content:p};this.pushItem(o),this.send({type:"response.output_item.done",event_id:r("event"),response_id:e,output_index:0,item:this.itemToProtocol(o)})}for(let[n,s]of t.tools){let p=s.args.length>0?s.args:"{}";this.send({type:"response.function_call_arguments.done",event_id:r("event"),item_id:n,response_id:e,output_index:s.outputIndex,call_id:n,name:s.name??"",arguments:p});let o={id:n,type:"function_call",callId:n,name:s.name,arguments:p};this.pushItem(o),this.send({type:"response.output_item.done",event_id:r("event"),response_id:e,output_index:s.outputIndex,item:this.itemToProtocol(o)})}this.messageItemOpenForAudio=!1,this.messageItemAdded=!1}responseObject(e,t,i){return{id:e,object:"realtime.response",status:t,conversation_id:this.conversationId,usage:null,...i??{}}}failedResponse(e,t){return this.responseObject(e,"failed",{status_details:{error:{code:t.code,message:t.message}}})}doneResponse(e,t,i){let n=i&&typeof i.usage=="object"&&i.usage!==null?i.usage:null,s=[];for(let p=t;p<this.items.length;p++)s.push(this.itemToProtocol(this.items[p]));return this.responseObject(e,"completed",{output:s,usage:n?{input_tokens:typeof n.input_tokens=="number"&&Number.isFinite(n.input_tokens)?n.input_tokens:0,output_tokens:typeof n.output_tokens=="number"&&Number.isFinite(n.output_tokens)?n.output_tokens:0,total_tokens:typeof n.total_tokens=="number"&&Number.isFinite(n.total_tokens)?n.total_tokens:(typeof n.input_tokens=="number"&&Number.isFinite(n.input_tokens)?n.input_tokens:0)+(typeof n.output_tokens=="number"&&Number.isFinite(n.output_tokens)?n.output_tokens:0),input_token_details:{cached_tokens:0,audio_tokens:0,text_tokens:0},output_token_details:{audio_tokens:0,text_tokens:0}}:null})}upstreamErrorBody(e){let t=e.error,i=t&&typeof t.message=="string"?t.message:JSON.stringify(e);return{type:"server_error",code:t&&typeof t.code=="string"?t.code:null,message:i}}emitUpstreamFault(e,t){if(this.cancelled)return;let i=E(null,e instanceof Error?e.message:String(e));this.responseFlushed?this.send(m(i)):this.send({type:"response.failed",event_id:r("event"),response:this.failedResponse(t,i)})}cancelResponse(){let e=this.inFlight;if(!e){this.send(m(f("no_response_to_cancel","no response is in progress","response.cancel")));return}this.cancelled=!0;let t=this.activeResponseId??"resp_";e.finally(()=>{this.send({type:"response.cancelled",event_id:r("event"),response:this.responseObject(t,"cancelled")})})}pushItem(e){this.items.push(e),this.items.length>U&&(this.items.shift(),this.send(m({type:"invalid_request_error",code:"transcript_evicted",message:`the conversation transcript exceeded ${U} items \u2014 the oldest item was evicted to keep the per-connection bound`})))}itemToProtocol(e){let t={id:e.id,object:"realtime.item"};if(e.type==="function_call")return{...t,type:"function_call",call_id:e.callId,name:e.name,arguments:e.arguments??"{}",status:"completed"};if(e.type==="function_call_output")return{...t,type:"function_call_output",call_id:e.callId,output:e.output};let i=(e.content??[]).map(n=>n.type==="input_text"||n.type==="output_text"?{type:n.type,text:n.text}:n.type==="input_audio"?{type:"input_audio",...n.transcript?{transcript:n.transcript}:{}}:{type:"output_audio",transcript:n.transcript??""});return{...t,type:"message",role:e.role,status:e.truncated?"incomplete":"completed",content:i}}send(e){if(!(this.closed||this.ws.readyState!==this.ws.OPEN)){if(this.ws.bufferedAmount>$){this.ws.close(ue,X("outbound buffer exceeded")),this.teardown("outbound buffer exceeded");return}this.ws.send(JSON.stringify(e))}}fail(e,t){this.send(m(e)),this.ws.close(de,X(t)),this.teardown(t)}teardown(e){if(!this.closed){this.closed=!0,this.turnLaneAbort?.abort(),this.turnLaneAbort=null,this.releaseVoiceBinding(),this.audioUnsubscribe?.(),this.audioUnsubscribe=null;try{this.ws.removeAllListeners("message")}catch{}this.onClosed?.(this),le(this.id,e)}}},V=[];function le(a,e){V.push({id:a,reason:e}),V.length>256&&V.shift()}var G="/v1/realtime",me=a=>a.slice(0,120);function _e(a,e,t){let i=e===401||e===403?"Unauthorized":e===502?"Upstream Fault":"Upgrade Refused";a.write(`HTTP/1.1 ${e} ${i}\r
2
- Connection: close\r
3
- Content-Type: text/plain\r
4
- \r
5
- ${me(t)}\r
6
- `),a.destroy()}function J(a,e){let t=new ce({noServer:!0}),i=new Set;return a.on("upgrade",(n,s,p)=>{if(new URL(n.url??"/","http://localhost").pathname!==G)return;i.add(s),s.on("close",()=>{i.delete(s)});let l=c=>{let y=c,x=y.session!=null&&typeof y.session.sendResponseCreate=="function",C=y.clients!=null&&typeof y.clients.createResponse=="function";if(!x&&!C)throw new Error("resolver returned a binding without an upstream turn seam (session.sendResponseCreate or clients.createResponse required) \u2014 refusing the upgrade LOUD");return new Promise((N,h)=>{t.handleUpgrade(n,s,p,A=>{try{new R({ws:A,binding:c}),N()}catch(I){h(I)}})})};(typeof e.resolver=="function"?e.resolver:e.resolver.resolve.bind(e.resolver))(n).then(l).catch(c=>{let y=c&&typeof c=="object"&&typeof c.status=="number"?c.status:502,x=c instanceof Error?c.message:String(c);s.destroyed||_e(s,y,x)}).finally(()=>{i.delete(s)})}),async()=>{let n=Date.now()+5e3;for(;i.size>0&&Date.now()<n;)await new Promise(s=>setTimeout(s,25));await new Promise(s=>t.close(()=>s()))}}export{Z as GEMINI_LIVE_PATH,G as OPENAI_REALTIME_PATH,R as OpenAIRealtimeConnection,ee as ResumptionRegistry,K as SessionGoneError,H as UnknownModelError,Q as VIDEO_OUT_SETUP_KEY,te as attachGeminiLive,J as attachOpenAIRealtime,ie as createOpenAIProxy,ne as serveGeminiLiveSocket};
1
+ import{B as l,C as a,D as x,E as s,F as O,G as f,S as I,a as n,b as p,c as m}from"../chunk-TPH77ZOW.js";import{d as r,e as i}from"../chunk-WCMX5VOF.js";import{a as o,h as t}from"../chunk-3JWYIKHM.js";import"../chunk-6M5JK4YC.js";import{c as e}from"../chunk-YSFSRI3D.js";e();export{o as GEMINI_LIVE_PATH,a as OPENAI_REALTIME_PATH,l as OpenAIRealtimeConnection,n as ResumptionRegistry,i as SessionGoneError,r as UnknownModelError,t as VIDEO_OUT_SETUP_KEY,p as attachGeminiLive,x as attachOpenAIRealtime,I as createOpenAIProxy,f as fixedClientsResolver,O as realtimeBindingFor,s as realtimeModelOf,m as serveGeminiLiveSocket};