@urun-sh/openai 0.2.60 → 0.3.1

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 (53) hide show
  1. package/dist/ResponsesClient-BYx3YLGo.d.ts +26 -0
  2. package/dist/ResponsesClient-Dft3bg3b.d.cts +26 -0
  3. package/dist/chunk-54VWZJU7.js +6 -0
  4. package/dist/chunk-5NXM4IO3.js +2 -0
  5. package/dist/chunk-CWJRDBDC.js +1 -0
  6. package/dist/chunk-F2TEK34X.js +1 -0
  7. package/dist/chunk-G3NMGP4N.js +1 -0
  8. package/dist/chunk-M6ICU4F5.js +40 -0
  9. package/dist/chunk-UVAY7Q7Z.js +1 -0
  10. package/dist/chunk-XHIIEA6Z.js +1 -0
  11. package/dist/chunk-YSFSRI3D.js +1 -0
  12. package/dist/chunk-ZW6ENXZY.js +1 -0
  13. package/dist/gemini-live.cjs +2 -0
  14. package/dist/gemini-live.d.cts +120 -0
  15. package/dist/gemini-live.d.ts +79 -0
  16. package/dist/gemini-live.js +1 -0
  17. package/dist/hosted/bin.cjs +56 -0
  18. package/dist/hosted/bin.js +23 -0
  19. package/dist/hosted/index.cjs +34 -0
  20. package/dist/hosted/index.d.cts +621 -0
  21. package/dist/hosted/index.d.ts +226 -0
  22. package/dist/hosted/index.js +1 -0
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.cts +4 -4
  25. package/dist/index.d.ts +4 -4
  26. package/dist/index.js +1 -1
  27. package/dist/pi-extension/index.cjs +4 -4
  28. package/dist/pi-extension/index.d.cts +2 -1
  29. package/dist/pi-extension/index.d.ts +2 -1
  30. package/dist/pi-extension/index.js +7 -7
  31. package/dist/pi-extension/standalone.cjs +54 -54
  32. package/dist/proxy/cli.cjs +39 -39
  33. package/dist/proxy/cli.js +14 -19
  34. package/dist/proxy/index.cjs +22 -22
  35. package/dist/proxy/index.d.cts +5 -182
  36. package/dist/proxy/index.d.ts +5 -67
  37. package/dist/proxy/index.js +1 -1
  38. package/dist/server-BfME37pQ.d.cts +200 -0
  39. package/dist/server-l1himPxc.d.ts +82 -0
  40. package/dist/translator-C9uPKypK.d.ts +130 -0
  41. package/dist/translator-CcDBEfvm.d.cts +227 -0
  42. package/dist/{ResponsesClient-y8g6OfNN.d.cts → types-lsVTbNcH.d.cts} +16 -24
  43. package/dist/{ResponsesClient-y8g6OfNN.d.ts → types-lsVTbNcH.d.ts} +8 -24
  44. package/dist/video-out-CWesbk12.d.ts +155 -0
  45. package/dist/video-out-D20UuJ8G.d.cts +298 -0
  46. package/package.json +22 -2
  47. package/dist/chunk-4FUBGLFN.js +0 -41
  48. package/dist/chunk-OLE2YJO3.js +0 -1
  49. package/dist/chunk-SSZL77P5.js +0 -1
  50. package/dist/chunk-VFHQZ4OM.js +0 -1
  51. package/dist/chunk-VXTNG2TP.js +0 -1
  52. package/dist/media-DCHTX3Ez.d.cts +0 -68
  53. package/dist/media-DCHTX3Ez.d.ts +0 -42
