@urun-sh/openai 0.5.3 → 0.5.5

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 (43) hide show
  1. package/dist/chunk-5BZCM3RS.js +4 -0
  2. package/dist/chunk-DMD5UENK.js +1 -0
  3. package/dist/{chunk-G3NMGP4N.js → chunk-GBBY3PCZ.js} +1 -1
  4. package/dist/chunk-I2Q3B3OG.js +6 -0
  5. package/dist/chunk-K23AZPI4.js +1 -0
  6. package/dist/chunk-OI2OY32M.js +1 -0
  7. package/dist/chunk-QVF7NF7G.js +44 -0
  8. package/dist/gemini-live.cjs +2 -2
  9. package/dist/gemini-live.d.cts +6 -0
  10. package/dist/gemini-live.d.ts +6 -0
  11. package/dist/gemini-live.js +1 -1
  12. package/dist/hosted/bin.cjs +36 -22
  13. package/dist/hosted/bin.js +4 -4
  14. package/dist/hosted/index.cjs +32 -18
  15. package/dist/hosted/index.d.cts +140 -198
  16. package/dist/hosted/index.d.ts +63 -82
  17. package/dist/hosted/index.js +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.cts +2 -18
  20. package/dist/index.d.ts +2 -18
  21. package/dist/index.js +1 -1
  22. package/dist/models-NYMZrklp.d.cts +38 -0
  23. package/dist/models-NYMZrklp.d.ts +38 -0
  24. package/dist/pi-extension/index.cjs +6 -6
  25. package/dist/pi-extension/index.js +1 -1
  26. package/dist/pi-extension/standalone.cjs +49 -49
  27. package/dist/proxy/cli.cjs +41 -37
  28. package/dist/proxy/cli.js +1 -1
  29. package/dist/proxy/index.cjs +30 -21
  30. package/dist/proxy/index.d.cts +468 -2
  31. package/dist/proxy/index.d.ts +173 -2
  32. package/dist/proxy/index.js +6 -1
  33. package/dist/responses-turn-KOAoIqZ-.d.ts +200 -0
  34. package/dist/responses-turn-OrO4euEN.d.cts +513 -0
  35. package/package.json +3 -3
  36. package/dist/chunk-F2TEK34X.js +0 -1
  37. package/dist/chunk-FVD4NMXJ.js +0 -1
  38. package/dist/chunk-M6ICU4F5.js +0 -40
  39. package/dist/chunk-ODHCPZHI.js +0 -6
  40. package/dist/chunk-UVAY7Q7Z.js +0 -1
  41. package/dist/chunk-XHIIEA6Z.js +0 -1
  42. package/dist/server-BfME37pQ.d.cts +0 -200
  43. package/dist/server-l1himPxc.d.ts +0 -82
@@ -1,5 +1,471 @@
1
- export { O as OpenAIProxyOptions, P as ProxyClients, a as ProxyIdentity, b as ProxyVideoOutLane, c as createOpenAIProxy } from '../server-BfME37pQ.cjs';
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';
2
4
  export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-CcDBEfvm.cjs';
3
- import 'node:http';
5
+ import { WebSocket } from 'ws';
6
+ import '../models-NYMZrklp.cjs';
4
7
  import '../video-out-D20UuJ8G.cjs';
5
8
  import '../types-lsVTbNcH.cjs';
