/** * The proxy that stands in the path, and does as little as possible there. * * The decision lives in `@trazum/core`'s `gatewayDecision`, which never sees a * prompt and cannot return a modified request. This file moves bytes: read a * body, ask, and either forward it **unchanged** or answer with the refusal. * The split is the safety property — everything that could go wrong in a * judgement is tested without a socket, and everything that could go wrong on * a socket has no judgement in it. * * **Loopback only, and the address is not a flag.** Same posture as `serve` * since 1.44, and more load-bearing here: this thing has somebody's provider * credential passing through it. `127.0.0.1` is compiled in. * * **The credential is not even borrowed.** The caller's own `authorization` * and `x-api-key` headers are forwarded untouched and never read, never * stored, never logged, and never put in a URL. Trazum holds no key for the * gateway and has no way to make a call of its own through it — which is a * stronger promise than the connector's *borrowed, never held*, and the right * one for a component sitting between somebody and their provider. * * **The upstream is compiled in.** A flag naming the host would turn this into * a credential-forwarding open proxy: anything that could rewrite a config on * disk could point a company's API key at a machine it chose. `checkedEndpoint` * has guarded Trazum's outbound calls on that principle since 1.14, and here * there is no caller-supplied endpoint at all. * * **Nothing about the payload is written down.** The body is read to count * tokens and to find the model, then forwarded and dropped. It is never * logged, never stored, and never included in a refusal — the store has held * aggregates since 1.42 and standing in the path changes nothing about that. */ import type { Server } from 'node:http'; import type { GatewayPolicy, GatewayStanding, LimitsConfig, MeasuredPosition, PricingCatalogue, WaiveEntry } from '@trazum/core'; /** Compiled in. See the module note. */ export declare const BIND_HOST = "127.0.0.1"; export declare const DEFAULT_GATEWAY_PORT = 7318; /** * Bodies larger than this are refused unread. * * Larger than `serve`'s limit because a real request carries a real prompt, * and smaller than unbounded because a proxy that buffers whatever it is * handed is a memory exhaustion away from taking down the application it was * installed to protect. */ export declare const MAX_GATEWAY_BODY_BYTES: number; /** * Where each provider actually is, and the one path this speaks for it. * * Deliberately narrow. A gateway that forwarded any path would be a general * proxy for somebody's API key, and the budget decision only has meaning for * the endpoint that spends tokens. */ export interface Upstream { origin: string; /** * The one path, or the one *shape* of path when the model is part of it. * * A pattern is not a widening. It exists because Google puts the model in * the URL rather than the body, so "one path" cannot be written down as a * literal for that provider — and every pattern here is anchored at both * ends with the model segment restricted to characters a model id is made * of, which is a narrower grammar than a literal comparison against a string * somebody could have put a `?` or a `..` in. */ path: string | RegExp; /** * Where the model name is, when the request body does not carry one. * * Gemini's body has `contents` and no `model` field. Reading it out of the * validated path is the only honest source: the alternative is a gateway * that forwards a call it could not price, which is the one thing standing * in the path was for. */ modelIn?: 'path'; /** * Paths that spend no tokens, forwarded without a budget decision. * * **Refusing these would be the wrong answer, not a stricter one.** * `count_tokens` is the call you make to find out whether you can afford the * other one; answering it with a 402 blinds a caller at exactly the moment * they are trying to behave. And a budget refusal only means something when * there is money on the line: a call that spends nothing has nothing to * judge, so judging it would be theatre with a real cost. * * **What this does and does not widen.** The origin is still compiled in, so * the credential can still only ever reach one host. What grows is the set * of *operations* somebody who can reach the loopback port may perform with * it, and that is why the list is literal strings only, enumerated here, and * why each entry has to be written into `docs/gateway.md` before a guard * will let it exist. A pattern here would be a widening with no budget check * behind it, which is the general-proxy shape this gateway is built to * refuse. * * Nothing that spends belongs here. `/v1/messages/batches` is the near miss: * it looks administrative and it bills. */ free?: readonly FreePath[]; } /** One path that costs nothing, and the method it answers on. */ export interface FreePath { method: 'GET' | 'POST'; path: string; } export declare const UPSTREAMS: Readonly>; /** * The path this gateway forwards for a provider, as a person reads it. * * One phrasing, used by the refusal, the documentation guard and the security * allowlist — because three renderings of the same fact is how the page, the * refusal and the test come to disagree about what is actually forwarded. */ export declare function forwards(upstream: Upstream): string; /** * The paths this gateway forwards without judging, as a person reads them. * * Same reason `forwards` exists: the refusal body, the documentation guard and * the page itself have to render this from one place, or they drift into three * different accounts of what is actually reachable. */ export declare function alsoForwards(upstream: Upstream): readonly string[]; /** * Whether this request is the one call this gateway speaks for, and the model * the path named if it named one. * * The returned path is **built here**, never the caller's string echoed back. * A pattern that matched is evidence the request was well formed; it is not a * licence to forward whatever matched it. Nothing reaches the upstream URL * except the compiled-in origin and a path assembled from a model id that has * already been restricted to `[A-Za-z0-9._-]`. */ export declare function route(upstream: Upstream, method: string | undefined, url: string | undefined): { path: string; model: string | null; spends: boolean; } | null; /** * Headers Trazum adds or removes. Everything else the caller sent is forwarded * verbatim, including their credential, which this never reads. */ /** Why a forwarded call's cost could not be measured. */ export type UnmeasuredCause = 'stream-broke' | 'no-usage-event' | 'no-usage-in-body'; export interface GatewayContext { provider: string; catalogue: PricingCatalogue; policy: GatewayPolicy; /** Where the budget stands, refreshed by the caller — never read per request. */ standing: () => GatewayStanding | null; /** The `limits` block, when the config carries one. */ limits?: LimitsConfig; /** * The measured position for one call's scopes — from an index built once * at start, like `standing`. Never a file read in the request path. */ position?: (call: { label?: string; session?: string; }) => MeasuredPosition; /** The config's `waive` list — a silenced limit forwards, on the record. */ waivers?: readonly WaiveEntry[]; /** * Called after a forwarded call returns, with the provider's own counts. * * Counts only. There is no parameter here that could carry a prompt, which * is what makes "nothing about the payload is written down" a fact about the * interface rather than a discipline. */ record: (measured: { model: string; label: string | null; inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; substituted: boolean; }) => void; /** * A call that was forwarded and whose cost could not be measured. * * The money is spent either way — the provider generated what it generated — * and the period's total will be lower than the bill by however much these * came to. Naming them is the only honest option: a zero would be a * measurement, and inventing an estimate would merge the two halves this * product spent an arc separating. * * Two causes, and the second is not a failure at all: * * - `stream-broke` — the connection died before the event carrying the * counts. Rare, and a real error. * - `no-usage-event` — the stream simply carried no counts. On OpenAI that is * **every streaming call** unless the caller passed `stream_options: * {include_usage: true}`, so this is the common case rather than the * exception, and an operator who is not told will read a total that is * quietly missing most of their traffic. */ unmeasured?: (cause: UnmeasuredCause) => void; /** A line for the operator's terminal. Never given a body, ever. */ note: (line: string) => void; /** Injected so the proxy is testable against a stub upstream. */ fetchImpl?: typeof fetch; } export declare function buildGateway(context: GatewayContext): Server; export declare function listenGateway(server: Server, where: { port: number; } | { socket: string; }): Promise; //# sourceMappingURL=gateway-server.d.ts.map