export declare const CANONICAL_CUSTOM_FETCH = "/**\n * Configurable fetch wrapper for Orval-generated clients.\n *\n * GENERATED by `@xemahq/api-client-generator` \u2014 do not edit. Every client in\n * the fleet ships this file byte-for-byte; a local edit is erased by the next\n * `pnpm refresh` and, until then, makes this one client speak to the platform\n * differently from all of its siblings. Change the template instead:\n * `api-client-generator/src/lib/custom-fetch-template.ts`.\n *\n * Consumers must call `configureClient()` before using any endpoint function.\n * The baseUrl is prepended to the relative paths generated by Orval.\n *\n * By default this transport issues exactly ONE request and adds no delay of its\n * own, so a slow call is a slow server. Retrying is opt-in, and opting in\n * requires supplying an observer \u2014 see `maxRetries` / `onRetry` below.\n */\n\n/** What {@link ClientConfig.onRetry} is told before each re-attempt. */\nexport interface RetryNotice {\n /** 1-based index of the re-attempt about to be made. */\n attempt: number;\n /** The configured budget this re-attempt is spending from. */\n maxRetries: number;\n /** The response status that made the previous attempt retryable. */\n status: number;\n /** Delay before the re-attempt, in ms \u2014 from `Retry-After` when the server sent one. */\n waitMs: number;\n /** Whether {@link waitMs} came from the server's `Retry-After` header. */\n retryAfterHonoured: boolean;\n /** Absolute URL being re-requested. */\n url: string;\n /** HTTP method being re-requested. */\n method: string;\n}\n\nexport interface ClientConfig {\n /**\n * Static base URL (e.g. 'http://localhost:3140') \u2014 no trailing slash.\n * Mutually exclusive with `baseUrlResolver`; exactly one MUST be set.\n */\n baseUrl?: string;\n /**\n * Per-request base-URL resolver. When set, the peer URL is resolved from\n * the service registry on EVERY request (boot-order-safe). Wired by\n * `configureOrvalClientResolved` from `@xemahq/platform-common`.\n */\n baseUrlResolver?: () => string | Promise;\n /** Optional async callback to get an auth token. Auto-sets Authorization header on every request. */\n getAuthToken?: () => Promise;\n /** Optional callback returning headers to inject on every request. Per-call headers take precedence. */\n getHeaders?: () => Record | Promise>;\n /**\n * Optional resolver for the CORRELATION ID of the request being made \u2014 the\n * handle that ties one causal chain together across every service hop.\n *\n * WHY IT IS A CALLBACK AND NOT A VALUE. `ClientConfig` is process-global\n * (`configureClient` is called once at wiring time), and a correlation id is\n * per-request. This is invoked INSIDE the request, so a server can point it\n * at whatever carries its ambient request context and get the CURRENT id\n * rather than the one that happened to be live at boot.\n *\n * WHY THE TRANSPORT DOES NOT MINT ONE. Returning `undefined` sends no header,\n * and the receiving service's `RequestContextMiddleware` mints its own \u2014 a\n * new trace, which is honest. A transport that minted per call would produce\n * a FRESH id on every hop while looking like propagation, which is strictly\n * worse than none: every row would carry a correlation id and no two rows\n * that belong together would share one. That is the exact defect this exists\n * to fix, so the transport must not reproduce it one layer down.\n *\n * A caller-supplied `X-Correlation-Id` header always wins, and so does one\n * from `getHeaders`.\n */\n getCorrelationId?: () =>\n | string\n | undefined\n | Promise;\n /**\n * Optional callback invoked on a 401 before ONE re-attempt. Supplying it is\n * what opts this client into that re-attempt; without it a 401 comes back to\n * the caller as a `ClientError` like any other 4xx.\n */\n onUnauthorized?: () => Promise;\n /**\n * Re-attempts for a retryable status. **Defaults to 0 \u2014 no retry.**\n *\n * Setting it above 0 REQUIRES `onRetry`; `configureClient` throws otherwise.\n * See the block above `customFetch` for why the budget is opt-in and why an\n * observer is mandatory rather than advisory.\n */\n maxRetries?: number;\n /**\n * Called immediately before every re-attempt, with the reason and the delay.\n *\n * Mandatory whenever `maxRetries` is above 0. It is the difference between a\n * retry and a hidden degradation path: without it a caller cannot tell a slow\n * server from this transport sleeping between attempts.\n */\n onRetry?: (notice: RetryNotice) => void;\n}\n\nexport class ClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly url: string,\n public readonly body: unknown,\n ) {\n super(`HTTP ${status} from ${url}`);\n this.name = 'ClientError';\n }\n}\n\nlet clientConfig: ClientConfig | null = null;\n\nexport function configureClient(config: ClientConfig): void {\n // Fail at WIRING time, not on the request that happens to be retried. A\n // client configured to retry without an observer is the exact defect this\n // transport was rebuilt to remove, so it must not be constructible.\n if ((config.maxRetries ?? 0) > 0 && !config.onRetry) {\n throw new Error(\n 'configureClient: maxRetries above 0 requires onRetry. A retry nothing ' +\n 'reports is indistinguishable from a slow server, which is how two ' +\n 'silent re-attempts were read as a 3,312ms call.',\n );\n }\n clientConfig = config;\n}\n\nexport function getClientConfig(): ClientConfig {\n if (!clientConfig) {\n throw new Error(\n 'Client not configured. Call configureClient({ baseUrl }) before using endpoint functions.',\n );\n }\n return clientConfig;\n}\n\n/*\n * RETRYING IS OPT-IN, OBSERVED, AND NARROW. Do not widen any of the three.\n *\n * This transport used to retry up to `maxRetries` (defaulting to THREE) over\n * 429/502/503/504 AND over any thrown network error, backing off from 1000ms,\n * with no log line anywhere. Three properties were wrong:\n *\n * 1. The DEFAULT was on. ~99% of the fleet's client configurations never\n * mention `maxRetries`, so two re-attempts could add ~3s to any call with\n * nothing to distinguish that from a slow peer. Every caller that DID set\n * it set it LOWER \u2014 canopy's control plane to 1, \"so a hard outage\n * surfaces inside the turn's latency budget rather than after three\n * backoffs\"; two llm-registry callers to 1; a test to 0. Nobody ever\n * raised it. A default three separate call sites work around is not a\n * default, and `configureOrvalClientResolved` cannot express the field at\n * all, so most callers could not have opted out if they had wanted to.\n * 2. It retried AMBIGUOUS failures. 502 and 504 mean a gateway did not get a\n * timely answer from upstream \u2014 the upstream may well have APPLIED the\n * request. So did a thrown network error mid-flight. Re-sending a POST in\n * either case is a duplicate write, and no amount of logging makes that\n * safe. Only 429 and 503 state positively that the request was NOT\n * processed, so only those are retried; everything else surfaces at once.\n * 3. Nothing reported it. Now `onRetry` is mandatory whenever the budget is\n * above 0, enforced in `configureClient`.\n *\n * Also note `baseUrlResolver` is awaited ONCE, above the loop: a re-attempt\n * returns to the same resolved instance. In a registry-resolved fleet the cure\n * for an unhealthy peer is re-resolution, which lives above this file \u2014 which\n * is a further reason not to lean on retrying here.\n *\n * Retry policy a caller genuinely wants belongs in `@xemahq/managed-fetch`,\n * which has backoff, a circuit breaker, a token bucket and health reporting,\n * and reports what it did.\n *\n * Enforced fleet-wide by `check-client-transport-envelope`, which compares\n * every client's transport to this template.\n */\n\n/** The only statuses that state the request was NOT processed. See above. */\nconst RETRYABLE_STATUSES = [429, 503];\n\n/**\n * The platform's correlation header, spelled once.\n *\n * Value-identical to what `RequestContextMiddleware` reads in\n * `@xemahq/platform-common`. It is a literal here rather than an import\n * because this file has ZERO imports on purpose: it ships byte-identical into\n * browser-target clients as well as server-target ones, and a dependency on a\n * NestJS-peer package would follow it into every one of them.\n */\nconst CORRELATION_ID_HEADER = 'X-Correlation-Id';\n\n/** Backoff floor, doubling per attempt up to {@link MAX_BACKOFF_MS}. */\nconst BASE_BACKOFF_MS = 1000;\n\n/** Ceiling on a single backoff, however many attempts have elapsed. */\nconst MAX_BACKOFF_MS = 30_000;\n\nasync function buildHeaders(\n config: ClientConfig,\n callerHeaders: RequestInit['headers'],\n): Promise {\n const headers = new Headers(callerHeaders);\n\n // Global headers from config (caller-provided headers take precedence)\n if (config.getHeaders) {\n const globalHeaders = await Promise.resolve(config.getHeaders());\n for (const [key, value] of Object.entries(globalHeaders)) {\n if (!headers.has(key)) {\n headers.set(key, value);\n }\n }\n }\n\n // Correlation id (caller and global headers still take precedence).\n //\n // Without this, every server-to-server hop through a generated client started\n // a NEW trace: the id is read-or-minted per hop by the receiving service's\n // RequestContextMiddleware, and nothing carried it outbound \u2014 so an audit\n // journal could record a whole causal chain and offer no way to join it back\n // together.\n //\n // Absent resolver, or a resolver that answers `undefined`: NO header. The\n // receiver mints and a new trace begins, which is the truthful outcome when\n // there is nothing to continue.\n if (config.getCorrelationId && !headers.has(CORRELATION_ID_HEADER)) {\n const correlationId = await Promise.resolve(config.getCorrelationId());\n if (correlationId) {\n headers.set(CORRELATION_ID_HEADER, correlationId);\n }\n }\n\n // Auth token (caller or global headers take precedence)\n if (config.getAuthToken && !headers.has('Authorization')) {\n const token = await config.getAuthToken();\n headers.set('Authorization', `Bearer ${token}`);\n }\n\n return headers;\n}\n\n/*\n * THE TRANSPORT RETURNS THE BODY UNCHANGED. Do not reintroduce an unwrap here.\n *\n * Xema services wrap every 2xx payload in a { data: T } envelope via the global\n * ResponseEnvelopeInterceptor (platform-common), and the generator emits types\n * that describe THAT ENVELOPE \u2014 every endpoint returns Promise\n * or Promise, never a bare inner T. Consumers read the data\n * property themselves.\n *\n * This template used to peel data, justified by a comment claiming generated\n * types describe the inner T. That stopped being true when the generator moved\n * to envelope-typed returns, and the comment outlived the fact \u2014 so the peel\n * then contradicted every type in every package it seeded. Four clients shipped\n * that way (resource-governance x3, workload-runtime-api): declared\n * *DataEnvelope, returned the already-peeled inner object, so .data read\n * undefined at runtime on every non-paginated endpoint. Paginated calls hid it,\n * because the old peel deliberately preserved an envelope carrying pagination.\n *\n * Four MORE shipped it in repositories the gate could not see \u2014 license-api and\n * license-internal-api in xema-operator, plus their host-web mirrors \u2014 because\n * the gate ran in xema-base only. That is why it now runs in every repository\n * that ships a client, and why this file is generator-owned rather than seeded.\n */\n\nexport const customFetch = async (\n url: string,\n options: RequestInit,\n): Promise => {\n const config = getClientConfig();\n const base = config.baseUrlResolver\n ? await config.baseUrlResolver()\n : config.baseUrl;\n if (base === undefined) {\n throw new Error(\n 'Client not configured: set baseUrl or baseUrlResolver via configureClient().',\n );\n }\n const fullUrl = `${base}${url}`;\n const maxRetries = config.maxRetries ?? 0;\n\n // A caller that supplied its own Authorization header owns that credential.\n // `buildHeaders` lets it win, so refreshing the CLIENT-WIDE token and\n // re-sending would replay the identical failing request with the identical\n // credential: one wasted round trip, plus a global refresh nobody asked for.\n const callerSuppliedAuth = new Headers(options.headers).has('Authorization');\n\n const headers = await buildHeaders(config, options.headers);\n const requestInit: RequestInit = { ...options, headers };\n\n let delay = BASE_BACKOFF_MS;\n\n for (let attempt = 0; ; attempt++) {\n const response = await fetch(fullUrl, requestInit);\n\n if (\n response.status === 401 &&\n config.onUnauthorized &&\n !callerSuppliedAuth &&\n attempt === 0\n ) {\n await config.onUnauthorized();\n const refreshedHeaders = await buildHeaders(config, options.headers);\n const retryResponse = await fetch(fullUrl, {\n ...options,\n headers: refreshedHeaders,\n });\n const retryBody = await parseBody(retryResponse);\n if (retryResponse.status >= 400) {\n throw new ClientError(retryResponse.status, fullUrl, retryBody);\n }\n return retryBody as T;\n }\n\n if (\n !RETRYABLE_STATUSES.includes(response.status) ||\n attempt >= maxRetries\n ) {\n const body = await parseBody(response);\n if (response.status >= 400) {\n throw new ClientError(response.status, fullUrl, body);\n }\n return body as T;\n }\n\n const retryAfter = parseRetryAfter(response.headers.get('Retry-After'));\n const waitMs = retryAfter ?? addJitter(delay);\n // Non-null: `configureClient` refuses a budget above 0 without an observer,\n // and this line is unreachable unless `maxRetries` is above 0.\n (config.onRetry as (notice: RetryNotice) => void)({\n attempt: attempt + 1,\n maxRetries,\n status: response.status,\n waitMs,\n retryAfterHonoured: retryAfter !== undefined,\n url: fullUrl,\n method: options.method ?? 'GET',\n });\n await sleep(waitMs);\n delay = Math.min(delay * 2, MAX_BACKOFF_MS);\n }\n};\n\n/**\n * Does this media type carry JSON?\n *\n * \u2500\u2500 THE OUTAGE THIS FIXES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * This used to be `contentType?.includes('application/json')`, and EVERY error\n * this fleet returns failed that test. The platform's `GlobalExceptionFilter`\n * emits RFC 9457 problem documents and sets the media type explicitly \u2014\n * `.type(PROBLEM_DETAILS_CONTENT_TYPE)` before `.json()`, so it cannot be\n * overridden \u2014 and that constant is `application/problem+json`, which does NOT\n * contain the substring `application/json`: after `application/` comes\n * `problem+json`.\n *\n * So every non-2xx body fell through to `response.text()` and reached the\n * caller as an UNPARSED STRING. `ClientError.body` is typed `unknown`, so\n * nothing complained; every consumer that reads a machine-readable code off an\n * error \u2014 `error.body.code`, `error.body.details.code` \u2014 silently read\n * `undefined` instead, for every error, in every service.\n *\n * Measured in production on 2026-09-15: skill-registry-api's\n * `resolveOrNull()` absorbs `RELEASE_CHANNEL_NOT_FOUND` into `null` by exactly\n * that read. With the code unreadable the absorb never fired, a routine \"no\n * pointer on this channel\" 404 became a 500 on `GET /skills` and\n * `GET /describe-objects`, and agent-session-api's `apply_control_bundle` step\n * aborted EVERY session launch in the organisation \u2014 7,871 failed resolutions\n * an hour. A fix written for that exact page months earlier was present in the\n * running image and could not help, because the classifier it repaired was\n * being handed a string.\n *\n * \u2500\u2500 WHY THE STRUCTURED SUFFIX, NOT A SECOND SUBSTRING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Adding `|| includes('application/problem+json')` would fix this one media\n * type and leave the next one \u2014 `application/vnd.api+json`, and anything else\n * a service legitimately emits. RFC 6839 defines `+json` as the structured\n * syntax suffix meaning \"this is JSON\"; that is the actual rule, so it is the\n * rule implemented. Parameters are stripped first, because\n * `application/problem+json; charset=utf-8` is the same media type.\n */\nfunction isJsonMediaType(contentType: string | null): boolean {\n if (contentType === null) {\n return false;\n }\n const essence = contentType.split(';')[0]?.trim().toLowerCase() ?? '';\n return essence === 'application/json' || essence.endsWith('+json');\n}\n\nasync function parseBody(response: Response): Promise {\n // 204 FIRST, and deliberately \u2014 this ORDER is carried from `develop`, which\n // fixed the same defect independently and got this half right where `main`\n // did not. A 204 carries no body, so `response.json()` on one throws\n // SyntaxError; and a 204 whose headers STILL declare a JSON content type is\n // ordinary, because the framework sets the header before the handler returns\n // nothing. With the JSON branch first, a documented `undefined` becomes a\n // thrown parse error. Pinned by a case in `transport-problem-json.test.cjs`.\n if (response.status === 204) {\n return undefined;\n }\n const contentType = response.headers.get('content-type');\n if (isJsonMediaType(contentType)) {\n return response.json();\n }\n return response.text();\n}\n\nfunction parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (!isNaN(seconds) && seconds >= 0) return seconds * 1000;\n const date = new Date(value);\n if (!isNaN(date.getTime())) return Math.max(0, date.getTime() - Date.now());\n return undefined;\n}\n\nfunction addJitter(delay: number): number {\n return delay + (Math.random() * 2 - 1) * delay * 0.25;\n}\n\nfunction sleep(ms: number): Promise {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport default customFetch;\n"; //# sourceMappingURL=custom-fetch-template.d.ts.map