@urun-sh/openai 0.6.0 → 0.6.2
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/dist/{chunk-YXK42SKC.js → chunk-76BZLCGT.js} +1 -1
- package/dist/chunk-7V2JKPIG.js +1 -0
- package/dist/chunk-CT2GQG5R.js +7 -0
- package/dist/chunk-TMQJY56F.js +4 -0
- package/dist/chunk-URMOBMSP.js +58 -0
- package/dist/chunk-X3GLH2HQ.js +1 -0
- package/dist/gemini-live.cjs +1 -1
- package/dist/gemini-live.d.cts +11 -0
- package/dist/gemini-live.d.ts +3 -0
- package/dist/gemini-live.js +1 -1
- package/dist/hosted/bin.cjs +36 -36
- package/dist/hosted/bin.js +1 -1
- package/dist/hosted/index.cjs +32 -32
- package/dist/hosted/index.d.cts +1 -1
- package/dist/hosted/index.d.ts +1 -1
- package/dist/hosted/index.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/pi-extension/index.cjs +6 -6
- package/dist/pi-extension/index.js +1 -1
- package/dist/pi-extension/standalone.cjs +52 -52
- package/dist/proxy/cli.cjs +46 -46
- package/dist/proxy/cli.js +1 -1
- package/dist/proxy/index.cjs +26 -26
- package/dist/proxy/index.d.cts +94 -15
- package/dist/proxy/index.d.ts +7 -3
- package/dist/proxy/index.js +1 -1
- package/dist/{responses-turn-CdhIre_a.d.ts → responses-turn-EwxfRJJg.d.ts} +15 -1
- package/dist/{responses-turn-BfdBbey8.d.cts → responses-turn-vMefRdgJ.d.cts} +112 -15
- package/package.json +2 -2
- package/dist/chunk-45FJ5GTP.js +0 -7
- package/dist/chunk-QTVTCMJU.js +0 -1
- package/dist/chunk-TDRZZGUK.js +0 -1
- package/dist/chunk-TPH77ZOW.js +0 -58
- package/dist/chunk-WCMX5VOF.js +0 -4
package/dist/proxy/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server, IncomingMessage } from 'node:http';
|
|
2
|
-
import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-
|
|
3
|
-
export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-
|
|
2
|
+
import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-vMefRdgJ.cjs';
|
|
3
|
+
export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-vMefRdgJ.cjs';
|
|
4
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
6
|
import '../models-DUdx_Y6X.cjs';
|
|
@@ -285,7 +285,7 @@ declare const OPENAI_REALTIME_PATH = "/v1/realtime";
|
|
|
285
285
|
* convention) or a `{ resolve(req, signal) }` object. `signal` aborts when
|
|
286
286
|
* the CLIENT SOCKET closes/errors before the upgrade is accepted — the
|
|
287
287
|
* resolver SHOULD abandon its admission wait on it (the backhaul's
|
|
288
|
-
* `
|
|
288
|
+
* `openAudioOn` rides the same signal into core `whenLive`'s own abort
|
|
289
289
|
* support); a resolver that ignores it still cannot admit an upgrade to a
|
|
290
290
|
* vanished client: the settled binding is released, never upgraded.
|
|
291
291
|
*/
|
|
@@ -302,7 +302,11 @@ interface RealtimeBinding<S = unknown> {
|
|
|
302
302
|
* - `session.sendResponseCreate(params, input, requestId)` (the direct
|
|
303
303
|
* SdkTransport-style seam), or
|
|
304
304
|
* - `clients.createResponse(params)` — the canonical ProxyClients seam
|
|
305
|
-
*
|
|
305
|
+
* shape (`input` rides inside `params`). The shared binding composition
|
|
306
|
+
* (binding.ts, ENG-446) hands out a PINNED view of it: the call dials
|
|
307
|
+
* `createResponseOn(handle, …)` on the ONE pooled session the binding
|
|
308
|
+
* resolved with the audio bridge, so no turn on the connection can
|
|
309
|
+
* reach a second pooled session behind the bridge's back.
|
|
306
310
|
* A binding with neither is rejected at upgrade time, LOUD.
|
|
307
311
|
*/
|
|
308
312
|
session?: S & {
|
|
@@ -321,8 +325,8 @@ interface RealtimeBinding<S = unknown> {
|
|
|
321
325
|
appendInputAudio(base64Pcm16: string): void;
|
|
322
326
|
onOutputAudio(handler: (b64: string) => void): () => void;
|
|
323
327
|
/**
|
|
324
|
-
* The pooled uRun session this lane rides (backhaul `
|
|
325
|
-
* core `Session.id`), when the backhaul knows it — the field the
|
|
328
|
+
* The pooled uRun session this lane rides (backhaul `openAudioOn`
|
|
329
|
+
* reports core `Session.id`), when the backhaul knows it — the field the
|
|
326
330
|
* `realtime_upgrade` log line puts on every lifecycle event so an
|
|
327
331
|
* orphaned session is traceable from the proxy log alone. Null is the
|
|
328
332
|
* honest absence (an id-less session reports no id); sessionIdOf
|
|
@@ -395,6 +399,22 @@ declare function attachOpenAIRealtime<S = unknown>(server: Server, options: Open
|
|
|
395
399
|
* Multi-attach on the SAME authorized session (explicit resume) is the one
|
|
396
400
|
* sanctioned shared form: one bridge owner at a time, validated by claim.
|
|
397
401
|
*
|
|
402
|
+
* ONE SESSION IDENTITY PER CONNECTION (ENG-446): the shared binding
|
|
403
|
+
* composition resolves the connection's pooled session ONCE and pins BOTH the
|
|
404
|
+
* audio bridge (`openAudioOn(handle)`) and every turn (`createResponseOn
|
|
405
|
+
* (handle, …)`, threaded through the binding's `clients` view) to that one
|
|
406
|
+
* handle. This adapter therefore treats a turn seam failure that means THE
|
|
407
|
+
* SESSION (not the request) is dead — a {@link SessionGoneError} (the pool
|
|
408
|
+
* replaced or dropped the pinned entry), or a dial that dies before any
|
|
409
|
+
* client-visible content — as the LOUD `session_gone` CLOSE of the whole
|
|
410
|
+
* connection: an `error` event naming the cause, then the 1011 close. The
|
|
411
|
+
* bridge stays bound to the dead session and NEVER rebinds, and the turn is
|
|
412
|
+
* never retried on a replacement behind the bridge's back (the two-session
|
|
413
|
+
* race the speech lane's #500 pin fixed for its lane). The ONE exception is
|
|
414
|
+
* core's own BOUNDED ADMISSION VERDICT (`SessionFailedError` never-live /
|
|
415
|
+
* create-failed — the platform's answer about this assignment, not a death):
|
|
416
|
+
* it stays the per-turn `response.failed`, exactly as before.
|
|
417
|
+
*
|
|
398
418
|
* NO-OP POLICY: every unsupported client event or backend gap renders as a
|
|
399
419
|
* LOUD `error` server event (or a close) — never a silently ignored frame.
|
|
400
420
|
*/
|
|
@@ -466,7 +486,9 @@ declare class OpenAIRealtimeConnection<S = unknown> {
|
|
|
466
486
|
* The upstream turn seam, normalized: a binding carries EITHER
|
|
467
487
|
* `session.sendResponseCreate(params, input, requestId)` (direct
|
|
468
488
|
* SdkTransport-style) OR `clients.createResponse(params)` (the canonical
|
|
469
|
-
* ProxyClients seam the shared
|
|
489
|
+
* ProxyClients seam SHAPE — the shared binding composition hands out a
|
|
490
|
+
* PINNED view whose call dials `createResponseOn(handle, …)` on the ONE
|
|
491
|
+
* pooled session the audio bridge rides, ENG-446). A binding
|
|
470
492
|
* with neither was already rejected at upgrade time; this is the loud
|
|
471
493
|
* backstop. The ProxyClients seam is PROMISE-VALUED
|
|
472
494
|
* (`rehomingCreateResponse` dials inside an async function — its
|
|
@@ -512,6 +534,45 @@ declare class OpenAIRealtimeConnection<S = unknown> {
|
|
|
512
534
|
*/
|
|
513
535
|
private doneResponse;
|
|
514
536
|
private upstreamErrorBody;
|
|
537
|
+
/**
|
|
538
|
+
* THE SESSION-GONE LOUD CLOSE (ENG-446): an `error` event naming the cause
|
|
539
|
+
* (`session_gone`), then the abnormal close and teardown. The connection's
|
|
540
|
+
* audio bridge is bound to the dead/replaced session and NEVER rebinds, and
|
|
541
|
+
* the turn is never retried on another session behind the bridge's back —
|
|
542
|
+
* the only honest moves are to say why and close.
|
|
543
|
+
*/
|
|
544
|
+
private failSessionGone;
|
|
545
|
+
/**
|
|
546
|
+
* Classify ONE upstream turn failure (the dial throwing, or the stream
|
|
547
|
+
* throwing mid-read) — the two outcomes are the LOUD `session_gone` close
|
|
548
|
+
* and the per-turn fault the lane always had:
|
|
549
|
+
*
|
|
550
|
+
* - a {@link SessionGoneError} (the pool no longer holds the handle's
|
|
551
|
+
* session, or a replacement serves under it now) closes the connection
|
|
552
|
+
* LOUDLY, pre- or post-content alike — and post-CANCEL alike: the
|
|
553
|
+
* bridge is orphaned either way, and a cancelled turn must never mute
|
|
554
|
+
* a dead pinned session (round-1 review, CodeRabbit + Greptile);
|
|
555
|
+
* - a CANCELLED turn suppresses every OTHER late fault (round-1 review):
|
|
556
|
+
* `response.cancel` does not abort a pending dial, and the dial's
|
|
557
|
+
* rejection settles AFTER `cancelled` was set — the lane's pre-#511
|
|
558
|
+
* behaviour stands (`response.cancelled` from `inFlight.finally`, the
|
|
559
|
+
* connection stays open), so a generic late fault from the settling
|
|
560
|
+
* dial/read is swallowed here rather than closing the connection as
|
|
561
|
+
* `session_gone`;
|
|
562
|
+
* - a dial that dies BEFORE any client-visible content (nothing flushed)
|
|
563
|
+
* closes the connection LOUDLY too — under the pin there is no re-home,
|
|
564
|
+
* and keeping the connection open would leave the bridge attached to a
|
|
565
|
+
* session that cannot serve a turn, silently mute on the next
|
|
566
|
+
* response.create. The ONE exception is core's own BOUNDED ADMISSION
|
|
567
|
+
* VERDICT (`SessionFailedError` never-live / create-failed — the
|
|
568
|
+
* platform's answer about this assignment, not a death), which keeps
|
|
569
|
+
* the per-turn failure exactly as before;
|
|
570
|
+
* - everything else (content already flowed; the upstream answered with
|
|
571
|
+
* an `error` EVENT, handled elsewhere) is the per-turn fault — the
|
|
572
|
+
* connection stays open, and a later turn on the same pinned handle
|
|
573
|
+
* decides the connection's fate.
|
|
574
|
+
*/
|
|
575
|
+
private upstreamTurnFault;
|
|
515
576
|
private emitUpstreamFault;
|
|
516
577
|
/**
|
|
517
578
|
* Cancel the in-flight response. The upstream iterator is stopped through
|
|
@@ -540,11 +601,24 @@ declare class OpenAIRealtimeConnection<S = unknown> {
|
|
|
540
601
|
* placements) through the SAME router-backed gate seam the Images lane uses
|
|
541
602
|
* (`ProxyClients.supportsAudio` — `ModelRouter.supportsAudio`). A non-audio
|
|
542
603
|
* model is refused LOUDLY with a 404 before anything is opened; a voice
|
|
543
|
-
* binding
|
|
544
|
-
*
|
|
545
|
-
* session
|
|
546
|
-
*
|
|
547
|
-
* the
|
|
604
|
+
* binding rides ONE pooled-session identity for BOTH of its legs (ENG-446,
|
|
605
|
+
* the speech lane's #500 pin applied to the realtime lane): the connection
|
|
606
|
+
* resolves its pooled session ONCE (`ProxyClients.sessionHandle` — the same
|
|
607
|
+
* handle seam the speech lane pins its collector and turn with, here naming
|
|
608
|
+
* the INTERACTIVE pooled slot), opens its PRIVATE audio bridge on that exact
|
|
609
|
+
* handle (`openAudioOn(handle, signal)` — the pinned open), and drives every
|
|
610
|
+
* turn on the SAME handle (`createResponseOn(handle, …)`, threaded through
|
|
611
|
+
* the binding's `clients` view). The unpinned shape — the bridge via
|
|
612
|
+
* `openAudio(model)`, turns via the re-homing `createResponse` — let each leg
|
|
613
|
+
* resolve its OWN pooled session, so a dial that died before client-visible
|
|
614
|
+
* content re-homed the turn onto a REPLACEMENT session while the bridge
|
|
615
|
+
* stayed attached to the evicted one: the turn's audio was delivered to a
|
|
616
|
+
* session the client was not attached to, or a concurrent bridge on the
|
|
617
|
+
* replacement received another caller's audio. Behind the pin, a dead or
|
|
618
|
+
* replaced session is the connection's LOUD `session_gone` close
|
|
619
|
+
* (connection.ts), never a retry across sessions. There is no env-var switch
|
|
620
|
+
* and no chat-default fall-through: an absent model resolves through the
|
|
621
|
+
* router exactly like every other lane, and a chat default is refused
|
|
548
622
|
* exactly like an explicitly named chat model.
|
|
549
623
|
*
|
|
550
624
|
* Used by BOTH compositions — the local `urun compat proxy` (fixed clients,
|
|
@@ -566,10 +640,15 @@ declare function realtimeModelOf(req: IncomingMessage): string | undefined;
|
|
|
566
640
|
* - {@link UnknownModelError} ⇒ 404 with the router's own loud message.
|
|
567
641
|
* - A non-audio verdict ⇒ 404 — the realtime lane never falls through to
|
|
568
642
|
* the chat lane, in either direction.
|
|
569
|
-
* - A voice verdict with no `
|
|
570
|
-
* be served without its bridge; a voiceless downgrade is never
|
|
643
|
+
* - A voice verdict with no `openAudioOn` seam ⇒ 501 (a voice binding cannot
|
|
644
|
+
* be served without its pinned bridge; a voiceless downgrade is never
|
|
645
|
+
* silent).
|
|
646
|
+
* - ONE pooled-session identity for the bridge AND every turn
|
|
647
|
+
* (`sessionHandle` once → `openAudioOn(handle, signal)` +
|
|
648
|
+
* `createResponseOn(handle, …)`), so no turn on this connection can ever
|
|
649
|
+
* resolve a different pooled session than the bridge.
|
|
571
650
|
*/
|
|
572
|
-
declare function realtimeBindingFor(clients: Pick<ProxyClients, '
|
|
651
|
+
declare function realtimeBindingFor(clients: Pick<ProxyClients, 'sessionHandle' | 'createResponseOn'> & Partial<Pick<ProxyClients, 'supportsAudio' | 'openAudioOn'>>, model: string | undefined, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
|
|
573
652
|
/**
|
|
574
653
|
* The LOCAL lane resolver: fixed pooled clients, optional fixed apiKey (the
|
|
575
654
|
* same shared-secret rule the local Gemini Live lane applies — a mismatched
|
package/dist/proxy/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server, IncomingMessage } from 'node:http';
|
|
2
|
-
import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-
|
|
3
|
-
export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-
|
|
2
|
+
import { f as ProxyHandlerOptions, P as ProxyClients } from '../responses-turn-EwxfRJJg.js';
|
|
3
|
+
export { D as DialObserver, a as DialReport, g as ProxyIdentity, h as ProxyVideoOutLane, S as SessionGoneError, U as UnknownModelError } from '../responses-turn-EwxfRJJg.js';
|
|
4
4
|
export { h as GEMINI_LIVE_PATH, V as VIDEO_OUT_SETUP_KEY } from '../translator-Ddd65sXR.js';
|
|
5
5
|
import { WebSocket } from 'ws';
|
|
6
6
|
import '../models-DUdx_Y6X.js';
|
|
@@ -171,6 +171,10 @@ declare class OpenAIRealtimeConnection<S = unknown> {
|
|
|
171
171
|
|
|
172
172
|
private doneResponse;
|
|
173
173
|
private upstreamErrorBody;
|
|
174
|
+
|
|
175
|
+
private failSessionGone;
|
|
176
|
+
|
|
177
|
+
private upstreamTurnFault;
|
|
174
178
|
private emitUpstreamFault;
|
|
175
179
|
|
|
176
180
|
private cancelResponse;
|
|
@@ -184,7 +188,7 @@ declare class OpenAIRealtimeConnection<S = unknown> {
|
|
|
184
188
|
|
|
185
189
|
declare function realtimeModelOf(req: IncomingMessage): string | undefined;
|
|
186
190
|
|
|
187
|
-
declare function realtimeBindingFor(clients: Pick<ProxyClients, '
|
|
191
|
+
declare function realtimeBindingFor(clients: Pick<ProxyClients, 'sessionHandle' | 'createResponseOn'> & Partial<Pick<ProxyClients, 'supportsAudio' | 'openAudioOn'>>, model: string | undefined, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
|
|
188
192
|
|
|
189
193
|
declare function fixedClientsResolver(clients: ProxyClients, apiKey?: string): {
|
|
190
194
|
resolve(req: IncomingMessage, signal?: AbortSignal): Promise<RealtimeBinding<ProxyClients>>;
|
package/dist/proxy/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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-
|
|
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-URMOBMSP.js";import{d as r,e as i}from"../chunk-TMQJY56F.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};
|
|
@@ -198,6 +198,10 @@ interface RouterModelEntry {
|
|
|
198
198
|
warm?: number;
|
|
199
199
|
|
|
200
200
|
voices?: string[];
|
|
201
|
+
|
|
202
|
+
capabilities?: {
|
|
203
|
+
reasoning: boolean;
|
|
204
|
+
};
|
|
201
205
|
}
|
|
202
206
|
interface RouterModelList {
|
|
203
207
|
object: 'list';
|
|
@@ -338,10 +342,14 @@ declare class ModelRouter<S> {
|
|
|
338
342
|
|
|
339
343
|
supportsSpeech(model: string | undefined): Promise<boolean>;
|
|
340
344
|
|
|
345
|
+
supportsTranscription(model: string | undefined): Promise<boolean>;
|
|
346
|
+
|
|
341
347
|
speechVoices(model: string | undefined): Promise<SpeechVoicesResolution>;
|
|
342
348
|
|
|
343
349
|
imageModelList(): Promise<RouterImageModelList>;
|
|
344
350
|
|
|
351
|
+
speechToTextModelList(): Promise<RouterModelList>;
|
|
352
|
+
|
|
345
353
|
closeAll(): Promise<void>;
|
|
346
354
|
}
|
|
347
355
|
|
|
@@ -555,6 +563,8 @@ interface ResponsesCreateParams {
|
|
|
555
563
|
response_format?: unknown;
|
|
556
564
|
|
|
557
565
|
voice?: string;
|
|
566
|
+
|
|
567
|
+
language?: string;
|
|
558
568
|
}
|
|
559
569
|
|
|
560
570
|
interface SessionEndInfo {
|
|
@@ -580,10 +590,14 @@ interface ProxyClients {
|
|
|
580
590
|
|
|
581
591
|
supportsSpeech?(model: string | undefined): Promise<boolean>;
|
|
582
592
|
|
|
593
|
+
supportsTranscription?(model: string | undefined): Promise<boolean>;
|
|
594
|
+
|
|
583
595
|
speechVoices?(model: string | undefined): Promise<SpeechVoicesResolution>;
|
|
584
596
|
|
|
585
597
|
listImageModels?(): Promise<unknown>;
|
|
586
598
|
|
|
599
|
+
listSpeechToTextModels?(): Promise<unknown>;
|
|
600
|
+
|
|
587
601
|
catalogIdentity(model: string | undefined): Promise<{
|
|
588
602
|
model_id: string;
|
|
589
603
|
variant: string;
|
|
@@ -593,7 +607,7 @@ interface ProxyClients {
|
|
|
593
607
|
|
|
594
608
|
openAudio?(model: string | undefined, signal?: AbortSignal): Promise<ProxyAudioLane>;
|
|
595
609
|
|
|
596
|
-
openAudioOn?(handle: string): Promise<PinnedAudioLane>;
|
|
610
|
+
openAudioOn?(handle: string, signal?: AbortSignal): Promise<PinnedAudioLane>;
|
|
597
611
|
|
|
598
612
|
openVideo?(model: string | undefined): Promise<ProxyVideoLane>;
|
|
599
613
|
|
|
@@ -650,6 +650,23 @@ interface RouterModelEntry {
|
|
|
650
650
|
* entry, and on tts entries whose voices the catalog does not vouch.
|
|
651
651
|
*/
|
|
652
652
|
voices?: string[];
|
|
653
|
+
/**
|
|
654
|
+
* Declared lane capabilities, derived from the catalog row — never
|
|
655
|
+
* hand-set. `reasoning` is true/false exactly when the row's
|
|
656
|
+
* `engine_args.reasoning_parser` is configured/absent on EVERY GPU
|
|
657
|
+
* placement (the runtime's own per-row parser knob, urun-python
|
|
658
|
+
* engines_vllm_engine.py / engines_sglang_engine.py): true means the
|
|
659
|
+
* serve lane separates thinking text onto `reasoning_content` /
|
|
660
|
+
* reasoning items / `thinking` blocks and `content` carries only the
|
|
661
|
+
* answer; false means the model emits no separated reasoning. A variant
|
|
662
|
+
* whose placements disagree, a malformed value, or a slug with no catalog
|
|
663
|
+
* row omit the object entirely — unknown, never guessed
|
|
664
|
+
* (openrouter-doc.ts `placementsReasoning`, applied by both listing
|
|
665
|
+
* lanes).
|
|
666
|
+
*/
|
|
667
|
+
capabilities?: {
|
|
668
|
+
reasoning: boolean;
|
|
669
|
+
};
|
|
653
670
|
}
|
|
654
671
|
interface RouterModelList {
|
|
655
672
|
object: 'list';
|
|
@@ -1182,6 +1199,23 @@ declare class ModelRouter<S> {
|
|
|
1182
1199
|
* never fabricates a `false` out of an infrastructure outage.
|
|
1183
1200
|
*/
|
|
1184
1201
|
supportsSpeech(model: string | undefined): Promise<boolean>;
|
|
1202
|
+
/**
|
|
1203
|
+
* THE TRANSCRIPTION LANE'S GATE (proxy/transcriptions.ts,
|
|
1204
|
+
* `POST /v1/audio/transcriptions`): does `model`'s deployed app carry
|
|
1205
|
+
* catalog `task` **stt** — INVARIANTLY across every GPU-placement row
|
|
1206
|
+
* ({@link audioModalityForRows} answering exactly `'stt'`)? The SAME
|
|
1207
|
+
* shape as {@link supportsImages}/{@link supportsSpeech}, and deliberately
|
|
1208
|
+
* NOT {@link supportsAudio} (which is modality-AGNISTIC — `!== null`
|
|
1209
|
+
* accepts stt OR tts — because the realtime lane serves both): a `tts`
|
|
1210
|
+
* model must never be asked to transcribe and a chat model is the loud
|
|
1211
|
+
* 404 the images lane renders for non-image models. A SHARED-lane hit
|
|
1212
|
+
* joins on the lane's exact catalog id + variant (the SAME join
|
|
1213
|
+
* {@link imageModelList} uses for the shared block), exactly like
|
|
1214
|
+
* {@link supportsSpeech}. A CONFIGURED catalog oracle that FAILS
|
|
1215
|
+
* propagates its rejection — the gate never fabricates a `false` out of
|
|
1216
|
+
* an infrastructure outage.
|
|
1217
|
+
*/
|
|
1218
|
+
supportsTranscription(model: string | undefined): Promise<boolean>;
|
|
1185
1219
|
/**
|
|
1186
1220
|
* THE SPEECH LANE'S VOICE-SET RESOLUTION (proxy/speech.ts): the voice set
|
|
1187
1221
|
* `/v1/models` publishes for `model` — resolved by the SAME resolver the
|
|
@@ -1213,6 +1247,21 @@ declare class ModelRouter<S> {
|
|
|
1213
1247
|
* bare-entries degradation here.
|
|
1214
1248
|
*/
|
|
1215
1249
|
imageModelList(): Promise<RouterImageModelList>;
|
|
1250
|
+
/**
|
|
1251
|
+
* `GET /v1/audio/models`: the TRANSCRIPTION-capable (task `stt`) slice of
|
|
1252
|
+
* the model surface — the audio twin of {@link imageModelList}: the
|
|
1253
|
+
* caller-org deployed apps whose catalog rows vouch `stt`
|
|
1254
|
+
* ({@link audioModalityForRows} answering exactly `'stt'`, the SAME
|
|
1255
|
+
* consensus {@link supportsTranscription} applies), plus the shared block's
|
|
1256
|
+
* endpoints joined against each endpoint's OWN catalog row (exact model_id +
|
|
1257
|
+
* variant; a definite non-stt task is simply skipped, never a loud defect —
|
|
1258
|
+
* the task column IS the vouch, there is no expects_image-style flag to
|
|
1259
|
+
* require). The catalog read propagates (this listing IS the capability
|
|
1260
|
+
* oracle's answer), and the PRICE SURFACE IS NOT READ AT ALL — an stt model
|
|
1261
|
+
* is not token-priced, so {@link UNPRICED_SURFACE} states that as a fact
|
|
1262
|
+
* about the lane, exactly like the images listing.
|
|
1263
|
+
*/
|
|
1264
|
+
speechToTextModelList(): Promise<RouterModelList>;
|
|
1216
1265
|
/** Close every pooled session (Session.end() underneath) — proxy shutdown. */
|
|
1217
1266
|
closeAll(): Promise<void>;
|
|
1218
1267
|
}
|
|
@@ -1940,6 +1989,17 @@ interface ResponsesCreateParams {
|
|
|
1940
1989
|
* default voice applies, today's behavior.
|
|
1941
1990
|
*/
|
|
1942
1991
|
voice?: string;
|
|
1992
|
+
/**
|
|
1993
|
+
* The transcription lane's per-request LANGUAGE hint (ENG-332, additive
|
|
1994
|
+
* like `voice`): the ISO code a `stt`-task engine transcribes under
|
|
1995
|
+
* (LiveKit's `openai.STT` always sends one, so refusing the field would
|
|
1996
|
+
* break the stock consumer). Forwarded VERBATIM on the serve envelope so
|
|
1997
|
+
* the runtime validates it against the transcribe engine's declared
|
|
1998
|
+
* `served_languages` — the proxy never validates languages itself (it
|
|
1999
|
+
* cannot know the engine's set) and never silently drops the field.
|
|
2000
|
+
* Absent -> key absent, never an empty string.
|
|
2001
|
+
*/
|
|
2002
|
+
language?: string;
|
|
1943
2003
|
}
|
|
1944
2004
|
/** How one pinned session ended (the native phase machinery's terminal step). */
|
|
1945
2005
|
interface SessionEndInfo {
|
|
@@ -2058,6 +2118,17 @@ interface ProxyClients {
|
|
|
2058
2118
|
* silent allow-all, and never a chat model asked to speak.
|
|
2059
2119
|
*/
|
|
2060
2120
|
supportsSpeech?(model: string | undefined): Promise<boolean>;
|
|
2121
|
+
/**
|
|
2122
|
+
* The transcription lane's capability gate (proxy/transcriptions.ts), backed
|
|
2123
|
+
* by ModelRouter.supportsTranscription — the caller-org catalog's `task`
|
|
2124
|
+
* column, which must vouch `stt` (NOT `tts`: a synthesis model must never
|
|
2125
|
+
* be asked to transcribe; NOT chat) INVARIANTLY across placements. Optional
|
|
2126
|
+
* at the seam exactly like {@link supportsSpeech}: hand-built clients may
|
|
2127
|
+
* omit it, in which case the mounted transcription lane answers the loud
|
|
2128
|
+
* 501 — never a silent allow-all, and never a chat model asked to
|
|
2129
|
+
* transcribe.
|
|
2130
|
+
*/
|
|
2131
|
+
supportsTranscription?(model: string | undefined): Promise<boolean>;
|
|
2061
2132
|
/**
|
|
2062
2133
|
* THE SPEECH LANE'S VOICE-SET RESOLUTION (proxy/speech.ts): the voices
|
|
2063
2134
|
* `/v1/models` publishes for `model` (ModelRouter.speechVoices — the SAME
|
|
@@ -2080,6 +2151,14 @@ interface ProxyClients {
|
|
|
2080
2151
|
* its absence is the loud 501, never a silently empty list.
|
|
2081
2152
|
*/
|
|
2082
2153
|
listImageModels?(): Promise<unknown>;
|
|
2154
|
+
/**
|
|
2155
|
+
* `GET /v1/audio/models` — the transcription-capable (task `stt`) slice of
|
|
2156
|
+
* the model surface (ModelRouter.speechToTextModelList: deployed stt apps +
|
|
2157
|
+
* the shared block joined against the catalog), the audio twin of
|
|
2158
|
+
* {@link listImageModels}. Optional at the seam for the same reason; its
|
|
2159
|
+
* absence is the loud 501, never a silently empty list.
|
|
2160
|
+
*/
|
|
2161
|
+
listSpeechToTextModels?(): Promise<unknown>;
|
|
2083
2162
|
/**
|
|
2084
2163
|
* THE PRE-DIAL DEFAULT FOR THE LEDGER'S BILLING IDENTITY
|
|
2085
2164
|
* (ModelRouter.catalogIdentityFor): the CATALOG `(model_id, variant)` a
|
|
@@ -2146,28 +2225,46 @@ interface ProxyClients {
|
|
|
2146
2225
|
* text-only embeddings exist — but a surface that RECEIVES audio while the
|
|
2147
2226
|
* embedder wired no `openAudio` must fail LOUD, never drop chunks.
|
|
2148
2227
|
*
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2228
|
+
* `signal` aborts when the CLIENT that asked for the lane went away while
|
|
2229
|
+
* the lane's admission was still pending (the realtime lane's raw socket
|
|
2230
|
+
* close) — the backhaul aborts its `whenLive` wait on it and hands the
|
|
2231
|
+
* queued admission ticket back through core `Session.cancel()`, so a
|
|
2232
|
+
* vanished client never holds the sense pod's one-session slot. The
|
|
2233
|
+
* client-gone wait is REFERENCE-COUNTED per pooled entry: several callers
|
|
2234
|
+
* can wait on ONE queued admission, and the ticket goes back only when
|
|
2235
|
+
* the LAST waiter aborts — one client's disconnect rejects only its own
|
|
2236
|
+
* open, never another caller's admission (round-1 review, Greptile).
|
|
2237
|
+
*/
|
|
2155
2238
|
openAudio?(model: string | undefined, signal?: AbortSignal): Promise<ProxyAudioLane>;
|
|
2156
2239
|
/**
|
|
2157
|
-
* THE PINNED AUDIO OPEN (speech
|
|
2158
|
-
* pooled session `handle` names (ModelRouter.sessionForHandle — a
|
|
2159
|
-
* replaced session throws {@link SessionGoneError} LOUDLY, never a
|
|
2160
|
-
* session opened behind the handle's back). The speech lane opens its
|
|
2240
|
+
* THE PINNED AUDIO OPEN (speech + realtime lanes): the NATIVE audio lane of
|
|
2241
|
+
* the EXACT pooled session `handle` names (ModelRouter.sessionForHandle — a
|
|
2242
|
+
* gone or replaced session throws {@link SessionGoneError} LOUDLY, never a
|
|
2243
|
+
* fresh session opened behind the handle's back). The speech lane opens its
|
|
2161
2244
|
* audio collector here and dials its response turn on the SAME handle
|
|
2162
2245
|
* ({@link createResponseOn}), so BOTH legs are one session identity by
|
|
2163
2246
|
* construction: a re-home can never move the turn to a second session
|
|
2164
2247
|
* while the collector stays subscribed to the first (which missed the
|
|
2165
2248
|
* replacement's audio and answered a voiceless 502, or worse, served a
|
|
2166
|
-
* concurrent collector another session's frames).
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2169
|
-
|
|
2170
|
-
|
|
2249
|
+
* concurrent collector another session's frames). The realtime binding
|
|
2250
|
+
* (openai-realtime/binding.ts, ENG-446) rides the SAME pin: one handle per
|
|
2251
|
+
* connection for the bridge AND every turn, so a re-home can never detach
|
|
2252
|
+
* the turn from the bridge the audio rides. Optional at the seam exactly
|
|
2253
|
+
* like {@link openAudio}; both lanes answer its absence with the loud 501 —
|
|
2254
|
+
* never a voiceless downgrade.
|
|
2255
|
+
*
|
|
2256
|
+
* `signal` carries {@link openAudio}'s client-gone semantics onto the
|
|
2257
|
+
* pinned open (ENG-446): the realtime lane threads the upgrade's
|
|
2258
|
+
* client-socket abort down here — abort while the pinned session's
|
|
2259
|
+
* admission is still queued abandons the queued session (core
|
|
2260
|
+
* `Session.cancel()`), and the open rejects. A vanished client never holds
|
|
2261
|
+
* the sense pod's one-session slot through a queue wait it is no longer
|
|
2262
|
+
* party to. An already-cached lane is returned untouched — and the
|
|
2263
|
+
* client-gone wait rides the SAME per-entry refcount as {@link openAudio}:
|
|
2264
|
+
* one of several waiters aborting rejects only that caller's open, the
|
|
2265
|
+
* queued admission going back only at the LAST waiter's abort.
|
|
2266
|
+
*/
|
|
2267
|
+
openAudioOn?(handle: string, signal?: AbortSignal): Promise<PinnedAudioLane>;
|
|
2171
2268
|
/**
|
|
2172
2269
|
* Open (or reuse) the NATIVE video FRAME lane on the pooled session `model`
|
|
2173
2270
|
* routes to (transport/media.ts `enableSessionVideo`: discrete JPEG frames →
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urun-sh/openai",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "OpenAI-compatible Realtime + Responses SDK over uRun session primitives (not websockets).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"build:bin": "bun build --compile --external @roamhq/wrtc src/proxy/bin.ts --outfile dist/bin/urun-openai && node scripts/assert-bin-transport.mjs"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@urun-sh/core": "^0.6.
|
|
73
|
+
"@urun-sh/core": "^0.6.2"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@prometheus-io/client": "^0.16.1",
|
package/dist/chunk-45FJ5GTP.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{a as j,b as W}from"./chunk-TDRZZGUK.js";import{C as re,D as oe,E as se,F as ie,H as ae,I as A,J as le,K as ce,L as ue,M as de,N as he,O as pe,P as ge,Q as fe,R as me,b as Z,d as ee,e as te,q as ne,r as k}from"./chunk-TPH77ZOW.js";import{e as Q,g as z}from"./chunk-WCMX5VOF.js";import{a as X}from"./chunk-3JWYIKHM.js";import{c as p}from"./chunk-YSFSRI3D.js";p();import{Counter as R,Gauge as ye,Histogram as we,Registry as Ue,collectDefaultMetrics as Pe}from"@prometheus-io/client";import{createServer as $e}from"http";var It=9464,be="/metrics",I="(other)",Le="(none)",He=64,Me=128,De=["/v1/responses","/v1/chat/completions","/v1/systemone","/v1/messages","/v1/messages/count_tokens","/v1/models","/v1/images/models","/v1/images/generations","/v1/images/edits","/v1/audio/speech",ae,ce],Fe=new Set(De);function Ke(t){return Fe.has(t)?t:I}function Ge(t){return t===null?"none":t>=500?"5xx":t>=400?"4xx":t>=300?"3xx":t>=200?"2xx":"1xx"}var N=class{constructor(e){this.max=e}max;admitted=new Set;overflows=0;of(e,n=!0){return e===null||e===""?Le:this.admitted.has(e)?e:n?this.admitted.size>=this.max?(this.overflows+=1,I):(this.admitted.add(e),e):I}get size(){return this.admitted.size}get overflowCount(){return this.overflows}},T=class{registry=new Ue;modelLabel=new N(He);orgLabel=new N(Me);requests;duration;ttft;streamErrors;undelivered;ledgerWrites;ledgerQueue;labelCardinality;labelOverflows;constructor(e={}){this.requests=new R({name:"urun_inference_requests_total",help:"Completed /v1 requests, by lane, method, HTTP status class, streaming, model and org.",labelNames:["lane","method","status_class","stream","model","org"],registers:[this.registry]}),this.duration=new we({name:"urun_inference_request_duration_seconds",help:"Wall time from request receipt to response close. For a stream this is the WHOLE generation, not the time to the head.",labelNames:["lane","stream","model","org"],buckets:[.1,.25,.5,1,2.5,5,10,30,60,120,300,600],registers:[this.registry]}),this.ttft=new we({name:"urun_inference_ttft_seconds",help:"Time to first output-text delta on a streamed response. The metric Hugging Face gates provider listing on (<5s for conversational models).",labelNames:["lane","model","org"],buckets:[.1,.25,.5,1,2,3,5,8,15,30,60],registers:[this.registry]}),this.streamErrors=new R({name:"urun_inference_stream_errors_total",help:"Responses that emitted an error frame AFTER the head flushed. These are HTTP 200 with a complete body, so no status-based metric can see them.",labelNames:["lane","model"],registers:[this.registry]}),this.undelivered=new R({name:"urun_inference_undelivered_total",help:"Responses whose socket did not finish flushing \u2014 the client vanished mid-answer.",labelNames:["lane","model"],registers:[this.registry]}),this.ledgerWrites=new R({name:"urun_inference_ledger_writes_total",help:"Terminal fate of the per-request ledger row for each billable /v1 request. outcome=written is the row landing (reason=priced|unpriced); outcome=skipped and outcome=failed are BOTH revenue that went unrecorded. failed/duplicate is the ENG-410 fabricated-row signal.",labelNames:["outcome","reason"],registers:[this.registry]});for(let n of ue)this.ledgerWrites.inc({outcome:n.outcome,reason:n.reason},0);this.ledgerQueue=new ye({name:"urun_inference_ledger_queue",help:'The ledger write queue right now. measure="depth" is rows waiting for a worker; measure="oldest_age_seconds" is how long the head has waited \u2014 approaching the dwell bound means rows are about to be given up on, which is revenue lost.',labelNames:["measure"],registers:[this.registry],collect:()=>{this.ledgerQueue.set({measure:"depth"},de()),this.ledgerQueue.set({measure:"oldest_age_seconds"},he()/1e3)}}),this.labelCardinality=new ye({name:"urun_inference_label_cardinality",help:"Distinct values admitted for a bounded metric label. At its ceiling, further values report as (other).",labelNames:["label"],registers:[this.registry],collect:()=>{this.labelCardinality.set({label:"model"},this.modelLabel.size),this.labelCardinality.set({label:"org"},this.orgLabel.size)}}),this.labelOverflows=new R({name:"urun_inference_label_overflows_total",help:"Observations that would have taken a new label value but found the budget spent. Non-zero means a metric dimension is now partly blind.",labelNames:["label"],registers:[this.registry]}),e.defaultMetrics!==!1&&Pe({register:this.registry,prefix:"urun_inference_proxy_"})}observeLedger(e){this.ledgerWrites.inc({outcome:e.outcome,reason:e.reason})}observe(e){let n=Ke(e.path),o=e.status!==null&&!(e.status>=400&&e.status<500),r=this.modelLabel.overflowCount+this.orgLabel.overflowCount,a=this.modelLabel.of(e.model,o),s=this.orgLabel.of(e.org,o);this.modelLabel.overflowCount+this.orgLabel.overflowCount-r>0&&(this.modelLabel.overflowCount>r?this.labelOverflows.inc({label:"model"}):this.labelOverflows.inc({label:"org"}));let l=e.stream===!0?"true":"false";this.requests.inc({lane:n,method:e.method===""?I:e.method,status_class:Ge(e.status),stream:l,model:a,org:s}),this.duration.observe({lane:n,stream:l,model:a,org:s},e.duration_ms/1e3),e.ttft_ms!==null&&this.ttft.observe({lane:n,model:a,org:s},e.ttft_ms/1e3),e.stream_error&&this.streamErrors.inc({lane:n,model:a}),e.delivered||this.undelivered.inc({lane:n,model:a})}scrape(){return this.registry.metrics()}get contentType(){return this.registry.contentType}};function ve(t){return $e((e,n)=>{(async()=>{let o=(e.url??"/").split("?")[0];if(e.method!=="GET"||o!==be){n.writeHead(404,{"content-type":"text/plain"}),n.end(`no route for ${e.method} ${o} \u2014 this port serves only GET ${be}
|
|
2
|
-
`);return}let r=await t.scrape();n.writeHead(200,{"content-type":t.contentType}),n.end(r)})().catch(o=>{if(!n.headersSent){n.writeHead(500,{"content-type":"text/plain"}),n.end(`${String(o instanceof Error?o.message:o)}
|
|
3
|
-
`);return}n.end()})})}p();import{createHash as Ee}from"crypto";import{createServer as Be}from"http";var $t=9465,ze="x-urun-key-id",je="x-urun-key-fingerprint",We="urun-ratelimit-bucket:",Je="urun-ratelimit-tier:",_e="anonymous",qe=/^urun_sk_[A-Za-z0-9_-]{43}$/;function Ve(t){return Ee("sha256").update(`${We}${t}`).digest("hex")}function Ye(t){return Ee("sha256").update(`${Je}${t}`).digest("hex")}function Qe(t){let e=/^Bearer[ \t]+(\S.*)$/i.exec((t??"").trim());if(!e)return null;let n=e[1].trim();return qe.test(n)?n:null}function Re(){return Be((t,e)=>{let n=Qe(t.headers.authorization);e.setHeader(ze,n===null?_e:Ve(n)),e.setHeader(je,n===null?_e:Ye(n)),e.writeHead(200),e.end()})}p();import{createClientToken as Xe,ClientTokenError as Ze}from"@urun-sh/core";var m=class extends Error{constructor(n,o=401){super(n);this.status=o;this.name="ProxyAuthError"}status},_=class extends Error{constructor(e){super(e),this.name="ControlPlaneUnavailableError"}};function S(t){let e=(t??"").trim();if(e==="")throw new m("missing Authorization header \u2014 send your uRun org API key as `Authorization: Bearer <key>`");let n=/^Bearer[ \t]+(\S.*)$/i.exec(e);if(!n)throw new m("malformed Authorization header \u2014 expected `Authorization: Bearer <urun org api key>`");return n[1].trim()}var et=60,tt=6e4,nt=4096;function rt(t){return t===401||t===403}var U=class{constructor(e){this.opts=e;this.mint=e.mint??Xe,this.now=e.now??Date.now}opts;cache=new Map;mint;now;async resolve(e){if(e==="")throw new m("empty Authorization bearer token");let n=this.cache.get(e);if(n&&this.now()-n.at<tt)return{apiKey:e,orgId:n.orgId};let o;try{o=(await this.mint(e,{baseUrl:this.opts.gatewayUrl,expiresIn:et})).orgId}catch(r){throw r instanceof Ze&&rt(r.status)?(this.cache.delete(e),new m("the uRun org API key was rejected by the control plane \u2014 check the key and that it is not revoked")):new _(`could not verify the API key against the uRun control plane at ${this.opts.gatewayUrl}: ${r instanceof Error?r.message:String(r)}`)}if(!o)throw new _("the control plane verified the API key but returned no org_id \u2014 refusing to route a request with no tenancy (control-plane contract violation)");if(this.cache.size>=nt){let r=this.cache.keys().next();r.done||this.cache.delete(r.value)}return this.cache.set(e,{orgId:o,at:this.now()}),{apiKey:e,orgId:o}}clear(){this.cache.clear()}};p();var ot=8080,P="serve",Gt="0.0.0.0";function st(t,e,n){let o=(t[e]??"").trim();if(!o)throw new Error(`${e} is not set \u2014 ${n}`);return o}function Bt(t=process.env){let e=(t.PORT??"").trim(),n=e===""?ot:Number(e);if(!Number.isInteger(n)||n<0||n>65535)throw new Error(`PORT must be an integer in 0..65535, got ${JSON.stringify(e)}`);let o=ne(k(st(t,"URUN_BASE_URL","the hosted proxy needs the uRun session-gateway base it opens sessions and verifies API keys against (e.g. https://api.urun.sh)"),"URUN_BASE_URL")),r=(t.URUN_API_URL??"").trim(),a=r?k(r,"URUN_API_URL"):`${o.replace(/\/+$/,"")}/v1`,s=(t.URUN_CATALOG_URL??"").trim(),i=(t.URUN_CATALOG_ANON_KEY??"").trim();if(!!s!=!!i)throw new Error("URUN_CATALOG_URL and URUN_CATALOG_ANON_KEY must be set together (or both left unset) \u2014 exactly one of them is set, which would silently disable the model-catalog oracle");let l=s?{catalogUrl:k(s,"URUN_CATALOG_URL"),anonKey:i}:null,u=(t.URUN_SESSION_TOKEN_SECRET??"").trim(),f=(t.URUN_SHARED_ORG_API_KEY??"").trim(),x=(t.URUN_SHARED_ORG_ID??"").trim();if(!!f!=!!x)throw new Error("URUN_SHARED_ORG_API_KEY and URUN_SHARED_ORG_ID must be set together (or both left unset) \u2014 exactly one of them is set, which would silently leave the shared endpoints on the session lane");return{port:n,baseUrl:o,apiUrl:a,catalog:l,decisionSecret:u,sharedOrgKey:f,sharedOrgId:x}}p();import{createHash as it,randomUUID as at}from"crypto";var lt=512;function M(t){return`urun-inference-proxy-${it("sha256").update(t).digest("hex").slice(0,32)}`}var $=class{constructor(e){this.opts=e}opts;tenants=new Map;label(e,n){return Object.assign(Object.create(e),{tenant:n.orgId})}build(e){if(this.opts.build){let i=this.opts.build(e);return{router:i.router,clients:this.label(i.clients,e)}}let n=0,o=M(e.apiKey),r=()=>{n+=1},a=j({baseUrl:this.opts.baseUrl,orgId:e.orgId,defaultApp:null,fnName:P,sharedOnlyListing:!0,apiUrl:this.opts.apiUrl,auth:{lane:"api-key",apiKey:e.apiKey,gatewayUrl:this.opts.baseUrl},catalog:this.opts.catalog,subject:()=>n===0?o:`${o}#${n}`,onPoolWedged:r,release:"end"}),s=this.opts.decisionIntake?new fe({targetFor:i=>a.targetFor(i),apiUrl:this.opts.apiUrl,apiKey:e.apiKey,orgId:e.orgId,fnName:P,secret:this.opts.decisionIntake.secret,...this.opts.decisionIntake.shared?{shared:this.opts.decisionIntake.shared}:{}}):void 0;return{router:a,clients:this.label(W(a,void 0,r,s),e)}}clientsFor(e){let n=this.tenants.get(e.apiKey);if(n)return this.tenants.delete(e.apiKey),this.tenants.set(e.apiKey,n),n.clients;for(;this.tenants.size>=lt;){let r=this.tenants.entries().next();if(r.done)break;let[a,s]=r.value;this.tenants.delete(a),s.router.closeAll().catch(()=>{})}let o=this.build(e);return this.tenants.set(e.apiKey,o),o.clients}get size(){return this.tenants.size}async closeAll(){let e=[...this.tenants.values()].map(o=>o.router);this.tenants.clear();let n=[];if(await Promise.all(e.map(async o=>{try{await o.closeAll()}catch(r){n.push(String(r instanceof Error?r.message:r))}})),n.length>0)throw new Error(`failed to close ${n.length} tenant backhaul(s): ${n.join("; ")}`)}},Se=512,ct=1024,L=class extends Error{constructor(){super(`conversation capacity reached (${Se} conversations on this replica) \u2014 every slot holds an active or resumable conversation; retry later or scale out`),this.name="ConversationCapacityError"}};function ut(t){return{sessionStartOptions:e=>{if(e.startsWith(z))return;let n=t.get(e);return n===void 0?void 0:{sessionId:n}},onSessionOpen:(e,n)=>{e.startsWith(z)||t.set(e,n)}}}function dt(t,e,n,o){let r=j({baseUrl:t.baseUrl,orgId:e.orgId,defaultApp:null,fnName:P,sharedOnlyListing:!0,apiUrl:t.apiUrl,auth:{lane:"api-key",apiKey:e.apiKey,gatewayUrl:t.baseUrl},catalog:t.catalog,subject:n.subject,release:"detach",...ut(o)});return{clients:W(r),release:async()=>{await r.closeAll()}}}var H=class{constructor(e){this.opts=e}opts;byCaller=new Map;byHandle=new Map;build(e,n){let o={id:n,subject:`${M(e.apiKey)}:live:${n}`},r=new Map,a=this.opts.build?this.opts.build(e,o):dt(this.opts,e,o,r);return{id:n,backhaul:a,attachments:0,nativeRefs:r}}attachEntry(e,n){let o=this.byCaller.get(e.apiKey);o===void 0&&(o=new Map,this.byCaller.set(e.apiKey,o));let r=o.get(n);if(r!==void 0)return o.delete(n),o.set(n,r),r.attachments+=1,r;this.evictOverflow();let a=this.build(e,n);return a.attachments=1,o.set(n,a),a}get conversationCount(){let e=0;for(let n of this.byCaller.values())e+=n.size;return e}evictOverflow(){for(;this.conversationCount>=Se;){let e=this.byCaller.keys().next();if(e.done)throw new L;let n=e.value,o=this.byCaller.get(n),r=!1;for(let[a,s]of o)if(!(s.attachments>0)){o.delete(a),this.dropHandleIndexFor(n,a),s.backhaul.release().catch(()=>{}),r=!0;break}if(!r)throw o.size===0&&this.byCaller.delete(n),new L;o.size===0&&this.byCaller.delete(n)}}dropHandleIndexFor(e,n){for(let[o,r]of this.byHandle)r.apiKey===e&&r.conversationId===n&&this.byHandle.delete(o)}connectionFor(e){let n=null,o=!1,r=()=>(n===null&&(n=this.attachEntry(e,at())),n),a={createResponse:(s,i)=>r().backhaul.clients.createResponse(s,i),catalogIdentity:s=>r().backhaul.clients.catalogIdentity(s),servingAppSlug:s=>{let i=r().backhaul.clients.servingAppSlug;if(i===void 0)throw new Error("the conversation backhaul wires no servingAppSlug seam \u2014 the caller-org app a request resolves to cannot be known");return i(s)},listModels:()=>r().backhaul.clients.listModels(),sessionHandle:s=>r().backhaul.clients.sessionHandle(s),createResponseOn:(s,i)=>r().backhaul.clients.createResponseOn(s,i),onSessionEnd:(s,i)=>r().backhaul.clients.onSessionEnd(s,i)};return a.openAudio=async(s,i)=>{let l=r().backhaul.clients.openAudio;if(l===void 0)throw new Error("the conversation backhaul wires no openAudio seam \u2014 voice is unavailable on this deployment");return l(s,i)},a.openVideo=async s=>{let i=r().backhaul.clients.openVideo;if(i===void 0)throw new Error("the conversation backhaul wires no openVideo seam \u2014 video-in is unavailable on this deployment");return i(s)},a.openVideoOut=async s=>{let i=r().backhaul.clients.openVideoOut;if(i===void 0)throw new Error("the conversation backhaul wires no openVideoOut seam \u2014 video-out is unavailable on this deployment");return i(s)},a.supportsAudio=async s=>{let i=r().backhaul.clients.supportsAudio;if(i===void 0)throw new Error("the conversation backhaul wires no supportsAudio seam \u2014 the realtime lane cannot gate audio modality on this deployment");return i(s)},a.supportsSpeech=async s=>{let i=r().backhaul.clients.supportsSpeech;if(i===void 0)throw new Error("the conversation backhaul wires no supportsSpeech seam \u2014 the speech lane cannot gate model capability on this deployment");return i(s)},a.speechVoices=async s=>{let i=r().backhaul.clients.speechVoices;if(i===void 0)throw new Error("the conversation backhaul wires no speechVoices seam \u2014 the speech lane cannot resolve the voice set /v1/models publishes on this deployment");return i(s)},a.speechSessionHandle=async s=>{let i=r().backhaul.clients.speechSessionHandle;if(i===void 0)throw new Error("the conversation backhaul wires no speechSessionHandle seam \u2014 the speech lane cannot resolve its own pooled session on this deployment");return i(s)},a.openAudioOn=async s=>{let i=r().backhaul.clients.openAudioOn;if(i===void 0)throw new Error("the conversation backhaul wires no openAudioOn seam \u2014 the speech lane cannot open its pinned native session audio on this deployment");return i(s)},a.evictSpeechSession=async s=>{let i=r().backhaul.clients.evictSpeechSession;if(i===void 0)throw new Error("the conversation backhaul wires no evictSpeechSession seam \u2014 the speech lane cannot retire its pinned speech session after a failed turn on this deployment");return i(s)},{clients:a,bindResumeHandle:s=>{let i=n;if(i===null)throw new Error("bindResumeHandle before the conversation opened \u2014 no conversation to bind to");for(;this.byHandle.size>=ct;){let l=this.byHandle.keys().next();if(l.done)break;this.byHandle.delete(l.value)}this.byHandle.set(s,{apiKey:e.apiKey,conversationId:i.id})},clientsForResume:async s=>{let i=this.byHandle.get(s),l=i===void 0?void 0:this.byCaller.get(i.apiKey),u=i===void 0||l===void 0?void 0:l.get(i.conversationId);if(i===void 0||u===void 0||i.apiKey!==e.apiKey)throw new Q("the conversation behind this resume handle is gone (evicted at capacity, or the replica restarted) or the handle is not yours \u2014 reconnect without a handle to start a new conversation");return n=u,u.attachments+=1,u.backhaul.clients},release:async()=>{if(o)return;o=!0;let s=n;n=null,s!==null&&(s.attachments-=1,!(s.attachments>0)&&await s.backhaul.release().catch(()=>{}))}}}async detachAll(){let e=[];for(let o of this.byCaller.values())for(let r of o.values())e.push(r.backhaul.release());this.byCaller.clear(),this.byHandle.clear();let n=[];if(await Promise.all(e.map(async o=>{try{await o}catch(r){n.push(String(r instanceof Error?r.message:r))}})),n.length>0)throw new Error(`failed to detach ${n.length} conversation backhaul(s): ${n.join("; ")}`)}};p();var ht="/inference-ledger",pt=1e4,D=class extends Error{ledgerFailureReason;constructor(e,n="duplicate"){super(e),this.name="LedgerDuplicateError",this.ledgerFailureReason=n}},q=class extends Error{constructor(n,o){super(o);this.ledgerAlreadyWritten=n;this.name="LedgerAlreadyWrittenError"}ledgerAlreadyWritten},g=class extends Error{ledgerFailureReason="surface";constructor(e){super(e),this.name="LedgerSurfaceError"}},V=class extends m{ledgerFailureReason="auth";constructor(e){super(e),this.name="LedgerAuthError"}},gt=[["inference_id",t=>typeof t=="string"&&/[^\s]/.test(t)],["model_id",t=>typeof t=="string"&&/[^\s]/.test(t)],["variant",t=>t===null||typeof t=="string"],["task",t=>typeof t=="string"&&/[^\s]/.test(t)],["http_status",t=>typeof t=="number"&&Number.isInteger(t)],["started_at",t=>typeof t=="string"&&!Number.isNaN(Date.parse(t))],["completed_at",t=>typeof t=="string"&&!Number.isNaN(Date.parse(t))],["prompt_tokens",t=>t===null||typeof t=="number"&&Number.isInteger(t)],["completion_tokens",t=>t===null||typeof t=="number"&&Number.isInteger(t)],["output_units",xe],["cost_nano_usd",xe],["price_version",t=>t===null||typeof t=="string"]];function ft(t){for(let[e,n]of gt){if(!(e in t))return`${e} (absent)`;if(!n(t[e]))return`${e} (${JSON.stringify(t[e])})`}return null}function mt(t){let e;try{e=JSON.parse(t)}catch{return{status:"unavailable"}}if(e===null||typeof e!="object"||Array.isArray(e))return{status:"unavailable"};let n=e.existing_status;if(n==="not_in_this_org")return{status:"not_in_this_org"};if(n!=="found")return{status:"unavailable"};let o=e.existing;return o===null||typeof o!="object"||Array.isArray(o)?{status:"unavailable"}:ft(o)!==null?{status:"unavailable"}:{status:"found",row:o}}function xe(t){return t===null?!0:typeof t=="number"?Number.isFinite(t):typeof t!="string"?!1:/[^\s]/.test(t)&&Number.isFinite(Number(t))}function J(t,e){return t===null?e===null:e===null?!1:Number(e)===t}function Ce(t,e){if(typeof e!="string")return!1;let n=Date.parse(t),o=Date.parse(e);return Number.isFinite(n)&&Number.isFinite(o)&&n===o}function yt(t,e){let n=e;return n.inference_id===t.inference_id&&n.model_id===t.model_id&&(n.variant??null)===t.variant&&n.task===t.task&&n.http_status===t.http_status&&Ce(t.started_at,n.started_at)&&Ce(t.completed_at,n.completed_at)&&J(t.prompt_tokens,n.prompt_tokens)&&J(t.completion_tokens,n.completion_tokens)&&J(t.output_units,n.output_units)}function wt(t,e){if(t===null||typeof t!="object"||Array.isArray(t))throw new g(`${e} answered a "recorded" that is not an object: ${JSON.stringify(t)}`);let n=t,o=n.inference_id;if(typeof o!="string"||o==="")throw new g(`${e} answered a row with no inference_id: ${JSON.stringify(t)}`);if(!("cost_nano_usd"in n))throw new g(`${e} answered a row with no cost_nano_usd field: ${JSON.stringify(t)}`);let r=n.cost_nano_usd;if(r!=null&&!(typeof r=="number"&&Number.isInteger(r)&&r>=0))throw new g(`${e} answered cost_nano_usd ${JSON.stringify(r)}, which is neither a non-negative integer nor null`);let a=u=>{if(!(u in n))throw new g(`${e} answered a row with no ${u} field: ${JSON.stringify(t)}`);let f=n[u];if(f===null)return null;if(typeof f!="string")throw new g(`${e} answered a non-string ${u}: ${JSON.stringify(f)}`);if(!/[^\s]/.test(f))throw new g(`${e} answered a BLANK ${u} (${JSON.stringify(f)}) \u2014 it satisfies "is a string" while naming nothing, which is exactly what inference_requests_priced_attribution_check exists to forbid`);return f},s=a("price_version"),i=a("unpriced_reason"),l=r!=null;if(l&&(s===null||i!==null))throw new g(`${e} answered a PRICED row (cost_nano_usd ${String(r)}) that is not cleanly attributed \u2014 price_version ${JSON.stringify(s)}, unpriced_reason ${JSON.stringify(i)}`);if(!l&&(s!==null||i===null))throw new g(`${e} answered an UNPRICED row that does not say why \u2014 price_version ${JSON.stringify(s)}, unpriced_reason ${JSON.stringify(i)}. NULL means NOT YET PRICED, and the reason is how an operator learns that traffic is being served which nobody can bill.`);return{inference_id:o,cost_nano_usd:r===void 0?null:r,price_version:s,unpriced_reason:i}}var F=class{constructor(e){this.opts=e;this.fetchImpl=e.fetchImpl??fetch}opts;fetchImpl;async write(e,n){let o=`${this.opts.apiUrl.replace(/\/+$/,"")}${ht}`,r;try{r=await this.fetchImpl(o,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify(n),signal:AbortSignal.timeout(pt)})}catch(l){throw new g(`the uRun ledger surface at ${o} could not be reached: ${l instanceof Error?l.message:String(l)}`)}if(r.status===409){let l=await r.text().catch(()=>""),u=mt(l);throw u.status==="found"&&yt(n,u.row)?new q(u.row,`the uRun control plane already holds THIS PROXY'S OWN row for inference_id ${n.inference_id} \u2014 every billable field matches the row being re-sent, so an earlier attempt landed and this retry is a no-op. Nothing was written and nothing was overwritten.`):u.status==="not_in_this_org"?new D(`inference_id ${n.inference_id} is ALREADY TAKEN by a ledger row this org cannot read. The id space is a global primary key, so this is not a retry and cannot be this proxy's own write \u2014 either two orgs collided on a uuid, or a row was written for this id by somebody else.`,"duplicate_foreign"):new D(`the uRun control plane already holds a ledger row for inference_id ${n.inference_id} that is NOT the one being written${u.status==="unavailable"?" (and it could not be read back to compare)":""} \u2014 nothing was written and nothing was overwritten. Either this proxy wrote twice, or a row was fabricated ahead of it (this write is authenticated by the CALLER'S OWN org key). ${l.slice(0,400)}`)}if(r.status===401||r.status===403)throw await r.text().catch(()=>{}),new V("the uRun org API key was rejected by the uRun control plane when writing the inference ledger \u2014 the request was served but produced no billable row");if(!r.ok){let l=await r.text().catch(u=>`<body unreadable: ${u instanceof Error?u.message:String(u)}>`);throw new g(`the uRun ledger surface at ${o} answered HTTP ${r.status}: ${l.slice(0,400)}`)}let a;try{a=await r.json()}catch(l){throw new g(`the uRun ledger surface at ${o} answered a body that is not JSON: ${l instanceof Error?l.message:String(l)}`)}let s=a?.recorded;if(s===void 0)throw new g(`the uRun ledger surface at ${o} answered a body with no "recorded" field`);let i=wt(s,`the uRun ledger surface at ${o}`);if(i.inference_id!==n.inference_id)throw new g(`the uRun ledger surface at ${o} answered for inference_id ${i.inference_id} when asked about ${n.inference_id}`);return i}};p();import{createServer as vt}from"http";p();var K="/inference-usage",Oe=25e3;var bt=new Set(["Error","TypeError","SyntaxError","TimeoutError"]);function ke(t){if(!(t instanceof Error))return`a ${typeof t}`;let e=bt.has(t.name)?t.name:"Error",n=t.cause?.code;return typeof n=="string"&&/^[A-Z][A-Z0-9_]{0,31}$/.test(n)?`${e} (${n})`:e}var G=class{constructor(e){this.opts=e;this.fetchImpl=e.fetchImpl??fetch}opts;fetchImpl;async lookup(e,n){let o=`${this.opts.apiUrl.replace(/\/+$/,"")}${K}`,r;try{r=await this.fetchImpl(o,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},body:JSON.stringify({inference_ids:n}),signal:AbortSignal.timeout(Oe)})}catch(s){throw new A(`the uRun usage surface (POST ${K}) could not be reached within ${Oe}ms: ${ke(s)}`)}if(r.status===401||r.status===403)throw await r.text().catch(()=>{}),new m("the uRun org API key was rejected by the uRun control plane \u2014 check the key and that it is not revoked");if(!r.ok){let s=await r.text().catch(()=>null),i=s===null?"body unreadable":`${s.length}-character body`;throw new A(`the uRun usage surface (POST ${K}) answered HTTP ${r.status} (${i})`)}let a;try{a=await r.json()}catch(s){throw new A(`the uRun usage surface (POST ${K}) answered HTTP ${r.status} with a body that is not JSON: ${ke(s)}`)}return le(a,this.opts.onRejectedRow)}};var _t=5e3,Et=3e3,Rt="/api/client-tokens";function Y(t,e,n){t.writeHead(e,{"content-type":"application/json"}),t.end(JSON.stringify(n))}function Ae(t,e,n,o){Y(t,e,{error:{message:n,type:o}})}async function St(t,e=fetch){let n=`${t.replace(/\/+$/,"")}${Rt}`;try{let o=await e(n,{method:"POST",headers:{"content-type":"application/json"},body:"{}",signal:AbortSignal.timeout(Et)});return o.status===404?{ready:!1,detail:`${n} answered 404 \u2014 this is not a uRun control plane`}:o.status>=500?{ready:!1,detail:`${n} answered HTTP ${o.status}`}:{ready:!0,detail:`${n} answered HTTP ${o.status}`}}catch(o){return{ready:!1,detail:`${n} unreachable: ${o instanceof Error?o.message:String(o)}`}}}function _n(t){let e=t.resolver??new U({gatewayUrl:t.baseUrl}),n=t.decisionSecret??"",o=t.sharedOrgKey&&t.sharedOrgId?{apiKey:t.sharedOrgKey}:void 0,r=t.registry??new $({baseUrl:t.baseUrl,apiUrl:t.apiUrl,catalog:t.catalog,...n!==""?{decisionIntake:{secret:n,...o?{shared:o}:{}}}:{}});n!==""&&process.stderr.write(`inference-proxy: stateless direct path ENABLED \u2014 caller-org decisions dial ready runtimes directly (no session mint)
|
|
4
|
-
`);let a=t.probeControlPlane??(()=>St(t.baseUrl)),s=t.usageClient??new G({apiUrl:t.apiUrl}),i=t.ledgerClient??new F({apiUrl:t.apiUrl}),l=new T(t.defaultMetrics===void 0?{}:{defaultMetrics:t.defaultMetrics}),u=async c=>e.resolve(S(c.headers.authorization)),f=c=>c instanceof m?{status:c.status,openaiType:"authentication_error",anthropicType:"authentication_error"}:c instanceof _?{status:502,openaiType:"upstream_error",anthropicType:"api_error"}:null,x=me({clients:async c=>r.clientsFor(await u(c)),usage:{authorize:c=>S(c.headers.authorization),lookup:(c,d)=>s.lookup(c,d)},statusOf:f,...t.requestLog?{requestLog:t.requestLog}:{},observe:c=>l.observe(c),ledger:{write:(c,d)=>i.write(S(c.headers.authorization),d),observe:c=>l.observeLedger(c),...t.ledgerLog?{report:t.ledgerLog}:{}}}),v=null,E=vt((c,d)=>{let h=new URL(c.url??"/","http://localhost");(async()=>{if(c.method==="GET"&&h.pathname==="/healthz"){Y(d,200,{ok:!0});return}if(c.method==="GET"&&h.pathname==="/readyz"){let O=Date.now();if(!v||O-v.at>=_t){let Te=await a();v={at:O,...Te}}Y(d,v.ready?200:503,{ok:v.ready,control_plane:v.detail});return}let y=h.pathname==="/v1"||h.pathname.startsWith("/v1/"),C=h.pathname.startsWith("/partners/");if(!y&&!C){Ae(d,404,`no route for ${c.method} ${h.pathname} \u2014 this endpoint serves the OpenAI-compatible /v1 surface, the partner adapter surface (/partners/...), the WS upgrade paths (/ws/google.ai.generativelanguage.v1beta.GenerativeService.BidiGenerateContent and /v1/realtime), plus /healthz and /readyz`,"invalid_request_error");return}x(c,d)})().catch(y=>{if(!d.headersSent){Ae(d,500,String(y instanceof Error?y.message:y),"proxy_error");return}d.end()})}),B=t.conversations??new H({baseUrl:t.baseUrl,apiUrl:t.apiUrl,catalog:t.catalog}),Ie=[Z(E,{authenticate:async c=>{let d=await e.resolve(c),h=B.connectionFor(d);return{clients:h.clients,scope:M(d.apiKey),onBindResumeHandle:h.bindResumeHandle,clientsForResume:h.clientsForResume,release:h.release}}}),te(E,{clients:async c=>r.clientsFor(await u(c)),statusOf:f}).close,oe(E,{resolver:{resolve:async(c,d)=>{let h=await e.resolve(S(c.headers.authorization)),y=B.connectionFor(h),C;try{C=await ie(y.clients,se(c),d)}catch(O){throw y.release().catch(()=>{}),O}return{...C,release:y.release}}}})];E.on("upgrade",(c,d)=>{let h=new URL(c.url??"/","http://localhost");h.pathname===X||h.pathname===ee||h.pathname===re||d.destroyed||(d.write(`HTTP/1.1 404 Not Found\r
|
|
5
|
-
Connection: close\r
|
|
6
|
-
\r
|
|
7
|
-
no WS route for ${h.pathname} \u2014 this endpoint serves only the Gemini Live, /v1/responses, and /v1/realtime upgrade paths`),d.destroy())});let Ne=async()=>{for(let c of Ie)await c();await B.detachAll()};return{server:E,metricsServer:ve(l),metrics:l,edgeIdentityServer:Re(),registry:r,closeAll:()=>r.closeAll(),closeWebSockets:Ne}}async function En(t,e,n){n&&await n();let o=new Promise(r=>t.close(()=>r()));t.closeAllConnections(),await o,await pe()||ge(),await e()}export{It as a,be as b,I as c,Le as d,He as e,Me as f,Ke as g,Ge as h,N as i,T as j,ve as k,$t as l,ze as m,je as n,We as o,Je as p,_e as q,Ve as r,Ye as s,Qe as t,Re as u,m as v,_ as w,S as x,tt as y,nt as z,U as A,ot as B,P as C,Gt as D,Bt as E,lt as F,M as G,$ as H,Se as I,ct as J,L as K,H as L,ht as M,pt as N,D as O,g as P,wt as Q,F as R,_t as S,Et as T,_n as U,En as V};
|
package/dist/chunk-QTVTCMJU.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c as a}from"./chunk-YSFSRI3D.js";a();function _(e,t,o,n){let r=e.response??{},i={request_id:o,consumer_id:n,stream:!0,kind:"chat",messages:t};return typeof r.instructions=="string"&&(i.instructions=r.instructions),Array.isArray(r.modalities)&&(i.modalities=r.modalities),typeof r.temperature=="number"&&(i.temperature=r.temperature),typeof r.max_output_tokens=="number"&&(i.max_output_tokens=r.max_output_tokens),r.tools!==void 0&&(i.tools=r.tools),i}function f(e,t,o){let n={request_id:t,consumer_id:o,stream:!!e.stream,kind:"responses",input:e.input};return e.model&&(n.model=e.model),e.tools!==void 0&&(n.tools=e.tools),e.tool_choice!==void 0&&(n.tool_choice=e.tool_choice),typeof e.temperature=="number"&&(n.temperature=e.temperature),typeof e.max_output_tokens=="number"&&(n.max_output_tokens=e.max_output_tokens),typeof e.top_p=="number"&&(n.top_p=e.top_p),Array.isArray(e.stop)&&e.stop.length>0&&(n.stop=e.stop),typeof e.reasoning_effort=="string"&&(n.reasoning_effort=e.reasoning_effort),typeof e.instructions=="string"&&(n.instructions=e.instructions),e.chat_template_kwargs!==void 0&&(n.chat_template_kwargs=e.chat_template_kwargs),e.response_format!==void 0&&(n.response_format=e.response_format),typeof e.voice=="string"&&(n.voice=e.voice),n}a();function y(e){if(e instanceof Error)return{type:"error",error:{type:"urun_error",code:e.name||null,message:e.message}};if(e&&typeof e=="object"&&e.t==="error"){let t=e,o=t.body??{},n=o.error,r=n?.message??o.message??`unreadable serve error body: ${JSON.stringify(t.body??null)}`;return{type:"error",error:{type:n?.type??"urun_error",code:t.code??n?.code??null,message:r}}}return{type:"error",error:{type:"urun_error",code:null,message:String(e)}}}a();a();var w="llm-resp";async function*m(e,t){let o=`${w}:${t}`,n=`resp_${t}`;yield{type:"response.created",response:{id:n,status:"in_progress"}};let r=new Map;try{for await(let i of e.stream(o).messages()){let s=i;if(s.t==="delta"){if(typeof s.delta=="string"&&(yield{type:"response.output_text.delta",item_id:n,delta:s.delta}),typeof s.reasoning=="string"&&(yield{type:"response.reasoning_text.delta",item_id:n,delta:s.reasoning}),Array.isArray(s.tool_calls))for(let u of s.tool_calls){let d=typeof u.index=="number"?u.index:0,c=r.get(d)??{};u.id&&(c.id=u.id),u.function?.name&&(c.name=u.function.name),r.set(d,c),yield{type:"response.function_call_arguments.delta",item_id:`fc_${t}_${d}`,tool_index:d,call_id:c.id,name:c.name,delta:u.function?.arguments??""}}}else if(s.t==="response"){yield{type:"response.completed",response:{id:n,status:"completed",...s.body}};return}else if(s.t==="error"){yield y(s);return}}}finally{try{e.doc("llm").set({requests:{[t]:null}})}catch{}}}a();var k=class e{constructor(t,o=e.DEFAULT_DOC_SYNC_GATE_TIMEOUT_MS){this.session=t;this.docSyncTimeoutMs=o;this.sessionTag=t.sessionId??globalThis.crypto.randomUUID()}session;docSyncTimeoutMs;sessionTag;static DEFAULT_DOC_SYNC_GATE_TIMEOUT_MS=15e3;get consumerId(){return this.session.consumerId}async ensureDocSynced(){let t=this.session.doc("llm");if(t.synced===!0||typeof t.onSynced!="function")return;let o;await new Promise((r,i)=>{let s=t.onSynced(()=>{clearTimeout(o),r()});o=setTimeout(()=>{s(),i(new Error(`[urun] llm doc plane never synced within ${this.docSyncTimeoutMs}ms \u2014 the request envelope write is withheld rather than raced into a possibly-lost pre-sync write (resumed-attach first-write-lost, prod-usw2 2026-09-23)`))},this.docSyncTimeoutMs)})}write(t){t.session_tag=this.sessionTag,this.session.doc("llm").set({requests:{[t.request_id]:{payload:t,consumer_id:t.consumer_id,stream:t.stream}}})}sendResponses(t,o){return this.writeGated(o,()=>{let n=f(t,o,this.consumerId);return this.write(n),m(this.session,o)})}sendResponseCreate(t,o,n){return this.writeGated(n,()=>{let r=_(t,o,n,this.consumerId);return this.write(r),m(this.session,n)})}async writeGated(t,o){try{await this.ensureDocSynced()}catch(n){throw new Error(`[urun] responses dial ${t} aborted before its envelope write: ${n instanceof Error?n.message:String(n)}`)}return o()}};export{_ as a,f as b,y as c,m as d,k as e};
|
package/dist/chunk-TDRZZGUK.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as C}from"./chunk-K23AZPI4.js";import{a as L}from"./chunk-YXK42SKC.js";import{a as x,b as I,e as v,f as G,g as D,h as U,j as M}from"./chunk-WCMX5VOF.js";import{c as E,f as k,g as R,p as F}from"./chunk-6M5JK4YC.js";import{c as w}from"./chunk-YSFSRI3D.js";w();import{App as H,createClientToken as Y}from"@urun-sh/core";w();import{SessionFailedError as W}from"@urun-sh/core";var b=2;function q(e){let s=e?.type;return s==="response.created"||s==="response.in_progress"}function K(e){let s=e?.type;return s==="response.completed"||s==="error"}function $(e){return async function(i,p){return(async function*(){for(let r=1;r<=b;r++){let{app:d,entry:c,identity:l}=await e.sessionFor(i.model);p?.({sessionId:e.sessionIdOf?.(c)??null,identity:l});let h=!1,a=!1;try{let n=await e.createStream(c,i);for await(let o of n){if(K(o)&&(h=!0),q(o)){if(r>1)continue}else a=!0;yield o}}catch(n){if(n instanceof W&&(n.kind==="never-live"||n.kind==="create-failed")){try{e.onAdmissionVerdict?.(d,c,n)}catch{}throw n}if(r<b&&!a){await e.evict(d,c);continue}throw n}if(h)return;if(r<b&&!a){await e.evict(d,c);continue}return}})()}}w();var J=3,Q="llm",X=new Set(["paused","error"]);function j(e,s){let i=e.doc(Q),p=typeof i.onConnectionState=="function",u=typeof e.onPhase=="function";if(!p&&!u)throw new Error(`session for app "${s}" exposes neither a doc connection-state surface nor onPhase \u2014 session-gone detection requires @urun-sh/core Session (SessionDocument.onConnectionState / Session.onPhase); pooling a session with no native lifecycle signal would silently swallow requests dispatched after the platform closes it`);let r=null,d=!1,c=new Set,l=[],h=a=>{if(!r){r=a;for(let n of[...c])n(a);c.clear()}};return p&&l.push(i.onConnectionState(a=>{d&&(a.consecutiveFailures<J||h(new v(`the pooled session for app "${s}" is gone: its doc backhaul failed ${a.consecutiveFailures} consecutive times (last close ${a.lastCloseCode===null?"by provider liveness reap":`code ${a.lastCloseCode}`}${a.lastCloseReason?`, reason ${JSON.stringify(a.lastCloseReason)}`:""}) \u2014 the platform closed the session (idle timeout or a serve-side crash). The entry is evicted; retry to get a fresh session.`)))})),u&&l.push(e.onPhase(a=>{if(a.name==="live"){d=!0;return}d&&X.has(a.name)&&h(new v(`the pooled session for app "${s}" lost its compute binding (phase "${a.name}"). The entry is evicted; retry to get a fresh session. The named session itself is not over \u2014 it stays attachable by name.`))})),{gone:()=>r,onGone(a){return r?(a(r),()=>{}):(c.add(a),()=>c.delete(a))},dispose(){c.clear();for(let a of l.splice(0))a()}}}async function*A(e,s){let i=s.gone();if(i)throw i;let p=e[Symbol.asyncIterator](),u,r=new Promise((d,c)=>{u=s.onGone(l=>c(l))});r.catch(()=>{});try{for(;;){let d=await Promise.race([p.next(),r]);if(d.done)return;yield d.value}}finally{u?.(),p.return?.(void 0)}}function V(e,s,i,p){let u=e[i];if(typeof u!="function")throw new Error(`app "${s}" has no function "${i}" (set URUN_FUNCTION)`);let r=p?u(void 0,p):u();if(typeof r.end!="function")throw new Error(`app "${s}" function "${i}" returned a session without end()`);return r}function z(e,s,i){let{baseUrl:p,orgId:u,auth:r}=e,d=typeof e.subject=="function"?e.subject():e.subject,c=i.startsWith(D)&&d!==void 0?`${d}:speech`:d,l=s.fnName??e.fnName,h=e.sessionStartOptions?.(i),a=s.sharedApp?{sharedApp:!0,...h}:h,n=m=>({session:m,responses:new L(m),gone:j(m,i)});if(r.lane==="jwt"){let m=H(s.appSlug,{baseUrl:p,orgId:u,jwt:r.jwt});return n(V(m,s.appSlug,l,a))}let{apiKey:o,gatewayUrl:t}=r,f=H(s.appSlug,{baseUrl:p,orgId:u,getAccessToken:async()=>(await Y(o,{baseUrl:t,expiresIn:300,allowedFunctions:[`${s.appSlug}/${l}`],...c===void 0?{}:{subject:c},...s.sharedApp?{sharedApp:!0}:{}})).token});return n(V(f,s.appSlug,l,a))}function Z(e){let s=e.session.id;if(typeof s!="string"||s.length===0)throw new Error("pooled session exposes no stable session id \u2014 the session-identity seam requires @urun-sh/core Session (session.id)");return s}function ee(e){if(e.kind==="never-live")return!0;if(e.kind!=="create-failed")return!1;let s=e.status.error?.httpStatus,i=e.status.error?.code;return s===410||s===409&&(i==="cancelled"||i==="expired")?!0:i==="client_ended"}var ne=new Set(["error","paused"]);function ge(e){let{auth:s,catalog:i}=e,p=null,u=(d,c)=>{let l=z(e,d,c);return e.onSessionOpen&&typeof l.session.id=="string"&&l.session.id!==""&&e.onSessionOpen(c,l.session.id),l.gone.onGone(()=>{p?.evict(c,l)}),l},r=new M({sharedOnlyListing:e.sharedOnlyListing===!0,defaultApp:e.defaultApp,fnName:e.fnName,openSession:u,closeSession:async d=>{if(d.gone.dispose(),e.release==="detach"){if(typeof d.session.detach!="function")throw new Error('pooled session exposes no native detach() \u2014 the release:"detach" lane requires @urun-sh/core Session.detach');d.session.detach();return}await d.session.end()},listApps:s.lane==="api-key"?()=>G({apiUrl:e.apiUrl,apiKey:s.apiKey}):null,listCatalog:i?()=>C(i):null,listPrices:i?()=>I(i):null,shared:s.lane==="api-key"?()=>x({apiUrl:e.apiUrl,apiKey:s.apiKey}):null,sessionKey:Z});return p=r,e.defaultApp!==null&&e.statelessDirect!==!0&&r.seed(e.defaultApp,u({appSlug:e.defaultApp},U({appSlug:e.defaultApp}))),r}async function P(e,s){let i=e.whenLive;if(typeof i!="function")throw new Error("pooled session exposes no native whenLive() \u2014 the audio lane must wait for the session's own admission (cold wake: queued \u2192 demand \u2192 promote) before wiring media; a session without the core Session.whenLive surface cannot be served audio");await i.call(e,s===void 0?void 0:{signal:s})}async function N(e){if(typeof e.cancel=="function")try{await e.cancel();return}catch{}if(typeof e.detach!="function")throw new Error("pooled session exposes neither cancel() nor detach() \u2014 the client-gone abandon path requires the @urun-sh/core Session native lifecycle surface");e.detach()}function B(e){return e.reason instanceof Error?e.reason:new Error("the client socket closed")}function ve(e,s=R,i,p){let u=new WeakMap,r=new WeakMap,d=new WeakMap,c=new WeakMap,l=n=>{let o=n.session.id;return typeof o=="string"&&o.length>0?o:null},h=(n,o)=>{let t=u.get(n);return t||(t=(async()=>{await P(n.session,o);let f=await E(n.session,await s());return Object.assign(f,{sessionId:n.session.id})})(),u.set(n,t),t.catch(()=>u.delete(n))),t},a=n=>{let o=c.get(n);return o||(o=(async()=>{let t=await h(n);return{appendInputAudio:f=>t.appendInputAudio(f),onOutputAudio:f=>t.onOutputAudio(f),sessionId:l(n)}})(),c.set(n,o),o.catch(()=>c.delete(n))),o};return{createResponse:$({sessionFor:n=>e.sessionFor(n),sessionIdOf:n=>l(n),evict:(n,o)=>e.evict(n,o),onAdmissionVerdict:(n,o,t)=>{ee(t)&&(e.evict(n,o),i?.(n,t.kind==="never-live"?"never-live":"create-failed"))},createStream:async(n,o)=>(await P(n.session),A(await n.responses.responses.create(o),n.gone))}),listModels:()=>e.modelList(),openRouterModels:()=>e.openRouterModels(),supportsImages:(n,o)=>e.supportsImages(n,o),listImageModels:()=>e.imageModelList(),catalogIdentity:n=>e.catalogIdentityFor(n),servingAppSlug:async n=>await e.servingAppSlugFor(n),supportsAudio:n=>e.supportsAudio(n),supportsSpeech:n=>e.supportsSpeech(n),speechVoices:n=>e.speechVoices(n),openAudio:async(n,o)=>{let{entry:t}=await e.sessionFor(n);if(o?.aborted)throw await N(t.session),B(o);let f=h(t,o);return o===void 0?f:new Promise((m,S)=>{let y=()=>{N(t.session).catch(()=>{}),S(B(o))};o.addEventListener("abort",y,{once:!0});let O=()=>o.removeEventListener("abort",y);f.then(g=>{O(),m(g)},g=>{O(),S(g)})})},openAudioOn:async n=>a((await e.sessionForHandle(n)).entry),openVideo:async n=>{let{entry:o}=await e.sessionFor(n),t=r.get(o);return t||(t=k(o.session),r.set(o,t)),t},openVideoOut:async n=>{let{entry:o}=await e.sessionFor(n),t=d.get(o);return t||(t=F(o.session),d.set(o,t)),t},sessionHandle:async n=>(await e.handleFor(n)).handle,speechSessionHandle:async n=>(await e.speechHandleFor(n)).handle,evictSpeechSession:async n=>{let{app:o,entry:t}=await e.sessionForHandle(n);await e.evict(o,t)},createResponseOn:async(n,o)=>{let{entry:t}=await e.sessionForHandle(n);return await P(t.session),A(await t.responses.responses.create(o),t.gone)},onSessionEnd:async(n,o)=>{let{entry:t}=await e.sessionForHandle(n),f=t.session;if(typeof f.onPhase!="function")throw new Error("pooled session exposes no onPhase \u2014 session-end (goAway) wiring requires @urun-sh/core Session");let m=!1;return f.onPhase(S=>{if(m||!ne.has(S.name))return;m=!0;let y=f.endsAt??null;o({timeLeftMs:y?Math.max(0,y.getTime()-Date.now()):null,reason:S.reason??`session ${S.name}`})})},...p?{stateless:p}:{}}}export{ge as a,ve as b};
|