@sigrun/provider-bifrost 0.0.0-stage → 0.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-10-18
11
+ ### Changed
12
+ - Renamed to `@sigrun/provider-bifrost` for the 0.1.0 release (GitHub
13
+ repo `sigrun-ai/sigrun-provider-bifrost`): published package metadata
14
+ (`publishConfig` access/provenance), host modules
15
+ (`@earendil-works/pi-ai`, `@earendil-works/pi-durable`, `typebox`)
16
+ moved to `peerDependencies` mirrored in `devDependencies` at exact
17
+ pins, CHANGELOG.md joined the files whitelist, and a manifest hygiene
18
+ check wired into `npm test`. The contributor identity follows the
19
+ package slug: the factory result's `name` becomes `provider-bifrost`
20
+ and the scoped settings file `settings/provider-bifrost.json` (the
21
+ loader pre-keys the two-phase host from the package name; a stale
22
+ declared name would split the factory-time settings scope).
23
+ ### Added
24
+ - Sigrun provider plugin for a Bifrost LLM gateway, ported from
25
+ pi-bifrost-gateway 0.3.0 (`5d15a28`): provider-only contributor
26
+ (`{ name: "provider-bifrost", providers: [{ provider, readiness }],
27
+ health }`), `provider: "bifrost"` stamped on every published model so
28
+ persisted catalogs survive cold-boot restore.
29
+ - Two discovery modes: member-only (default, `publishChains: false` —
30
+ single `/v1/models` fetch, ZERO management-plane calls, stock-gateway
31
+ compatible) and chains (`publishChains: true` — VK self-identification
32
+ via `/api/governance/virtual-keys` + chain rules via
33
+ `/api/routing/rules`, chains published as `bifrost/<chain>` with
34
+ min-member context windows and cost 0).
35
+ - Scoped-settings configuration (`settings/provider-bifrost.json`), read
36
+ once at factory time (restart-required): `baseUrl`, `publishChains`
37
+ (default false), `publishUpstream` (default true),
38
+ `managementApiKey`, `thinkingOverrides`, `nativeProtocols`
39
+ (`responses`/`messages` toggles + exact-name lists replacing the
40
+ built-in family heuristic; both default false — chat completions
41
+ everywhere). No /login flow.
42
+ - Native protocol surfaces ported exactly: model NAME decides the family
43
+ (never the serving prefix); claude → `anthropic-messages` at
44
+ `<gateway-root>/anthropic` with the `forceAdaptiveThinking` pin and
45
+ `off: null` ladder; gpt(?!-oss)/o\d/codex → `openai-responses` with
46
+ `off: "none"`; everything else chat completions with `off: "off"`.
47
+ - TEXT_ONLY denylist ported (glm-5.3, deepseek-v4-flash/pro + snapshot
48
+ ids; exact bare-name matching so vision-capable siblings keep image
49
+ input).
50
+ - Credentials through sigrun's auth.json bridge with `BIFROST_API_KEY`
51
+ as the ambient fallback; base URL settings-first with
52
+ `BIFROST_BASE_URL` ambient fallback (env reads via the injected seam,
53
+ never `process.env`).
54
+ - `fetchModels` throws on unconfigured / VK mismatch / zero chains /
55
+ both-flags-off / discovery failure — never returns `[]` (an empty
56
+ array would publish and wipe the persisted catalog).
57
+ - Three-state readiness tracker (`not_configured` / `degraded` /
58
+ `ready`) seeded from credential resolution and updated per refresh
59
+ outcome; `console.warn` on state transitions only. `health()` maps
60
+ `not_configured` to `"ok"` and configured-and-failing to `"degraded"`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael MacLeod
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ # @sigrun/provider-bifrost
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [sigrun](https://github.com/sigrun-ai) provider plugin: a
4
+ [Bifrost](https://github.com/maximhq/bifrost) LLM gateway as one dynamic
5
+ chat provider. Ported from
6
+ [pi-bifrost-gateway](https://github.com/MacLeodMike/pi-bifrost-gateway)
7
+ 0.3.0 (`5d15a28`). All failover, key selection, and routing happens in
8
+ Bifrost — this plugin never routes anything itself.
9
+
10
+ ## What it contributes
11
+
12
+ A **provider-only contributor** — the default export is a factory
13
+ returning `{ name: "provider-bifrost", providers: [{ provider, readiness }],
14
+ health }` with no pi-durable extension surface. Sigrun's loader merges
15
+ the provider into its `Models` store at boot.
16
+
17
+ - **Provider** `bifrost` — dynamic catalog over `openai-completions`
18
+ (plus optional per-family native surfaces, below). Two discovery
19
+ modes, picked by the settings `publishChains` flag:
20
+ - **member-only mode (default, `publishChains: false`)** — a single
21
+ `GET <base>/models` with the virtual key, publishing every upstream
22
+ as a direct-selectable `provider/model` entry with real per-member
23
+ rates. **Zero management-plane calls** — works against a stock
24
+ gateway with governance disabled entirely.
25
+ - **chains mode (`publishChains: true`)** — the management plane is
26
+ queried too: the VK self-identifies against
27
+ `/api/governance/virtual-keys` (the credential value matched to VK
28
+ rows), then `/api/routing/rules?scope=virtual_key&scope_id=<vk>`
29
+ yields the chain rules; every usable chain publishes as
30
+ `bifrost/<chain>` (min-member context window, cost 0 by design —
31
+ the gateway's own `/api/logs` is the billing system of record),
32
+ optionally followed by the upstream catalog (`publishUpstream`,
33
+ default true).
34
+ - **Readiness** (`not_configured` / `degraded` / `ready`, surfaced on
35
+ sigrun's `/v1/auth/check`) — seeded from credential resolution and
36
+ updated per refresh outcome; state transitions log via `console.warn`
37
+ (transitions only, never per failure).
38
+ - **Health** — `degraded` only when the provider is configured and its
39
+ last refresh failed; an unconfigured optional plugin reports `ok`.
40
+
41
+ ## Settings
42
+
43
+ Read **once at factory time** from the plugin's scoped settings —
44
+ `settings/provider-bifrost.json` in the sigrun config dir — and **baked
45
+ into the provider**; applying a change requires a restart (sigrun's
46
+ loader pre-keys the factory host on the provisional slug, so the
47
+ factory-time read resolves this file). Absent file = defaults.
48
+ Wrong-typed values and unknown keys warn and fall back — never fatal.
49
+
50
+ ```jsonc
51
+ // settings/provider-bifrost.json
52
+ {
53
+ "baseUrl": "https://your-bifrost.example/v1", // settings wins, then BIFROST_BASE_URL
54
+ "publishChains": false, // chains mode when true (needs management plane)
55
+ "publishUpstream": true, // upstream catalog entries (both modes)
56
+ "managementApiKey": "…", // /api/* bearer; falls back to the VK
57
+ "thinkingOverrides": { // per-member ladders keyed "provider/model";
58
+ "acme/model-x": ["low", "high"] // replaces the full default ladder
59
+ },
60
+ "nativeProtocols": { // both default false = chat completions everywhere
61
+ "responses": true, // gpt-family → openai-responses
62
+ "messages": true, // claude-family → anthropic-messages at <root>/anthropic
63
+ "responsesModels": ["gpt-6-luna"], // exact-name lists REPLACE the heuristic
64
+ "messagesModels": ["claude-sonnet-5-5"]
65
+ }
66
+ }
67
+ ```
68
+
69
+ **Plaintext caveat:** this settings file is plain JSON — it has none of
70
+ `auth.json`'s `0600` discipline. That is consistent with sigrun's
71
+ operator-provisioned-plaintext posture: `managementApiKey` is
72
+ provisioned by the operator into the config dir exactly like
73
+ `settings.json` itself, and anything needing secret-handling semantics
74
+ belongs in the VK (`auth.json` / `BIFROST_API_KEY`) instead — the
75
+ management plane deliberately falls back to the VK when no separate key
76
+ is set.
77
+
78
+ ### Native protocol surfaces
79
+
80
+ With both `nativeProtocols` toggles off (the default), every model
81
+ publishes chat completions — the only wire every Bifrost serves. With a
82
+ toggle on, the model **NAME** (never the serving prefix) picks the
83
+ wire: `claude-*` → `anthropic-messages` at `<gateway-root>/anthropic`
84
+ (the Anthropic SDK appends `/v1/messages` itself), `gpt(?!-oss)`/`o\d`/
85
+ `codex` → `openai-responses` on the `/v1` base, everything else —
86
+ including `gpt-oss`, an open-weight model — stays completions. Setting
87
+ the paired exact-name list replaces the built-in heuristic for that
88
+ surface. Per-surface thinking-compat pins ship as in the pi port
89
+ (`off: null` + `forceAdaptiveThinking` on anthropic, `off: "none"` on
90
+ responses, `off: "off"` on completions).
91
+
92
+ ## Credentials
93
+
94
+ Resolved per request through sigrun's auth.json bridge, falling back to
95
+ the ambient environment:
96
+
97
+ 1. the bridged auth.json credential (`"default"` entry, then the
98
+ `"bifrost"` entry), then
99
+ 2. `BIFROST_API_KEY`.
100
+
101
+ The base URL resolves settings-`baseUrl` first, then the ambient
102
+ `BIFROST_BASE_URL`. Both env reads go through sigrun's env seam — the
103
+ plugin never reads auth.json or `process.env` itself. There is **no
104
+ /login flow** (the pi port's interactive login has no sigrun analog);
105
+ rotating the key or the env URL applies on the next resolution — no
106
+ restart. Settings changes DO need a restart (baked at factory time).
107
+
108
+ `managementApiKey` comes from scoped settings only and is used as the
109
+ bearer on `/api/*` (VK lookup + routing rules); the inference catalog
110
+ always uses the VK itself. When unset, the VK serves the management
111
+ plane too — the right shape for dashboard-auth-disabled deployments.
112
+
113
+ ## Failure semantics
114
+
115
+ `fetchModels` **throws** — it never returns an empty array, which pi-ai
116
+ would publish and persist, wiping the catalog — on: unconfigured (no
117
+ key or no base URL), a credential matching no VK (chains mode), zero
118
+ usable chain rules (chains mode), both publish flags off, and any
119
+ discovery failure (unreachable gateway, schema mismatch). A failed
120
+ refresh retains the previous catalog and records a per-provider error;
121
+ the provider still boots unconfigured with an empty baseline catalog
122
+ and `not_configured` readiness.
123
+
124
+ ## Install
125
+
126
+ ```jsonc
127
+ // settings.json (sigrun config dir) — the UNIFIED extensions list:
128
+ // install + load in one place; npm and git specifiers are the same list
129
+ // npm ("@sigrun/provider-bifrost@^0.1.0"), git
130
+ // ("git:github.com/sigrun-ai/sigrun-provider-bifrost", or any
131
+ // https git URL with an optional @ref), or the file: form below.
132
+ {
133
+ "extensions": ["file:/path/to/sigrun-bifrost"]
134
+ }
135
+ ```
136
+
137
+ The committed `dist/` is the load target for git installs; pi-ai and
138
+ pi-durable are peerDependencies (sigrun supplies them at runtime);
139
+ they are mirrored in devDependencies for this repo's own build and tests.
140
+
141
+ ## Development
142
+
143
+ ```sh
144
+ npm install # also builds dist/ via prepare
145
+ npm test # vitest — hermetic fixture gateway server, no network
146
+ npm run typecheck
147
+ npm run build
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Catalog composition (ported from pi-bifrost-gateway 0.3.0 @ 5d15a28,
3
+ * `src/catalog.ts` + the constants of its `src/bifrost.ts`): wire schemas
4
+ * and fetching, chain decoding from routing-rule CELs, native-family
5
+ * surface selection, TEXT_ONLY denylist scoping, and the pure model
6
+ * builders. Every published model is stamped `provider: "bifrost"` —
7
+ * pi-ai's restore path drops stored models whose `provider` differs from
8
+ * the registered id, so an unstamped catalog silently vanishes on cold
9
+ * boot (restore-filter spike, Review Focus #3).
10
+ */
11
+ import type { Model } from "@earendil-works/pi-ai";
12
+ import { Type } from "@earendil-works/pi-ai";
13
+ import { type FetchJsonOptions } from "./http.js";
14
+ import type { NativeProtocols } from "./config.js";
15
+ /** The provider id models are registered (and stamped) under. */
16
+ export declare const PROVIDER_ID = "bifrost";
17
+ export declare const PROVIDER_DISPLAY_NAME = "Bifrost Gateway";
18
+ /** Different relative to the configured /v1 base URL. */
19
+ export declare const INFERENCE_PATH_MODELS = "models";
20
+ /** Management API paths (live-verified on Bifrost v2.2.x, 2026-10-08). */
21
+ export declare const MANAGEMENT_PATH_VIRTUAL_KEYS = "/api/governance/virtual-keys";
22
+ export declare const MANAGEMENT_PATH_ROUTING_RULES = "/api/routing/rules";
23
+ /**
24
+ * Neutral createProvider-shape baseUrl used before a URL exists from any
25
+ * source. NEVER requested: every published model carries its own
26
+ * discovery-time baseUrl, and discovery refuses to run without a real URL
27
+ * (unconfigured throws). An install in this state publishes zero models.
28
+ */
29
+ export declare const UNRESOLVED_BASE_URL = "http://localhost:8080/v1";
30
+ /** Model apis this plugin publishes, keyed to pi-ai's registered implementations. */
31
+ export type SurfaceApi = "openai-completions" | "openai-responses" | "anthropic-messages";
32
+ export type GatewayModel = Model<"openai-completions"> | Model<"openai-responses"> | Model<"anthropic-messages">;
33
+ /**
34
+ * Which native wire a model NAME speaks (pi-inferhub nativeFamily, ported).
35
+ * BUILT-IN heuristic — the user-facing gates live in settings
36
+ * (nativeProtocols.responses/messages toggles, plus exact-name lists that
37
+ * replace this heuristic when set). Family of the model NAME (last path
38
+ * segment), never the serving prefix: the live catalog has `kiro/` serving
39
+ * both claude-* and gpt-* rows. `gpt-oss` is an open-weight model, not an
40
+ * [OI] native one, so it stays other; `o\d` catches o3/o4-style rows,
41
+ * `codex` the codex family.
42
+ */
43
+ export type NativeFamily = "anthropic" | "openai" | "other";
44
+ export declare function nativeFamily(id: string): NativeFamily;
45
+ /**
46
+ * Client wire + base URL for one model id, per settings:
47
+ * - nativeProtocols.messages on + (name in messagesModels list, or built-in
48
+ * claude heuristic when no list) → anthropic-messages at the gateway's
49
+ * /anthropic integration prefix (pi-ai's Anthropic SDK appends /v1/messages
50
+ * itself; x-api-key VK auth verified live).
51
+ * - nativeProtocols.responses on + (responsesModels list, or built-in gpt
52
+ * heuristic) → openai-responses on the /v1 base.
53
+ * - otherwise openai-completions — the only wire every Bifrost serves, and
54
+ * the gateway translates at the boundary anyway.
55
+ */
56
+ export declare function surfaceFor(id: string, baseUrl: string, native?: NativeProtocols): {
57
+ api: SurfaceApi;
58
+ baseUrl: string;
59
+ };
60
+ export declare const CatalogModelSchema: Type.TObject<{
61
+ id: Type.TString;
62
+ owned_by: Type.TOptional<Type.TString>;
63
+ context_length: Type.TOptional<Type.TNumber>;
64
+ max_output_limit: Type.TOptional<Type.TNumber>;
65
+ pricing: Type.TOptional<Type.TObject<{
66
+ prompt: Type.TString;
67
+ completion: Type.TString;
68
+ input_cache_read: Type.TOptional<Type.TString>;
69
+ input_cache_write: Type.TOptional<Type.TString>;
70
+ }>>;
71
+ }>;
72
+ export declare const ModelsEnvelopeSchema: Type.TObject<{
73
+ data: Type.TArray<Type.TObject<{
74
+ id: Type.TString;
75
+ owned_by: Type.TOptional<Type.TString>;
76
+ context_length: Type.TOptional<Type.TNumber>;
77
+ max_output_limit: Type.TOptional<Type.TNumber>;
78
+ pricing: Type.TOptional<Type.TObject<{
79
+ prompt: Type.TString;
80
+ completion: Type.TString;
81
+ input_cache_read: Type.TOptional<Type.TString>;
82
+ input_cache_write: Type.TOptional<Type.TString>;
83
+ }>>;
84
+ }>>;
85
+ }>;
86
+ export declare const RoutingTargetSchema: Type.TObject<{
87
+ provider: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
88
+ model: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
89
+ weight: Type.TNumber;
90
+ }>;
91
+ export declare const RoutingRuleSchema: Type.TObject<{
92
+ id: Type.TString;
93
+ name: Type.TString;
94
+ enabled: Type.TOptional<Type.TBoolean>;
95
+ cel_expression: Type.TString;
96
+ targets: Type.TArray<Type.TObject<{
97
+ provider: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
98
+ model: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
99
+ weight: Type.TNumber;
100
+ }>>;
101
+ fallbacks: Type.TOptional<Type.TArray<Type.TString>>;
102
+ scope: Type.TString;
103
+ scope_id: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
104
+ priority: Type.TOptional<Type.TNumber>;
105
+ }>;
106
+ export declare const RulesEnvelopeSchema: Type.TObject<{
107
+ rules: Type.TArray<Type.TObject<{
108
+ id: Type.TString;
109
+ name: Type.TString;
110
+ enabled: Type.TOptional<Type.TBoolean>;
111
+ cel_expression: Type.TString;
112
+ targets: Type.TArray<Type.TObject<{
113
+ provider: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
114
+ model: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
115
+ weight: Type.TNumber;
116
+ }>>;
117
+ fallbacks: Type.TOptional<Type.TArray<Type.TString>>;
118
+ scope: Type.TString;
119
+ scope_id: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
120
+ priority: Type.TOptional<Type.TNumber>;
121
+ }>>;
122
+ }>;
123
+ export declare const VirtualKeySchema: Type.TObject<{
124
+ id: Type.TString;
125
+ name: Type.TOptional<Type.TString>;
126
+ value: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
127
+ }>;
128
+ /**
129
+ * Wire envelope as observed 2026-10-08: GET /api/governance/virtual-keys
130
+ * returns `{virtual_keys: [...], count, ...}` with the full VK rows
131
+ * INCLUDING their secret values. Unknown extra fields pass validation.
132
+ */
133
+ export declare const VirtualKeysEnvelopeSchema: Type.TObject<{
134
+ virtual_keys: Type.TArray<Type.TObject<{
135
+ id: Type.TString;
136
+ name: Type.TOptional<Type.TString>;
137
+ value: Type.TOptional<Type.TUnion<[Type.TString, Type.TNull]>>;
138
+ }>>;
139
+ }>;
140
+ export type CatalogModel = {
141
+ id: string;
142
+ owned_by?: string;
143
+ context_length?: number;
144
+ max_output_limit?: number;
145
+ pricing?: {
146
+ prompt: string;
147
+ completion: string;
148
+ input_cache_read?: string;
149
+ input_cache_write?: string;
150
+ };
151
+ };
152
+ export type RoutingRule = {
153
+ id: string;
154
+ name: string;
155
+ enabled?: boolean;
156
+ cel_expression: string;
157
+ targets: Array<{
158
+ provider?: string | null;
159
+ model?: string | null;
160
+ weight: number;
161
+ }>;
162
+ fallbacks?: string[];
163
+ scope: string;
164
+ scope_id?: string | null;
165
+ priority?: number;
166
+ };
167
+ export type VirtualKey = {
168
+ id: string;
169
+ name?: string;
170
+ value?: string | null;
171
+ };
172
+ export type Chain = {
173
+ /** Chain model name extracted from the routing rule (e.g. "glm-5.3-flash"). */
174
+ name: string;
175
+ /** Primary member id, "provider/model". */
176
+ primary: string;
177
+ /** Fallback member ids, in order. */
178
+ fallbacks: string[];
179
+ };
180
+ /** Decode an OpenAI-shaped /v1/models wire envelope into member entries. */
181
+ export declare function wireCatalogToModels(envelope: {
182
+ data: CatalogModel[];
183
+ }): CatalogModel[];
184
+ /** GET gateway /v1/models with the VK — the full member catalog with pricing. */
185
+ export declare function wireCatalog(token: string, baseUrl: string, signal?: AbortSignal, opts?: FetchJsonOptions): Promise<CatalogModel[]>;
186
+ /** Resolve the management-plane base URL from the inference /v1 base URL. */
187
+ export declare function managementBase(baseUrl: string): string;
188
+ /**
189
+ * GET /api/governance/virtual-keys and match the INFERENCE credential value
190
+ * (the VK) against VK values to discover this plugin's VK id (the scope_id
191
+ * for routing-rule filtering). AUTH on the call is the MANAGEMENT token
192
+ * (settings managementApiKey, falling back to the VK — the split matters on
193
+ * deployments whose management plane expects a different credential than
194
+ * inference). Returns undefined when no VK row's value matches vkValue.
195
+ */
196
+ export declare function wireScopeId(managementToken: string, vkValue: string, baseUrl: string, signal?: AbortSignal, opts?: FetchJsonOptions): Promise<string | undefined>;
197
+ /**
198
+ * GET /api/routing/rules?scope=virtual_key&scope_id=<id> and decode the
199
+ * chain rules. AUTH is the MANAGEMENT token (see wireScopeId). Throws when
200
+ * Bifrost responds with an error envelope (schema mismatch or HTTP error
201
+ * surfaced by fetchJson).
202
+ */
203
+ export declare function wireRules(managementToken: string, baseUrl: string, scopeId: string, signal?: AbortSignal, opts?: FetchJsonOptions): Promise<RoutingRule[]>;
204
+ /** Full discovery: catalog + rules for the VK, in parallel where possible.
205
+ * AUTH: management plane (VK lookup + rules) uses managementToken; the
206
+ * inference catalog uses the VK itself. */
207
+ export declare function wireDiscovery(managementToken: string, vkValue: string, baseUrl: string, scopeId: string, signal?: AbortSignal, opts?: FetchJsonOptions): Promise<{
208
+ catalog: CatalogModel[];
209
+ rules: RoutingRule[];
210
+ }>;
211
+ /**
212
+ * Extract a chain model name from a routing rule's CEL expression.
213
+ * Recognizes `model == "x"` arms and strips managed prefixes (`bifrost/`,
214
+ * legacy `gateway/`) from each name. Every arm must reduce to one canonical
215
+ * bare name — the chains-first convention. Anything else is not a chain rule.
216
+ */
217
+ export declare function chainNameFromCel(cel: string): string | undefined;
218
+ /** Decode enabled, unambiguous chain rules. Order follows rule priority. */
219
+ export declare function wireRulesToChains(rules: RoutingRule[]): Chain[];
220
+ /** Convert a $/token wire string to pi's $/Mtok rate. */
221
+ export declare function perMtok(dollarsPerToken: string): number | undefined;
222
+ /**
223
+ * Build one bifrost/<chain> chat model per chain. Cost is 0 by design. The
224
+ * wire follows the chain's model-family (native protocol end to end): the
225
+ * chain name decides — fallback legs of a chain are same-model by
226
+ * construction, and the gateway translates any client wire for legs that
227
+ * differ.
228
+ */
229
+ export declare function buildChainModels(chains: Chain[], catalog: CatalogModel[], providerSlug: string, baseUrl: string, native?: NativeProtocols): GatewayModel[];
230
+ /**
231
+ * Build direct-selectable models for the /v1/models catalog with real
232
+ * per-member pricing, ordered with chain members first.
233
+ */
234
+ export declare function buildMemberModels(chains: Chain[], catalog: CatalogModel[], providerSlug: string, baseUrl: string, thinkingOverrides?: Record<string, string[]>, native?: NativeProtocols): GatewayModel[];
235
+ /** Chains first, then upstream models (deduped by id — chain ids are namespaced). */
236
+ export declare function buildAllModels(chains: Chain[], catalog: CatalogModel[], providerSlug: string, baseUrl: string, publishChains: boolean, publishUpstream: boolean, thinkingOverrides?: Record<string, string[]>, native?: NativeProtocols): GatewayModel[];