@@ -0,0 +1,621 @@
1
+ import { Server } from 'node:http';
2
+ import { createClientToken } from '@urun-sh/core';
3
+ import { U as UrunResponses } from '../ResponsesClient-Dft3bg3b.cjs';
4
+ import { b as UrunSessionLike } from '../types-lsVTbNcH.cjs';
5
+ import { P as ProxyClients } from '../server-BfME37pQ.cjs';
6
+ import '../video-out-D20UuJ8G.cjs';
7
+
8
+ /**
9
+ * Per-model → per-app routing for the compat proxy (owner directive
10
+ * 2026-08-07): swapping the model in a coding harness routes the request to
11
+ * the org's DEPLOYED app for that model. v1 is deployed-only — a uRun model
12
+ * that is not deployed gets a loud 404 naming `urun serve <id>`; a later
13
+ * phase (explicitly out of scope here; urun-infra#1490 shared-endpoints)
14
+ * auto-creates from the model catalog on first request.
15
+ *
16
+ * MODEL-ID SURFACE (the documented mapping): a model may be named by
17
+ * - the app slug itself ("qwen3-6-27b-bf16"), or
18
+ * - the catalog id ("qwen3.6-27b"), or
19
+ * - the catalog id:variant ("qwen3.6-27b:bf16"),
20
+ * where slugification mirrors urun-cli `serve.py _default_app_name` exactly:
21
+ * lowercase, every non-alphanumeric-non-dash character becomes "-", leading/
22
+ * trailing dashes stripped (catalog id "qwen3.6-27b" + variant "bf16" → app
23
+ * "qwen3-6-27b-bf16"). COLLISION RULE: an exact slug match always wins over
24
+ * the catalog-id (prefix) interpretation.
25
+ *
26
+ * RESOLUTION ORDER (one canonical path, documented end to end):
27
+ * 1. model absent / "urun" / an alias of the startup app → DEFAULT app.
28
+ * 2. exact slug match on a deployed serve app → that app.
29
+ * 3. catalog-id form matching exactly one deployed app → that app
30
+ * (two or more candidates → loud ambiguity error naming them).
31
+ * 4. the name maps to an org app that is NOT an active app exposing the
32
+ * proxy's serve function → loud 404
33
+ * naming `urun serve <model>` and the available models.
34
+ * 5. the name matches a catalog row but no deployed app → loud 404
35
+ * naming `urun serve <id>` (deployed-only v1).
36
+ * 6. anything else — a model name outside the uRun namespace entirely
37
+ * (e.g. the harness's own upstream default, "claude-*"/"gpt-*") →
38
+ * DEFAULT app. This IS today's single-app contract, kept deliberately
39
+ * so `urun compat <agent>` with the agent's stock model keeps working
40
+ * with zero new env; the per-model /stats table records every such
41
+ * mapping so it is visible, never silent. Models the proxy ADVERTISES
42
+ * on /v1/models can never land here — they resolve (2/3) or fail loud
43
+ * (4/5) above.
44
+ *
45
+ * NO-DEFAULT MODE (`defaultApp: null`) — the HOSTED multi-tenant lane
46
+ * (`src/hosted/`): a shared endpoint serving every org has no "the app this
47
+ * proxy was started for", so rules 1 and 6 have nothing to fall back TO.
48
+ * Rather than inventing one (picking "some" app for a caller would be the
49
+ * worst kind of silent divergence), both rules become the SAME loud
50
+ * {@link UnknownModelError} that rules 4/5 already raise: name a deployed
51
+ * model, here is the list. Rules 2–5 are byte-for-byte the local behavior —
52
+ * one router, one resolution order, two configurations.
53
+ */
54
+ /** One org app row from `GET {orgApi}/apps` (urun-cli `ApiClient.list_apps`). */
55
+ interface DeployedApp {
56
+ app_slug: string;
57
+ function_name?: string | null;
58
+ deployment_status?: string | null;
59
+ [k: string]: unknown;
60
+ }
61
+ /**
62
+ * A session handle names a pooled session that no longer exists (closed,
63
+ * evicted after its pod died, replaced by a re-home, or the proxy restarted).
64
+ * The caller asked to REATTACH that exact session — opening a fresh one and
65
+ * calling it "resumed" would be a silent lie, so this is always loud.
66
+ */
67
+ declare class SessionGoneError extends Error {
68
+ }
69
+ /** OpenAI-shaped model list (same shape as models.ts listModels). */
70
+ interface RouterModelList {
71
+ object: 'list';
72
+ data: Array<{
73
+ id: string;
74
+ object: 'model';
75
+ created: number;
76
+ owned_by: string;
77
+ }>;
78
+ }
79
+ interface ModelRouterOptions<S> {
80
+ /**
81
+ * The startup app slug (URUN_APP) — the DEFAULT model — or `null` for the
82
+ * hosted multi-tenant lane, which has no per-proxy default app: there,
83
+ * every request must NAME a deployed model and an unnamed/unknown one
84
+ * fails loud instead of silently landing somewhere (see the module header,
85
+ * "NO-DEFAULT MODE").
86
+ */
87
+ defaultApp: string | null;
88
+ /** The serve function name every routed app must expose (URUN_FUNCTION). */
89
+ fnName: string;
90
+ /** Open a backhaul session for an app slug (called at most once per app). */
91
+ openSession: (appSlug: string) => S | Promise<S>;
92
+ /** Terminal release for one pool entry (Session.end() underneath). */
93
+ closeSession: (entry: S) => Promise<void>;
94
+ /**
95
+ * List the org's deployed apps, or null when the credentials cannot
96
+ * (URUN_JWT lane: the pre-vended token is scoped to the default app, so
97
+ * there is no org listing AND no cross-app session — routing degrades to
98
+ * the default-app-only contract, which is exactly today's behavior).
99
+ */
100
+ listApps: (() => Promise<DeployedApp[]>) | null;
101
+ /**
102
+ * Catalog rows (models.ts fetchCatalogRows) as the uRun-namespace oracle
103
+ * for rule 5, or null when catalog access is not configured.
104
+ */
105
+ listCatalog: (() => Promise<Array<{
106
+ model_id: string;
107
+ variant: string;
108
+ }>>) | null;
109
+ /** Deployed-apps cache TTL (the list changes on deploys, not per request). */
110
+ appsTtlMs?: number;
111
+ /**
112
+ * The stable NATIVE identity of one pooled entry (the uRun session id in
113
+ * the proxy wiring — the same identity the serve-side session-affinity tag
114
+ * rides, urun-python#1556). Powers the session-identity seam
115
+ * ({@link ModelRouter.handleFor} / {@link ModelRouter.sessionForHandle});
116
+ * a router without it fails LOUD on those calls, never approximates.
117
+ */
118
+ sessionKey?: (entry: S) => string;
119
+ }
120
+ /**
121
+ * The session pool: one backhaul session per deployed app, keyed by app slug,
122
+ * opened lazily on the first request that routes to it and reused for every
123
+ * subsequent one. The startup app is seeded eagerly by the CLI. Sessions
124
+ * close on proxy shutdown via {@link closeAll}; there is NO idle-close policy
125
+ * (deliberate v1 simplification — noted as a follow-up in the PR).
126
+ */
127
+ declare class ModelRouter<S> {
128
+ private readonly opts;
129
+ private readonly pool;
130
+ private appsCache;
131
+ constructor(opts: ModelRouterOptions<S>);
132
+ /** Seed an already-open session (the CLI's eagerly-opened startup app). */
133
+ seed(appSlug: string, entry: S): void;
134
+ private deployedApps;
135
+ /** Apps this proxy may serve: active AND exposing the serve function. */
136
+ private servable;
137
+ private availableIds;
138
+ /**
139
+ * NO-DEFAULT MODE's terminal for rules 1 and 6: there is no app to fall
140
+ * back to, so say so loudly and list what the CALLER'S org actually has.
141
+ * Never returns.
142
+ */
143
+ private noDefaultApp;
144
+ /**
145
+ * Resolve a request's `model` to an app slug — the documented resolution
146
+ * order from the module header. Throws {@link UnknownModelError} for a uRun
147
+ * model that is not deployed (rules 4/5).
148
+ */
149
+ resolveApp(model: string | undefined): Promise<string>;
150
+ /** The pooled session for a model — opened lazily, reused afterwards. */
151
+ sessionFor(model: string | undefined): Promise<{
152
+ app: string;
153
+ entry: S;
154
+ }>;
155
+ private keyOf;
156
+ /**
157
+ * SESSION-IDENTITY SEAM (a): the opaque stable handle for the pooled
158
+ * session currently serving `model`'s turns. Rides the SAME acquisition
159
+ * path as every request ({@link sessionFor}) — the session opens lazily if
160
+ * this model has none yet — and derives the handle from native identity
161
+ * (app slug + uRun session id), zero bespoke bookkeeping.
162
+ */
163
+ handleFor(model: string | undefined): Promise<{
164
+ app: string;
165
+ handle: string;
166
+ }>;
167
+ /**
168
+ * SESSION-IDENTITY SEAM (b): the exact pooled session a handle names.
169
+ * NEVER opens a fresh session — a handle whose session is gone (closed,
170
+ * evicted, re-homed to a replacement, proxy restarted) or malformed throws
171
+ * {@link SessionGoneError} loudly. Resume is reattach-or-fail, not
172
+ * reattach-or-quietly-restart.
173
+ */
174
+ sessionForHandle(handle: string): Promise<{
175
+ app: string;
176
+ entry: S;
177
+ }>;
178
+ /**
179
+ * Drop ONE pooled session whose backhaul died (its pod was restarted /
180
+ * drained / deleted) and release it — the next {@link sessionFor} opens a
181
+ * fresh one, i.e. asks the control plane for a new assignment. Used by the
182
+ * one-shot re-home (rehome.ts, urun-sh/urun-python#1592).
183
+ *
184
+ * IDENTITY-GUARDED (the same rule the pi lane's SessionPool follows): a
185
+ * concurrent request that already re-homed this app has put a NEWER entry
186
+ * under the key, and evicting that would close a healthy session out from
187
+ * under it.
188
+ */
189
+ evict(app: string, entry: S): Promise<void>;
190
+ /**
191
+ * `GET /v1/models`: the org's deployed serve apps as model entries, the
192
+ * default app FIRST. On the JWT lane (no org listing) this is the default
193
+ * app plus any app already in the pool — the gap is called out loudly in
194
+ * the PR, not papered over here.
195
+ */
196
+ modelList(): Promise<RouterModelList>;
197
+ /** Close every pooled session (Session.end() underneath) — proxy shutdown. */
198
+ closeAll(): Promise<void>;
199
+ }
200
+
201
+ /**
202
+ * HOSTED AUTH — the Bearer key IS the identity AND the tenancy.
203
+ *
204
+ * Owner directive (2026-08-19): "the Bearer key alone determines the org —
205
+ * resolve org_id from the key server-side and scope ALL routing to THAT org.
206
+ * No org id in the URL or headers." So this module has exactly one job:
207
+ * `Authorization: Bearer <urun org api key>` → `org_id`, or a loud 401.
208
+ *
209
+ * IT INVENTS NO AUTH SCHEME. It calls the platform's OWN key verification —
210
+ * `createClientToken(apiKey)` from `@urun-sh/core`, i.e.
211
+ * `POST {gateway}/api/client-tokens` with the key on `Authorization` — which
212
+ * is the same call `urun compat` already makes to open a session, and which
213
+ * returns `org_id`: the key's org binding, straight from the control plane.
214
+ * There is no JWT path, no org header, no locally-held mapping table.
215
+ *
216
+ * NO STANDING CREDENTIAL. This process holds no platform secret at all: every
217
+ * upstream call it makes rides the CALLER'S OWN key. That is what makes an
218
+ * internet-facing shared endpoint safe to run, and it is what makes cross-org
219
+ * isolation structural rather than a rule this code has to remember — a key
220
+ * simply cannot mint a token, list apps, or dial a session outside its org.
221
+ */
222
+
223
+ /**
224
+ * A refused request. `status` is what the caller sees; the message is the
225
+ * OpenAI-envelope `error.message`. Every rejection here is explicit — there
226
+ * is no anonymous lane to fall through to.
227
+ */
228
+ declare class ProxyAuthError extends Error {
229
+ readonly status: number;
230
+ constructor(message: string, status?: number);
231
+ }
232
+ /** The control plane could not be reached/asked. Loud 502, never a pass. */
233
+ declare class ControlPlaneUnavailableError extends Error {
234
+ constructor(message: string);
235
+ }
236
+ /** The verified caller: their key, and the org the control plane bound it to. */
237
+ interface CallerIdentity {
238
+ apiKey: string;
239
+ orgId: string;
240
+ }
241
+ /**
242
+ * Pull the raw key off `Authorization: Bearer <key>`. Missing, malformed, or
243
+ * a non-Bearer scheme is a 401 — never an anonymous request.
244
+ */
245
+ declare function bearerFrom(headerValue: string | undefined): string;
246
+ /**
247
+ * How long a verified key→org binding is reused before re-asking the control
248
+ * plane. Bounded and short: a revoked key stops working within this window,
249
+ * and until then it can still only reach its OWN org (the binding is the org
250
+ * it always had), so the window can never widen a tenancy boundary.
251
+ */
252
+ declare const ORG_BINDING_TTL_MS = 60000;
253
+ /** The most distinct keys one replica remembers verifying (bounded memory). */
254
+ declare const MAX_CACHED_KEYS = 4096;
255
+ interface OrgResolverOptions {
256
+ /** The session-gateway base the mint call goes to (external input). */
257
+ gatewayUrl: string;
258
+ /** Injected in tests; production uses the SDK's own fetch. */
259
+ mint?: typeof createClientToken;
260
+ /** Injected in tests so cache expiry is exercised without wall-clock waits. */
261
+ now?: () => number;
262
+ }
263
+ /**
264
+ * Key → org resolution with a short bounded cache.
265
+ *
266
+ * The cache is keyed on the RAW key held only in memory for the TTL. It is
267
+ * deliberately not a "session registry": it holds no session, no doc, no
268
+ * routing state — nothing that would have to be shared between replicas, and
269
+ * nothing whose loss changes behavior (a cold replica just re-verifies).
270
+ */
271
+ declare class OrgResolver {
272
+ private readonly opts;
273
+ private readonly cache;
274
+ private readonly mint;
275
+ private readonly now;
276
+ constructor(opts: OrgResolverOptions);
277
+ /**
278
+ * Verify a Bearer key against the control plane and return its org binding.
279
+ *
280
+ * Three outcomes, all explicit:
281
+ * - the control plane binds the key to an org → {@link CallerIdentity};
282
+ * - the control plane rejects the key (401/403) → {@link ProxyAuthError};
283
+ * - the control plane cannot be asked → loud
284
+ * {@link ControlPlaneUnavailableError} (502). NEVER a pass: a proxy that
285
+ * admitted requests while it could not verify keys would be an open
286
+ * relay into every org.
287
+ */
288
+ resolve(apiKey: string): Promise<CallerIdentity>;
289
+ /** Test/ops seam: forget every cached binding. */
290
+ clear(): void;
291
+ }
292
+
293
+ /**
294
+ * SESSION GONE for the proxy serve lane — level-triggered, keyed off the
295
+ * transport's OWN close/rejection signals (live defect, prod-usw2 2026-08-12).
296
+ *
297
+ * A pooled backhaul session can be closed by the PLATFORM out from under the
298
+ * proxy (close_reason=idle_timeout, or a serve-side crash close). The request
299
+ * marker is a write into the session's `llm` Yjs doc — and a Yjs provider
300
+ * whose room is gone buffers the write locally and redials FOREVER (that is
301
+ * its contract: "Still retrying — the doc provider never gives up"), so every
302
+ * request dispatched into a dead session was silently swallowed: zero events,
303
+ * no error, no re-home (live receipt: session
304
+ * ef1bf00c-24d7-4327-b057-452a72221e43 idle-closed 18:50:55Z; a /v1/messages
305
+ * request accepted 18:51:18Z produced nothing for 147s until the client gave
306
+ * up).
307
+ *
308
+ * This module derives ONE latched "session gone" verdict from the two native
309
+ * lifecycle surfaces core already exposes — NO liveness timers, NO
310
+ * heartbeats, no polling (the pi lane's stall watchdog stays a pi-lane
311
+ * concern):
312
+ *
313
+ * 1. The doc provider's own connection-state surface
314
+ * (yjs-provider `DocConnectionState`, via `SessionDocument.
315
+ * onConnectionState` on the SAME `llm` doc the transport writes request
316
+ * markers into): `consecutiveFailures` at/above the provider's own
317
+ * outage bar (`DOC_OUTAGE_LOUD_THRESHOLD` = 3 — the exact state the live
318
+ * log line reports, close code 1002 / an upgrade rejection for a room
319
+ * that no longer exists) means the session's backhaul is unreachable.
320
+ * `consecutiveFailures` resets on every healthy sync, so a recovered
321
+ * blip never trips this.
322
+ *
323
+ * 2. Core Session's own terminal phase machinery (`Session.onPhase` →
324
+ * 'ended' / 'expired' / 'error') — the session lease the pool already
325
+ * tracks (the same signal cli.ts `onSessionEnd` rides).
326
+ *
327
+ * Consumers (cli.ts):
328
+ * - {@link watchSessionGone} per pooled entry; `onGone` → `router.evict`
329
+ * (level-triggered eviction, so the NEXT acquire re-allocates through the
330
+ * existing `sessionFor` path);
331
+ * - {@link guardSessionGone} around each dispatched event stream, so an
332
+ * in-flight (or newly dispatched) request THROWS {@link SessionGoneError}
333
+ * promptly instead of waiting on a doc room that will never answer. The
334
+ * throw surfaces BEFORE any content event, which is exactly what the
335
+ * existing one-shot re-home (rehome.ts) turns into a fresh-session
336
+ * re-dial — no parallel retry mechanism.
337
+ */
338
+
339
+ /** A latched per-entry session-gone verdict. */
340
+ interface SessionGoneWatch {
341
+ /** The latched verdict — null while the session is (believed) alive. */
342
+ gone(): SessionGoneError | null;
343
+ /**
344
+ * Subscribe to the gone transition. Level-triggered: an already-gone watch
345
+ * invokes the callback immediately. Fires at most once per subscriber.
346
+ * Returns an unsubscribe fn.
347
+ */
348
+ onGone(cb: (err: SessionGoneError) => void): () => void;
349
+ /** Detach from the session's surfaces (entry teardown / proxy shutdown). */
350
+ dispose(): void;
351
+ }
352
+
353
+ /**
354
+ * THE canonical backhaul factory — the one place a `ProxyClients` seam is
355
+ * built over uRun sessions.
356
+ *
357
+ * This code used to live inside `cli.ts`, which executes `main()` on import
358
+ * and therefore cannot be imported by anything (tests included). Hosting the
359
+ * SAME proxy as a server (`src/hosted/`) needs exactly this wiring, so it was
360
+ * lifted here verbatim rather than forked: `cli.ts` (local, single-tenant) and
361
+ * `hosted/` (shared, multi-tenant) now call the identical
362
+ * {@link buildRouter} / {@link buildClients} pair, and the only difference
363
+ * between them is the {@link BackhaulConfig} they hand it.
364
+ *
365
+ * The two configurations:
366
+ * - LOCAL `defaultApp: <URUN_APP>` — the startup app is the default model
367
+ * and is seeded eagerly, exactly as `urun compat` always behaved.
368
+ * - HOSTED `defaultApp: null` — no default model, nothing seeded;
369
+ * every request must name a deployed model (routing.ts,
370
+ * "NO-DEFAULT MODE") and `auth.apiKey` is the CALLER'S OWN org
371
+ * API key, so every control-plane call and every session dial is
372
+ * org-scoped by the platform itself.
373
+ */
374
+
375
+ /**
376
+ * The `model_catalog` oracle (models.ts) behind routing rule 5 — telling an
377
+ * UNDEPLOYED uRun catalog model apart from a name outside the uRun namespace.
378
+ * `null` disables rule 5; every caller says so out loud when it does.
379
+ */
380
+ interface CatalogConfig {
381
+ catalogUrl: string;
382
+ anonKey: string;
383
+ }
384
+ /**
385
+ * The session plus the SDK's OWN terminal release (`Session.end()`), and the
386
+ * core Session surfaces the identity seam rides — all NATIVE @urun-sh/core
387
+ * Session members (`id`, `endsAt`, `onPhase`), typed optional here because
388
+ * the narrow UrunSessionLike does not declare them; the seam fails LOUD when
389
+ * a session object lacks them (no approximation, no minted identity).
390
+ */
391
+ type OwnedSession = UrunSessionLike & {
392
+ end: () => Promise<unknown>;
393
+ id?: string;
394
+ endsAt?: Date | null;
395
+ onPhase?: (handler: (phase: {
396
+ name: string;
397
+ }) => void) => () => void;
398
+ };
399
+ /**
400
+ * One pooled backhaul: the session, its (stateful) Responses client, and the
401
+ * latched session-gone watch over the session's own transport signals.
402
+ */
403
+ type PoolEntry = {
404
+ session: OwnedSession;
405
+ responses: UrunResponses;
406
+ gone: SessionGoneWatch;
407
+ };
408
+
409
+ /**
410
+ * PER-CALLER BACKHAULS — the multi-tenant half of the hosted endpoint.
411
+ *
412
+ * One {@link ModelRouter} + {@link ProxyClients} per verified API key, built
413
+ * by the SAME `buildRouter`/`buildClients` factory the local `urun compat`
414
+ * proxy uses (proxy/backhaul.ts). Nothing about routing, pooling, re-homing
415
+ * or session-gone handling is reimplemented here; this module only decides
416
+ * WHICH backhaul a request gets.
417
+ *
418
+ * WHY KEYED ON THE API KEY, NOT THE ORG: two keys in one org must not share a
419
+ * backhaul, because every session dial and every usage event is attributed to
420
+ * the key that minted its token (`usage_events.api_key_id`). Sharing would
421
+ * silently bill one key's traffic to another. Same-org keys therefore get
422
+ * separate pools — a small duplication that keeps attribution honest.
423
+ *
424
+ * CROSS-ORG ISOLATION IS STRUCTURAL, NOT BOOKKEEPING. A tenant's router is
425
+ * built with the caller's OWN key as its only credential, so:
426
+ * - `/v1/models` enumerates `GET {apiUrl}/apps` WITH THAT KEY — the control
427
+ * plane returns that org's apps and no others;
428
+ * - a session dial mints a client token scoped to `<app>/serve` WITH THAT
429
+ * KEY — the control plane refuses to scope it outside the key's org.
430
+ * There is no place in this process where one org's app list or session could
431
+ * be handed to another org's key, because this process never holds a
432
+ * credential that spans orgs.
433
+ *
434
+ * HORIZONTAL SCALE (phase 1). There is deliberately NO shared state plane —
435
+ * no valkey, no shard map, no cross-replica session registry. Two facts make
436
+ * one unnecessary:
437
+ * 1. Nothing here is authoritative. A replica's tenant map is a CACHE of
438
+ * backhauls it happens to have open; a cold replica rebuilds it from the
439
+ * caller's key on the first request. Losing it changes no answer.
440
+ * 2. Replicas converge on the platform's OWN dedupe rather than on a
441
+ * registry of ours: client tokens are minted with a STABLE `subject`
442
+ * derived from the key, and the platform dedupes sessions per
443
+ * (org, actor, app, function) — so replica A and replica B dialing the
444
+ * same (key, app) coalesce onto ONE platform session. That is the
445
+ * library's own mechanism doing the sharding.
446
+ * The yjs/valkey state plane in urun-infra#1490 §7 is therefore NOT built
447
+ * here, and is not silently missing either — it is unnecessary for this
448
+ * phase's correctness and is called out as such in the PR.
449
+ */
450
+
451
+ /**
452
+ * The most distinct callers one replica keeps backhauls open for. On overflow
453
+ * the least-recently-used tenant is closed (`ModelRouter.closeAll()` — the
454
+ * SDK's own terminal release).
455
+ *
456
+ * HONEST TRADEOFF, not a costless one: LRU position is refreshed when a
457
+ * request STARTS, so a tenant part-way through a long generation can become
458
+ * least-recently-used and be evicted under its own in-flight stream. That
459
+ * failure is LOUD, not silent — the existing session-gone machinery turns it
460
+ * into a terminal error event in the lane's native SSE shape, and the next
461
+ * request re-dials. Making it impossible needs in-flight refcounting, i.e.
462
+ * exactly the bespoke bookkeeping this design avoids, so phase 1 sets the cap
463
+ * high enough that reaching it is itself the signal to revisit.
464
+ *
465
+ * There is intentionally no idle TIMER: an idle tenant's pooled sessions are
466
+ * reaped by the PLATFORM'S own idle-close, and the level-triggered
467
+ * session-gone watch (proxy/session-gone.ts) evicts each entry from the pool
468
+ * the moment that happens. So an idle tenant decays to an empty router by
469
+ * itself, through the platform's mechanism, with no reclaim timers of ours.
470
+ */
471
+ declare const MAX_TENANTS = 512;
472
+ interface TenantRegistryOptions {
473
+ /** The session-gateway base sessions are opened against. */
474
+ baseUrl: string;
475
+ /** The org control-plane API base (`GET {apiUrl}/apps`). */
476
+ apiUrl: string;
477
+ /** The model-catalog oracle for routing rule 5, or null. */
478
+ catalog: CatalogConfig | null;
479
+ /** Injected in tests; production builds real uRun backhauls. */
480
+ build?: (caller: CallerIdentity) => {
481
+ router: ModelRouter<PoolEntry>;
482
+ clients: ProxyClients;
483
+ };
484
+ }
485
+ /**
486
+ * A stable, non-secret `subject` for one caller's client tokens. Derived from
487
+ * the key so it is identical on every replica (that is the whole point — see
488
+ * the module header's HORIZONTAL SCALE note), and hashed so the raw key never
489
+ * travels inside a token claim or a log line.
490
+ */
491
+ declare function tenantSubject(apiKey: string): string;
492
+ declare class TenantRegistry {
493
+ private readonly opts;
494
+ /** Insertion-ordered = LRU order, because a hit re-inserts at the end. */
495
+ private readonly tenants;
496
+ constructor(opts: TenantRegistryOptions);
497
+ private build;
498
+ /** The backhaul for THIS caller, opened on first use and reused after. */
499
+ clientsFor(caller: CallerIdentity): ProxyClients;
500
+ /** Live tenant count — the readiness/ops view, and the tests' assertion. */
501
+ get size(): number;
502
+ /** Shutdown: end every pooled session through `Session.end()`. */
503
+ closeAll(): Promise<void>;
504
+ }
505
+
506
+ /**
507
+ * The hosted endpoint's configuration.
508
+ *
509
+ * CONFIG MANDATE (org-wide): env vars are NOT a config mechanism. The ONLY
510
+ * things read from the environment here are genuine EXTERNAL INPUTS — which
511
+ * control plane this deployment points at, and the runtime-injected `PORT`.
512
+ * Everything behavioral is a module constant in this file, visible in one
513
+ * place and changed by editing code, never by a knob on a running pod.
514
+ *
515
+ * There is deliberately NO credential input. The service holds no standing
516
+ * platform secret: every upstream call rides the CALLER'S own org API key
517
+ * (see hosted/auth.ts), which is what lets an internet-facing shared endpoint
518
+ * exist without mounting an org-wide credential onto it.
519
+ */
520
+
521
+ /**
522
+ * The container's listen port. The interface contract with the deployment
523
+ * chart is 8080; `PORT` is the runtime-injected override (the one env var
524
+ * class the mandate keeps), never a behavior knob.
525
+ */
526
+ declare const DEFAULT_PORT = 8080;
527
+ /**
528
+ * The serve function every routed app must expose. HARDCODED, not an env var:
529
+ * "which function name is a serve lane" is a platform contract, not a
530
+ * per-deployment tuning knob, and a pod that could be pointed at a different
531
+ * function would silently serve a different shape of app.
532
+ */
533
+ declare const SERVE_FUNCTION = "serve";
534
+ /**
535
+ * The address the server binds. A hosted pod must accept traffic from the
536
+ * Service, so this is all interfaces — the opposite of the local CLI proxy's
537
+ * loopback-only bind, and stated explicitly rather than defaulted into.
538
+ */
539
+ declare const BIND_HOST = "0.0.0.0";
540
+ /** Validated external inputs for one hosted deployment. */
541
+ interface HostedConfig {
542
+ /** Listen port (runtime-injected `PORT`, default {@link DEFAULT_PORT}). */
543
+ port: number;
544
+ /**
545
+ * The SESSION-GATEWAY base (`https://api.urun.sh`) — sessions are opened
546
+ * against it AND the client-token mint (`POST {}/api/client-tokens`), which
547
+ * is also the key-verification call, lives on it.
548
+ */
549
+ baseUrl: string;
550
+ /** The org control-plane API base (`GET {}/apps`), default `{baseUrl}/v1`. */
551
+ apiUrl: string;
552
+ /** The `model_catalog` oracle for routing rule 5, or null when unconfigured. */
553
+ catalog: CatalogConfig | null;
554
+ }
555
+ /**
556
+ * Read and VALIDATE the deployment's external inputs. Every failure is loud
557
+ * at startup: a pod that cannot name its control plane must not come up
558
+ * half-configured and start answering requests it cannot serve.
559
+ */
560
+ declare function resolveHostedConfig(env?: NodeJS.ProcessEnv): HostedConfig;
561
+
562
+ /**
563
+ * THE HOSTED SHARED ENDPOINT — `inference-proxy`.
564
+ *
565
+ * Owner directive (2026-08-19): "stand up a managed endpoint, authenticated
566
+ * via urun api key, that scales HA, hosts the proxy so that we can just point
567
+ * at it with api key from the org its deployed in and use just like
568
+ * openai/gemini." Plus the binding addition: the Bearer key ALONE determines
569
+ * the org; there is no org id in the URL or in a header.
570
+ *
571
+ * This file is the server SHELL only. It contains no OpenAI/Anthropic
572
+ * protocol code of its own: every `/v1` request is handed to
573
+ * `createProxyRequestHandler` (proxy/server.ts) — the very same handler the
574
+ * local `urun compat proxy` mounts — with a per-request `ProxyClients`
575
+ * resolved from the caller's key. One implementation, two deployments.
576
+ *
577
+ * ROUTES (the contract the deployment chart is built against):
578
+ * GET /healthz liveness — process is up. No upstream calls, ever.
579
+ * GET /readyz readiness — ALSO requires the control plane to be
580
+ * reachable, because a replica that cannot verify API keys
581
+ * or list apps can serve nothing and must be pulled out of
582
+ * the Service rather than answering 502s.
583
+ * * /v1/... the compat surface, org-scoped by the Bearer key.
584
+ * * anything else → 404 in the OpenAI error envelope.
585
+ *
586
+ * WHAT IS DELIBERATELY NOT HERE: `/stats`. The local proxy exposes it for the
587
+ * launcher's reuse probe; on a shared endpoint it would publish one tenant's
588
+ * per-model traffic to every other caller, so the hosted router simply never
589
+ * dispatches it.
590
+ */
591
+
592
+ /**
593
+ * How long a readiness verdict is reused. Kubernetes probes every few
594
+ * seconds; without this the replica would hammer the control plane purely to
595
+ * answer its own probes. Short enough that a control-plane outage pulls the
596
+ * pod from the Service within one probe period or two.
597
+ */
598
+ declare const READINESS_TTL_MS = 5000;
599
+ /** Readiness probes must not hang: an unanswered probe IS a failed probe. */
600
+ declare const READINESS_TIMEOUT_MS = 3000;
601
+ interface HostedProxyOptions extends HostedConfig {
602
+ /** Injected in tests; production uses the real control-plane probe. */
603
+ probeControlPlane?: () => Promise<{
604
+ ready: boolean;
605
+ detail: string;
606
+ }>;
607
+ /** Injected in tests so key verification is exercised without a cluster. */
608
+ resolver?: OrgResolver;
609
+ /** Injected in tests so backhauls are stubbed at the seam. */
610
+ registry?: TenantRegistry;
611
+ }
612
+ /**
613
+ * Build (not listen) the hosted endpoint. The caller owns listen/close, and
614
+ * `closeAll` releases every pooled backhaul through `Session.end()`.
615
+ */
616
+ declare function createHostedProxy(options: HostedProxyOptions): {
617
+ server: Server;
618
+ closeAll: () => Promise<void>;
619
+ };
620
+
621
+ export { BIND_HOST, type CallerIdentity, ControlPlaneUnavailableError, DEFAULT_PORT, type HostedConfig, type HostedProxyOptions, MAX_CACHED_KEYS, MAX_TENANTS, ORG_BINDING_TTL_MS, OrgResolver, type OrgResolverOptions, ProxyAuthError, READINESS_TIMEOUT_MS, READINESS_TTL_MS, SERVE_FUNCTION, TenantRegistry, type TenantRegistryOptions, bearerFrom, createHostedProxy, resolveHostedConfig, tenantSubject };