@sigrun/provider-bifrost 0.0.0-stage → 0.1.1
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 +65 -0
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/catalog.d.ts +236 -0
- package/dist/catalog.js +448 -0
- package/dist/config.d.ts +77 -0
- package/dist/config.js +161 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +88 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +280 -0
- package/package.json +52 -5
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
## [0.1.1] - 2026-10-27
|
|
9
|
+
### Changed
|
|
10
|
+
- Re-released through CI trusted publishing; this version carries SLSA
|
|
11
|
+
provenance. No functional change from 0.1.0.
|
|
12
|
+
|
|
13
|
+
## [Unreleased]
|
|
14
|
+
|
|
15
|
+
## [0.1.0] - 2026-10-18
|
|
16
|
+
### Changed
|
|
17
|
+
- Renamed to `@sigrun/provider-bifrost` for the 0.1.0 release (GitHub
|
|
18
|
+
repo `sigrun-ai/sigrun-provider-bifrost`): published package metadata
|
|
19
|
+
(`publishConfig` access/provenance), host modules
|
|
20
|
+
(`@earendil-works/pi-ai`, `@earendil-works/pi-durable`, `typebox`)
|
|
21
|
+
moved to `peerDependencies` mirrored in `devDependencies` at exact
|
|
22
|
+
pins, CHANGELOG.md joined the files whitelist, and a manifest hygiene
|
|
23
|
+
check wired into `npm test`. The contributor identity follows the
|
|
24
|
+
package slug: the factory result's `name` becomes `provider-bifrost`
|
|
25
|
+
and the scoped settings file `settings/provider-bifrost.json` (the
|
|
26
|
+
loader pre-keys the two-phase host from the package name; a stale
|
|
27
|
+
declared name would split the factory-time settings scope).
|
|
28
|
+
### Added
|
|
29
|
+
- Sigrun provider plugin for a Bifrost LLM gateway, ported from
|
|
30
|
+
pi-bifrost-gateway 0.3.0 (`5d15a28`): provider-only contributor
|
|
31
|
+
(`{ name: "provider-bifrost", providers: [{ provider, readiness }],
|
|
32
|
+
health }`), `provider: "bifrost"` stamped on every published model so
|
|
33
|
+
persisted catalogs survive cold-boot restore.
|
|
34
|
+
- Two discovery modes: member-only (default, `publishChains: false` —
|
|
35
|
+
single `/v1/models` fetch, ZERO management-plane calls, stock-gateway
|
|
36
|
+
compatible) and chains (`publishChains: true` — VK self-identification
|
|
37
|
+
via `/api/governance/virtual-keys` + chain rules via
|
|
38
|
+
`/api/routing/rules`, chains published as `bifrost/<chain>` with
|
|
39
|
+
min-member context windows and cost 0).
|
|
40
|
+
- Scoped-settings configuration (`settings/provider-bifrost.json`), read
|
|
41
|
+
once at factory time (restart-required): `baseUrl`, `publishChains`
|
|
42
|
+
(default false), `publishUpstream` (default true),
|
|
43
|
+
`managementApiKey`, `thinkingOverrides`, `nativeProtocols`
|
|
44
|
+
(`responses`/`messages` toggles + exact-name lists replacing the
|
|
45
|
+
built-in family heuristic; both default false — chat completions
|
|
46
|
+
everywhere). No /login flow.
|
|
47
|
+
- Native protocol surfaces ported exactly: model NAME decides the family
|
|
48
|
+
(never the serving prefix); claude → `anthropic-messages` at
|
|
49
|
+
`<gateway-root>/anthropic` with the `forceAdaptiveThinking` pin and
|
|
50
|
+
`off: null` ladder; gpt(?!-oss)/o\d/codex → `openai-responses` with
|
|
51
|
+
`off: "none"`; everything else chat completions with `off: "off"`.
|
|
52
|
+
- TEXT_ONLY denylist ported (glm-5.3, deepseek-v4-flash/pro + snapshot
|
|
53
|
+
ids; exact bare-name matching so vision-capable siblings keep image
|
|
54
|
+
input).
|
|
55
|
+
- Credentials through sigrun's auth.json bridge with `BIFROST_API_KEY`
|
|
56
|
+
as the ambient fallback; base URL settings-first with
|
|
57
|
+
`BIFROST_BASE_URL` ambient fallback (env reads via the injected seam,
|
|
58
|
+
never `process.env`).
|
|
59
|
+
- `fetchModels` throws on unconfigured / VK mismatch / zero chains /
|
|
60
|
+
both-flags-off / discovery failure — never returns `[]` (an empty
|
|
61
|
+
array would publish and wipe the persisted catalog).
|
|
62
|
+
- Three-state readiness tracker (`not_configured` / `degraded` /
|
|
63
|
+
`ready`) seeded from credential resolution and updated per refresh
|
|
64
|
+
outcome; `console.warn` on state transitions only. `health()` maps
|
|
65
|
+
`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
|
-
#
|
|
1
|
+
# @sigrun/provider-bifrost
|
|
2
2
|
|
|
3
|
-
|
|
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[];
|