9
+
10
+ /**
11
+ * The local OpenAI-compatible proxy — `npx @urun-sh/openai proxy`.
12
+ *
13
+ * A loopback HTTP server speaking the OpenAI REST surface (`/v1/models`,
14
+ * `/v1/responses`, `/v1/chat/completions`, SSE streaming included), so any
15
+ * OpenAI-env-var tool — coding agents first — integrates with uRun with ZERO
16
+ * code changes: point `OPENAI_BASE_URL` at it and keep making plain, local,
17
+ * "inefficient" HTTP requests. The proxy backhauls each request over the uRun
18
+ * session transport (the `@urun-sh/openai` Responses client → session doc
19
+ * lanes + streams — no public OpenAI-style HTTP leaves the machine), which is
20
+ * also where the delta-sync chat-state doc lane (urun-python
21
+ * `urun/serve/chat_state.py`) plugs in as the transport evolves.
22
+ *
23
+ * Local-trust model: binds 127.0.0.1 by default; `apiKey` (when set) must
24
+ * match the agent's `Authorization: Bearer` — otherwise any local bearer is
25
+ * accepted (the key the agent sends is NEVER forwarded upstream; uRun auth is
26
+ * the session's own).
27
+ */
28
+
29
+ /** The proxy server's own options: a FIXED backhaul (the local CLI lane). */
30
+ interface OpenAIProxyOptions extends ProxyHandlerOptions {
31
+ clients: ProxyClients;
32
+ }
33
+ /** Build (not listen) the proxy server — the caller owns listen/close. */
34
+ declare function createOpenAIProxy(options: OpenAIProxyOptions): Server;
35
+
36
+ /**
37
+ * The Gemini Live WS surface: a minimal `ws` upgrade hook on the proxy's
38
+ * node:http server plus one per-connection protocol driver. ALL session
39
+ * plumbing is the proxy's existing seam — `ProxyClients` (lazily-opened
40
+ * pooled per-app uRun sessions behind ModelRouter) — the driver holds ONLY
41
+ * protocol translation state: the conversation transcript the Live API keeps
42
+ * server-side (Live clients never re-send history, so the driver accumulates
43
+ * Responses input items and sends the full transcript per turn; the
44
+ * backhaul's delta-sync chat-state lane makes the re-send cheap).
45
+ *
46
+ * SESSION RESUMPTION (`setup.sessionResumption`) rides the session-identity
47
+ * seam (ProxyClients.sessionHandle / onSessionEnd → ModelRouter.handleFor /
48
+ * sessionForHandle): a resumption handle snapshots the Live conversation
49
+ * PLUS the opaque handle of the pooled uRun session serving it, so resuming
50
+ * reattaches the SAME session (session-affine slot + doc state — the
51
+ * platform's native resume, urun-python#1556/#1582). A handle whose session
52
+ * is gone fails LOUD (1008) — a fresh session is never silently sold as a
53
+ * resume.
54
+ *
55
+ * HOSTED MULTI-TENANT MODE: `attachGeminiLive` either rides a FIXED local
56
+ * backhaul (the `urun compat proxy` lane: `clients` + optional `apiKey`) or
57
+ * resolves PER REQUEST through `authenticate` (the hosted lane), which runs
58
+ * to completion BEFORE `ws.handleUpgrade` — a refused credential never
59
+ * becomes a Live session. Header credentials (`x-goog-api-key` or
60
+ * `Authorization: Bearer`) and the browser `?key=` channel are
61
+ * distinguished; contradictory distinct credentials are refused, and the
62
+ * hosted lane refuses `?key=` outright (origin policy: long-lived org keys
63
+ * must never ride browser query strings) as well as missing auth.
64
+ * Resumption snapshots are bound to the verified caller's scope — another
65
+ * key/org cannot recover a transcript even holding the handle (loud 1008,
66
+ * identical to an unknown handle, so handle existence never leaks).
67
+ *
68
+ * COMPOSITION: the attach claims ONLY the BidiGenerateContent upgrade path
69
+ * and leaves every other upgrade untouched for the next `upgrade` listener
70
+ * (the /v1/responses acceptor, then — registered LAST by the embedder — the
71
+ * final refusal of unclaimed upgrades). It returns an async close handle
72
+ * (ws-native lifecycle: graceful close frame to every live socket, then
73
+ * `wss.close()`) for drain to run BEFORE HTTP-server closure.
74
+ *
75
+ * goAway: wired to core's NATIVE terminal phase signal via
76
+ * ProxyClients.onSessionEnd. MISSING PRIMITIVE (surfaced in the PR): core
77
+ * exposes `endsAt` but no PRE-expiry notice event, so goAway is emitted AT
78
+ * the moment of loss (timeLeft ≈ 0s) — per the no-invented-timers rule.
79
+ *
80
+ * Loud-failure contract (no silent degradation):
81
+ * 1007 protocol violations / declared-follow-up inputs (automatic
82
+ * server-side VAD…)
83
+ * 1008 unknown model (sendUnknownModel's WS mirror) / resumption handle
84
+ * whose session is gone (CLOSE_SESSION_GONE)
85
+ * 1011 upstream failures (error event, stream without response.completed,
86
+ * pooled session terminal loss — preceded by goAway)
87
+ *
88
+ * Manual VAD/activity + barge-in: with
89
+ * `setup.realtimeInputConfig.automaticActivityDetection.disabled: true` the
90
+ * client marks its own activity windows (`realtimeInput.activityStart` /
91
+ * `activityEnd`). An activityStart while a generation is in flight is a
92
+ * BARGE-IN (activityHandling START_OF_ACTIVITY_INTERRUPTS, the default): the
93
+ * turn loop breaks out of its for-await, which calls `return()` on the
94
+ * upstream async iterator — the async-iterator-native cancel the /v1/messages
95
+ * lane uses for client disconnects (server.ts), no bespoke abort plumbing —
96
+ * then emits the golden `serverContent.interrupted: true` and DROPS the
97
+ * partial turn (no generationComplete/turnComplete, no transcript adoption).
98
+ * Cancellation lands at the next upstream event, same as the HTTP lane.
99
+ */
100
+
101
+ /**
102
+ * The upstream seams the Live lane uses: the pooled-session Responses call,
103
+ * the pooled session's NATIVE audio lanes (`openAudio` — the same
104
+ * enableSessionAudio/AudioBridge machinery RealtimeClient.enableAudio
105
+ * rides), the NATIVE video frame lane (`openVideo` — enableSessionVideo's
106
+ * rt-video-in named-DATA frame path), and the session-identity seam
107
+ * (sessionHandle/onSessionEnd → ModelRouter.handleFor/sessionForHandle)
108
+ * sessionResumption rides.
109
+ */
110
+ type GeminiLiveClients = Pick<ProxyClients, 'createResponse' | 'openAudio' | 'openVideo' | 'openVideoOut' | 'sessionHandle' | 'onSessionEnd'>;
111
+ interface LiveSnapshot {
112
+ model: string;
113
+ /** The session-identity seam's opaque handle for the pooled uRun session. */
114
+ proxyHandle: string;
115
+ /** The Live conversation at snapshot time, as Responses input items. */
116
+ transcript: Array<Record<string, unknown>>;
117
+ }
118
+ /**
119
+ * One registry per attached proxy server (created in {@link attachGeminiLive}).
120
+ *
121
+ * Snapshots are keyed by CALLER SCOPE + handle: a handle minted for one
122
+ * verified caller is unreachable to every other caller — handle in hand or
123
+ * not, the lookup is exactly the unknown-handle loud 1008, so handle
124
+ * existence is never leaked across tenants. The scope is the authorized
125
+ * caller's identity (LOCAL_SCOPE on the local lane, the hosted lane's
126
+ * verified-caller subject); the conversation dimension lives in the handle
127
+ * itself, which names one conversation's snapshot. Multiattach is normal:
128
+ * concurrent connections of the SAME caller may resume the SAME handle.
129
+ *
130
+ * HONEST SCOPE (not HA): this registry is per-process, per-attach. A replica
131
+ * restart loses every handle (loud 1008, never a silent restart), and no
132
+ * other replica can serve one. Cross-replica / persistent resumption needs a
133
+ * shared persistence plane for snapshots + session identity — a reported
134
+ * platform requirement, never silently claimed here.
135
+ */
136
+ declare class ResumptionRegistry {
137
+ private readonly snapshots;
138
+ store(scope: string, handle: string, snapshot: LiveSnapshot): void;
139
+ get(scope: string, handle: string): LiveSnapshot | undefined;
140
+ }
141
+ /**
142
+ * One authorized Live call: the backhaul to ride and the tenant scope its
143
+ * resumption snapshots bind to.
144
+ */
145
+ interface GeminiLiveCall {
146
+ clients: GeminiLiveClients;
147
+ /**
148
+ * The verified caller's scope (hosted: derived from the verified org API
149
+ * key — e.g. TenantRegistry's non-secret `tenantSubject`). A resumption
150
+ * handle minted under one scope is UNREACHABLE under any other, and the
151
+ * failed lookup is the ordinary unknown-handle loud 1008 — never a
152
+ * "wrong tenant" error that would confirm the handle exists.
153
+ */
154
+ scope: string;
155
+ /**
156
+ * HOSTED conversation binding (optional): the lane calls it with every
157
+ * resume handle it mints, so the embedder can index handle → the private
158
+ * conversation backhaul that will serve a resume of it. Absent on the
159
+ * local lane (fixed clients).
160
+ */
161
+ onBindResumeHandle?: (handle: string) => void;
162
+ /**
163
+ * HOSTED (optional): resolve the backhaul a minted resume handle belongs
164
+ * to. Runs at resume setup BEFORE any seam call — the resumed connection
165
+ * reattaches the SAME private conversation (same native session identity,
166
+ * owner binding enforced); a throw is the loud session-gone close (1008).
167
+ * Absent on the local lane.
168
+ */
169
+ clientsForResume?: (resumeHandle: string) => Promise<GeminiLiveClients>;
170
+ /**
171
+ * HOSTED per-attachment release (optional): called ONCE when this
172
+ * connection's socket closes, so the embedder can drop THIS attachment's
173
+ * claim on the private conversation backhaul (native detach at the last
174
+ * attachment — no resource keepalive merely to preserve local resume).
175
+ * Fire-and-forget from the lane; must be idempotent. Absent on the local
176
+ * lane.
177
+ */
178
+ release?: () => Promise<void>;
179
+ }
180
+ /**
181
+ * HOSTED per-request auth: verify the single Gemini credential the client
182
+ * presented (already policy-checked — no missing auth, no `?key=`, no
183
+ * contradictory inputs) and resolve THAT caller's own backhaul + tenant
184
+ * scope. Runs to completion BEFORE `ws.handleUpgrade`; a throw refuses the
185
+ * upgrade with the error's own `status` (e.g. ProxyAuthError's 401) or 502
186
+ * when the error carries none.
187
+ */
188
+ type GeminiLiveAuthenticate = (credential: string, req: IncomingMessage) => Promise<GeminiLiveCall>;
189
+ interface GeminiLiveOptions {
190
+ /** The FIXED backhaul (local CLI lane). Required unless `authenticate`. */
191
+ clients?: GeminiLiveClients;
192
+ /**
193
+ * The proxy's local bearer (OpenAIProxyOptions.apiKey). Gemini Live clients
194
+ * authenticate with `?key=…` or `x-goog-api-key`; both are accepted here
195
+ * (never forwarded upstream — uRun auth is the session's own). Omitted ⇒
196
+ * the local lane is open (a loopback embedder's choice).
197
+ */
198
+ apiKey?: string;
199
+ /**
200
+ * HOSTED per-request resolution; mutually exclusive with a fixed
201
+ * `clients`/`apiKey` lane (throw at attach). Sets the HOSTED credential
202
+ * policy: missing auth refused, `?key=` refused (origin policy — long-lived
203
+ * org keys must never appear in browser query strings), contradictory
204
+ * distinct credentials refused.
205
+ */
206
+ authenticate?: GeminiLiveAuthenticate;
207
+ }
208
+ /**
209
+ * Attach the Gemini Live BidiGenerateContent WS endpoint to an HTTP server.
210
+ *
211
+ * COMPOSITION: this attach claims ONLY the BidiGenerateContent upgrade path.
212
+ * Upgrades on any other path are left untouched for the NEXT `upgrade`
213
+ * listener (the /v1/responses acceptor, and — registered LAST by the
214
+ * embedder — the final refusal of unclaimed upgrades). On the claimed path
215
+ * auth happens BEFORE `ws.handleUpgrade`: the local fixed bearer 401s, and
216
+ * the hosted lane runs `authenticate` to completion (401 on a
217
+ * refused/missing credential, the error's own status for a resolver
218
+ * rejection, 502 when the error carries none — never an open pass).
219
+ *
220
+ * Returns the lane's close handle — `await close()` runs the ws-native
221
+ * lifecycle: a graceful close() to every live socket (close frames, sends
222
+ * flushed), then `wss.close()` resolving when the last socket is gone.
223
+ * Disconnects are DETACHES: closing never ends a pooled session. The handle
224
+ * is idempotent.
225
+ */
226
+ declare function attachGeminiLive(server: Server, options: GeminiLiveOptions): () => Promise<void>;
227
+ /**
228
+ * Drive ONE already-authenticated Gemini Live socket — the per-connection
229
+ * driver underneath {@link attachGeminiLive}, for surfaces that own the
230
+ * upgrade + auth themselves (e.g. a destination-side SFU route that
231
+ * pre-authenticates and hands the live `WebSocket` over).
232
+ *
233
+ * Runs the full protocol loop (setup → setupComplete, clientContent /
234
+ * realtimeInput / toolResponse → turns, native audio/video lanes, session
235
+ * resumption, goAway) against `call`'s seams, and detaches cleanly when the
236
+ * socket closes: `call.release?.()` fires once (per-attachment release —
237
+ * native detach at the last attachment), sessions are never ended by a
238
+ * socket closing.
239
+ *
240
+ * `registry` collects this driver's resumption snapshots. Omit it for a
241
+ * private per-socket registry (handles then live exactly as long as the
242
+ * socket); pass a shared one to let several sockets resume each other's
243
+ * handles — always scoped by `call.scope`, never across callers.
244
+ */
245
+ declare function serveGeminiLiveSocket(ws: WebSocket, call: GeminiLiveCall, registry?: ResumptionRegistry): void;
246
+
247
+ /**
248
+ * `attachOpenAIRealtime(server, options)` — the WS upgrade hook for the OpenAI
249
+ * Realtime surface (GA protocol subset; see protocol.ts for the event subset).
250
+ *
251
+ * Contract (Main-owned composition; this lane claims ONLY `/v1/realtime`):
252
+ * - Upgrades on any other path are passed through UNTOUCHED — a plain
253
+ * `next()` listener, so sibling lanes (Gemini Live, Responses WS) and the
254
+ * hosted composition keep their own upgrade hooks.
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.
259
+ * - The resolved binding carries a PRIVATE native uRun session (fresh or
260
+ * explicitly-resumed) and, iff voice, a PRIVATE audio bridge — never the
261
+ * pooled tenant lane. The connection's cross-caller claim is invariant
262
+ * validation, not capacity policy.
263
+ * - Returns an async close function: waits for every touched socket to
264
+ * settle, then closes the internal WebSocketServer. It does NOT end the
265
+ * native uRun sessions (detach ≠ session death — resume is Main-owned;
266
+ * the binding seam decides lifetime).
267
+ */
268
+
269
+ declare const OPENAI_REALTIME_PATH = "/v1/realtime";
270
+ /**
271
+ * The resolver seam — the ONLY credential, modality, and session-allocation
272
+ * authority this adapter recognizes. Implemented by Main's composition (and
273
+ * 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.
276
+ */
277
+ type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage) => Promise<RealtimeBinding<S>>) | {
278
+ resolve(req: IncomingMessage): Promise<RealtimeBinding<S>>;
279
+ };
280
+ interface RealtimeBinding<S = unknown> {
281
+ /**
282
+ * The PRIVATE native uRun session serving this conversation (the shared
283
+ * per-conversation factory in hosted/tenants.ts allocates it; reuse/multi-
284
+ * attach is sanctioned ONLY for the SAME authorized conversation on an
285
+ * explicit resume — two different conversations MUST NOT receive one
286
+ * session). The upstream turn seam is EITHER form:
287
+ * - `session.sendResponseCreate(params, input, requestId)` (the direct
288
+ * SdkTransport-style seam), or
289
+ * - `clients.createResponse(params)` — the canonical ProxyClients seam
290
+ * the conversation factory hands out (`input` rides inside `params`).
291
+ * A binding with neither is rejected at upgrade time, LOUD.
292
+ */
293
+ session?: S & {
294
+ sendResponseCreate?(params: unknown, input: unknown[], requestId: string): AsyncIterable<unknown>;
295
+ };
296
+ clients?: Pick<ProxyClients, 'createResponse'>;
297
+ /**
298
+ * Authoritative modality verdict from the resolver. A function NAME proves
299
+ * nothing — without `voice: true`, audio events are refused and no audio
300
+ * primitive is ever opened on the session.
301
+ */
302
+ voice: boolean;
303
+ /** The PRIVATE audio bridge for THIS conversation (required iff `voice`). */
304
+ audioSession?: {
305
+ audio: {
306
+ appendInputAudio(base64Pcm16: string): void;
307
+ onOutputAudio(handler: (b64: string) => void): () => void;
308
+ };
309
+ /** Cross-caller reuse registry (invariant validation; see connection.ts). */
310
+ openaiRealtimeClaims?: Set<RealtimeBinding['audioSession']>;
311
+ };
312
+ /** `model` the binding names for this conversation (overrides session config default). */
313
+ model?: string;
314
+ /** True when `session` is a resumed authorized session (vs a fresh private one). */
315
+ resumed?: boolean;
316
+ /**
317
+ * The backend's OWN turn/transcript lane — the §5 `stt` named stream
318
+ * (urun.serve.transcribe_bridge wire shape: `t` = delta/final/turn_start/
319
+ * turn_end/turn_eager_end/turn_resumed, `delta` text, additive `start_ms`
320
+ * and semantic-VAD `confidence`). The resolver maps `session.stream('stt')
321
+ * .messages()` onto this. Its `turn_end` IS the native commit and
322
+ * `turn_start` IS the native barge-in — the adapter drives server-VAD
323
+ * emulation, real transcription events, and model-visible committed audio
324
+ * from it. Absent = the backend wired no turn lane: manual mode only.
325
+ */
326
+ turns?: () => AsyncIterable<{
327
+ kind: 'delta' | 'final' | 'turn_start' | 'turn_end' | 'turn_eager_end' | 'turn_resumed';
328
+ text?: string;
329
+ confidence?: number;
330
+ startMs?: number;
331
+ }>;
332
+ }
333
+ interface OpenAIRealtimeOptions<S = unknown> {
334
+ resolver: OpenAIRealtimeResolver<S>;
335
+ }
336
+ /**
337
+ * Attach the OpenAI Realtime WS endpoint to `server`. Upgrades on any other
338
+ * path pass through untouched. Returns an async close function.
339
+ */
340
+ declare function attachOpenAIRealtime<S = unknown>(server: Server, options: OpenAIRealtimeOptions<S>): () => Promise<void>;
341
+
342
+ /**
343
+ * One authenticated OpenAI Realtime WS connection — the adapter's core.
344
+ *
345
+ * OWNERSHIP MODEL (Main's contract, 2026-09-08): the binding seam hands this
346
+ * connection an EXCLUSIVE, connection-private native uRun session (and, when
347
+ * the resolver authoritatively binds voice, a PRIVATE audio bridge). The
348
+ * pooled tenant `openAudio` lane is NEVER used here — `binding.audioSession`
349
+ * comes from the resolver's own per-conversation acquisition, and
350
+ * {@link claimVoiceBinding} is the INVARIANT VALIDATION that an accidental
351
+ * cross-caller reuse of one bridge fails LOUD (it is not a capacity policy).
352
+ * Multi-attach on the SAME authorized session (explicit resume) is the one
353
+ * sanctioned shared form: one bridge owner at a time, validated by claim.
354
+ *
355
+ * NO-OP POLICY: every unsupported client event or backend gap renders as a
356
+ * LOUD `error` server event (or a close) — never a silently ignored frame.
357
+ */
358
+
359
+ declare class OpenAIRealtimeConnection<S = unknown> {
360
+ readonly id: string;
361
+ private readonly ws;
362
+ private readonly binding;
363
+ private readonly onClosed?;
364
+ private config;
365
+ private readonly sessionId;
366
+ private readonly conversationId;
367
+ private readonly items;
368
+ /** Uncommitted input audio (base64 PCM16 chunks) held locally until commit. */
369
+ private pendingAudioBytes;
370
+ private pendingAudioTotalBytes;
371
+ /** Committed audio of the frozen turn, dispatched at response.create. */
372
+ private frozenAudio;
373
+ private inFlight;
374
+ private activeResponseId;
375
+ /** Output events already flowed for the active response (post-flush faults → error event, pre-flush → response.failed). */
376
+ private responseFlushed;
377
+ private cancelled;
378
+ private voiceClaimed;
379
+ private audioLaneOwner;
380
+ private audioUnsubscribe;
381
+ private outOfTurnAudioWarned;
382
+ private audioOnlyTranscriptGapWarned;
383
+ private closed;
384
+ /** The backend's turn/transcript lane consumer (binding.turns), if wired. */
385
+ private turnLaneAbort;
386
+ /** Accumulated transcript of the OPEN user turn (delta/final events). */
387
+ private turnTranscript;
388
+ /** Transcript of the LAST committed turn (the model-visible commit). */
389
+ private lastCommittedTranscript;
390
+ constructor(opts: {
391
+ ws: WebSocket;
392
+ binding: RealtimeBinding<S>;
393
+ onClosed?: (conn: OpenAIRealtimeConnection<S>) => void;
394
+ });
395
+ /**
396
+ * Consume the backend's OWN turn/transcript lane (§5 stt stream): the
397
+ * semantic-VAD boundary events drive REAL protocol semantics — turn_start
398
+ * is the native barge-in, turn_end is the native commit, deltas/finals are
399
+ * the REAL input transcription. This is the server-VAD primitive, not an
400
+ * emulation gap: the voice engine itself decides turn boundaries.
401
+ */
402
+ private startTurnLane;
403
+ /** One backend turn/transcript event → REAL GA protocol events. */
404
+ private onTurnEvent;
405
+ /** The committed turn's transcript, consumed once (null = none). */
406
+ private takeTurnTranscript;
407
+ private onMessage;
408
+ private handle;
409
+ /** Refuse loudly when a text-only-resolved session is asked to carry audio. */
410
+ private ensureVoice;
411
+ /**
412
+ * Cross-caller reuse guard — the INVARIANT check, not capacity policy. The
413
+ * resolver must hand every independent conversation its OWN bridge; if two
414
+ * connections ever hold one, the second fails LOUD instead of mixing audio.
415
+ */
416
+ private claimVoiceBinding;
417
+ private releaseVoiceBinding;
418
+ private warnAudioOnlyTranscriptGap;
419
+ /**
420
+ * The upstream turn seam, normalized: a binding carries EITHER
421
+ * `session.sendResponseCreate(params, input, requestId)` (direct
422
+ * SdkTransport-style) OR `clients.createResponse(params)` (the canonical
423
+ * ProxyClients seam the shared conversation factory hands out). A binding
424
+ * with neither was already rejected at upgrade time; this is the loud
425
+ * backstop.
426
+ */
427
+ private upstreamStream;
428
+ private startResponse;
429
+ /** Bridge voice OUT frame → `response.output_audio.delta` on the open turn. */
430
+ private onOutputAudioFrame;
431
+ /** One assistant message item per turn (text and audio parts share it). */
432
+ private messageItemAdded;
433
+ /** true when an audio delta opened the message item this turn. */
434
+ private messageItemOpenForAudio;
435
+ private beginMessageItem;
436
+ private beginAudioMessageItem;
437
+ /**
438
+ * Close every item the turn opened, in first-open order, adopt them into
439
+ * the transcript, and emit their `done` events.
440
+ */
441
+ private closeOpenItems;
442
+ private responseObject;
443
+ private failedResponse;
444
+ /**
445
+ * `response.done` — REAL usage only: the serve runtime emits
446
+ * `{input_tokens, output_tokens, total_tokens}` on terminal bodies; when it
447
+ * sent none, `usage: null` is delivered (never zeros dressed as counts).
448
+ */
449
+ private doneResponse;
450
+ private upstreamErrorBody;
451
+ private emitUpstreamFault;
452
+ /**
453
+ * Cancel the in-flight response. The upstream iterator is stopped through
454
+ * the for-await `break` protocol (its `return()` runs — no bespoke cancel
455
+ * machinery); the turn stops at the NEXT upstream event and the partial
456
+ * output is NEVER adopted into the transcript (see closeOpenItems being
457
+ * skipped on the cancelled path).
458
+ */
459
+ private cancelResponse;
460
+ private pushItem;
461
+ private itemToProtocol;
462
+ private send;
463
+ private fail;
464
+ /**
465
+ * Socket-level teardown ONLY (never session death): the native uRun session
466
+ * behind this conversation stays alive — resume semantics are Main-owned.
467
+ */
468
+ private teardown;
469
+ }
470
+
471
+ export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, serveGeminiLiveSocket };
@@ -1,5 +1,176 @@
1
- export { O as OpenAIProxyOptions, P as ProxyClients, a as ProxyIdentity, b as ProxyVideoOutLane, c as createOpenAIProxy } from '../server-l1himPxc.js';
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';
2
4
  export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-C9uPKypK.js';
