/** * The way the Wayfinder router selected a model for a request. * * The Rust fork emits `scored`, `pinned`, `threshold-override`, and `sticky`. * The type stays open to tolerate values from other Wayfinder builds (for * example upstream Python's `slash-pinned`). */ export type RouterMode = | "scored" | "pinned" | "threshold-override" | "sticky" | (string & {}); /** * Normalized HTTP headers as delivered by Pi's `after_provider_response` * event. Keys may be any case and values may be a string or an array of * strings. */ export type HeaderMap = Record; /** * A single Wayfinder routing decision parsed from the `x-wayfinder-router-*` * response headers. */ export interface Decision { /** * How the model was chosen (`x-wayfinder-router-mode`). */ mode: RouterMode; /** * The model the router chose (`x-wayfinder-router-model`). */ chose: string; /** * The model that actually served the request * (`x-wayfinder-router-served-by`); falls back to `chose` when the header is * absent. */ servedBy: string; /** * The complexity score (`x-wayfinder-router-score`), or `undefined` when the * header is absent or not a finite number. */ score: number | undefined; /** * The request id (`x-wayfinder-router-request-id`) for correlating with * `GET /router/recent`, or `undefined` when absent. */ requestId: string | undefined; /** * Whether the gateway served offline (`x-wayfinder-router-offline`). */ offline: boolean; /** * Whether the request failed over to a different model * (`x-wayfinder-router-failover`). */ failover: boolean; /** * The cache outcome (`x-wayfinder-router-cache`), or `undefined` when absent. */ cache: "hit" | "miss" | undefined; /** * Whether the gateway ran in decision-only mode with no backend configured * (`x-wayfinder-router-decision-only`). */ decisionOnly: boolean; } const PREFIX = "x-wayfinder-router-"; /** * Read a single header value case-insensitively, returning the first element * when the value is an array and `undefined` when the header is absent. */ function get(headers: HeaderMap, name: string): string | undefined { for (const key of Object.keys(headers)) { if (key.toLowerCase() === name) { const value = headers[key]; return Array.isArray(value) ? value[0] : value; } } return undefined; } /** * Interpret a header's presence as a boolean: true when present and not * explicitly falsey (`"false"` or `"0"`). */ function flag(headers: HeaderMap, name: string): boolean { const value = get(headers, name); if (value === undefined) return false; const normalized = value.trim().toLowerCase(); return normalized !== "" && normalized !== "false" && normalized !== "0"; } /** * Parse the `x-wayfinder-router-*` response headers into a {@link Decision}. * * Returns `null` when `x-wayfinder-router-mode` is absent, which indicates the * response did not come from a Wayfinder-routed provider (or the transport did * not expose the headers). Callers should do nothing on `null` rather than * clobbering a previous decision. * * @param headers - Normalized response headers from `after_provider_response`. * @returns The parsed decision, or `null` when the response is not Wayfinder-routed. */ export function parseDecision(headers: HeaderMap): Decision | null { const mode = get(headers, `${PREFIX}mode`); if (mode === undefined) return null; const chose = get(headers, `${PREFIX}model`) ?? ""; const servedBy = get(headers, `${PREFIX}served-by`) ?? chose; const scoreRaw = get(headers, `${PREFIX}score`); const scoreNum = scoreRaw === undefined || scoreRaw.trim() === "" ? Number.NaN : Number(scoreRaw); const score = Number.isFinite(scoreNum) ? scoreNum : undefined; const cacheRaw = get(headers, `${PREFIX}cache`)?.trim().toLowerCase(); const cache = cacheRaw === "hit" ? "hit" : cacheRaw === "miss" ? "miss" : undefined; return { mode, chose, servedBy, score, requestId: get(headers, `${PREFIX}request-id`), offline: flag(headers, `${PREFIX}offline`), failover: flag(headers, `${PREFIX}failover`), cache, decisionOnly: flag(headers, `${PREFIX}decision-only`), }; }