@urun-sh/openai 0.2.60 → 0.3.0

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