3
- import 'node:http';
5
+ import { WebSocket } from 'ws';
6
+ import '../models-NYMZrklp.js';
4
7
  import '../video-out-CWesbk12.js';
5
8
  import '../types-lsVTbNcH.js';
9
+
10
+ interface OpenAIProxyOptions extends ProxyHandlerOptions {
11
+ clients: ProxyClients;
12
+ }
13
+
14
+ declare function createOpenAIProxy(options: OpenAIProxyOptions): Server;
15
+
16
+ type GeminiLiveClients = Pick<ProxyClients, 'createResponse' | 'openAudio' | 'openVideo' | 'openVideoOut' | 'sessionHandle' | 'onSessionEnd'>;
17
+ interface LiveSnapshot {
18
+ model: string;
19
+
20
+ proxyHandle: string;
21
+
22
+ transcript: Array<Record<string, unknown>>;
23
+ }
24
+
25
+ declare class ResumptionRegistry {
26
+ private readonly snapshots;
27
+ store(scope: string, handle: string, snapshot: LiveSnapshot): void;
28
+ get(scope: string, handle: string): LiveSnapshot | undefined;
29
+ }
30
+
31
+ interface GeminiLiveCall {
32
+ clients: GeminiLiveClients;
33
+
34
+ scope: string;
35
+
36
+ onBindResumeHandle?: (handle: string) => void;
37
+
38
+ clientsForResume?: (resumeHandle: string) => Promise<GeminiLiveClients>;
39
+
40
+ release?: () => Promise<void>;
41
+ }
42
+
43
+ type GeminiLiveAuthenticate = (credential: string, req: IncomingMessage) => Promise<GeminiLiveCall>;
44
+ interface GeminiLiveOptions {
45
+
46
+ clients?: GeminiLiveClients;
47
+
48
+ apiKey?: string;
49
+
50
+ authenticate?: GeminiLiveAuthenticate;
51
+ }
52
+
53
+ declare function attachGeminiLive(server: Server, options: GeminiLiveOptions): () => Promise<void>;
54
+
55
+ declare function serveGeminiLiveSocket(ws: WebSocket, call: GeminiLiveCall, registry?: ResumptionRegistry): void;
56
+
57
+ declare const OPENAI_REALTIME_PATH = "/v1/realtime";
58
+
59
+ type OpenAIRealtimeResolver<S = unknown> = ((req: IncomingMessage) => Promise<RealtimeBinding<S>>) | {
60
+ resolve(req: IncomingMessage): Promise<RealtimeBinding<S>>;
61
+ };
62
+ interface RealtimeBinding<S = unknown> {
63
+
64
+ session?: S & {
65
+ sendResponseCreate?(params: unknown, input: unknown[], requestId: string): AsyncIterable<unknown>;
66
+ };
67
+ clients?: Pick<ProxyClients, 'createResponse'>;
68
+
69
+ voice: boolean;
70
+
71
+ audioSession?: {
72
+ audio: {
73
+ appendInputAudio(base64Pcm16: string): void;
74
+ onOutputAudio(handler: (b64: string) => void): () => void;
75
+ };
76
+
77
+ openaiRealtimeClaims?: Set<RealtimeBinding['audioSession']>;
78
+ };
79
+
80
+ model?: string;
81
+
82
+ resumed?: boolean;
83
+
84
+ turns?: () => AsyncIterable<{
85
+ kind: 'delta' | 'final' | 'turn_start' | 'turn_end' | 'turn_eager_end' | 'turn_resumed';
86
+ text?: string;
87
+ confidence?: number;
88
+ startMs?: number;
89
+ }>;
90
+ }
91
+ interface OpenAIRealtimeOptions<S = unknown> {
92
+ resolver: OpenAIRealtimeResolver<S>;
93
+ }
94
+
95
+ declare function attachOpenAIRealtime<S = unknown>(server: Server, options: OpenAIRealtimeOptions<S>): () => Promise<void>;
96
+
97
+ declare class OpenAIRealtimeConnection<S = unknown> {
98
+ readonly id: string;
99
+ private readonly ws;
100
+ private readonly binding;
101
+ private readonly onClosed?;
102
+ private config;
103
+ private readonly sessionId;
104
+ private readonly conversationId;
105
+ private readonly items;
106
+
107
+ private pendingAudioBytes;
108
+ private pendingAudioTotalBytes;
109
+
110
+ private frozenAudio;
111
+ private inFlight;
112
+ private activeResponseId;
113
+
114
+ private responseFlushed;
115
+ private cancelled;
116
+ private voiceClaimed;
117
+ private audioLaneOwner;
118
+ private audioUnsubscribe;
119
+ private outOfTurnAudioWarned;
120
+ private audioOnlyTranscriptGapWarned;
121
+ private closed;
122
+
123
+ private turnLaneAbort;
124
+
125
+ private turnTranscript;
126
+
127
+ private lastCommittedTranscript;
128
+ constructor(opts: {
129
+ ws: WebSocket;
130
+ binding: RealtimeBinding<S>;
131
+ onClosed?: (conn: OpenAIRealtimeConnection<S>) => void;
132
+ });
133
+
134
+ private startTurnLane;
135
+
136
+ private onTurnEvent;
137
+
138
+ private takeTurnTranscript;
139
+ private onMessage;
140
+ private handle;
141
+
142
+ private ensureVoice;
143
+
144
+ private claimVoiceBinding;
145
+ private releaseVoiceBinding;
146
+ private warnAudioOnlyTranscriptGap;
147
+
148
+ private upstreamStream;
149
+ private startResponse;
150
+
151
+ private onOutputAudioFrame;
152
+
153
+ private messageItemAdded;
154
+
155
+ private messageItemOpenForAudio;
156
+ private beginMessageItem;
157
+ private beginAudioMessageItem;
158
+
159
+ private closeOpenItems;
160
+ private responseObject;
161
+ private failedResponse;
162
+
163
+ private doneResponse;
164
+ private upstreamErrorBody;
165
+ private emitUpstreamFault;
166
+
167
+ private cancelResponse;
168
+ private pushItem;
169
+ private itemToProtocol;
170
+ private send;
171
+ private fail;
172
+
173
+ private teardown;
174
+ }
175
+
176
+ export { type GeminiLiveCall, type GeminiLiveClients, OPENAI_REALTIME_PATH, type OpenAIProxyOptions, OpenAIRealtimeConnection, type OpenAIRealtimeOptions, type OpenAIRealtimeResolver, ProxyClients, type RealtimeBinding, ResumptionRegistry, attachGeminiLive, attachOpenAIRealtime, createOpenAIProxy, serveGeminiLiveSocket };