@agentproto/runtime 1.1.0 → 2.0.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/README.md +3 -1
- package/dist/catalog-models.d.ts +255 -0
- package/dist/catalog-models.mjs +386 -0
- package/dist/catalog-models.mjs.map +1 -0
- package/dist/config.d.ts +378 -1
- package/dist/config.mjs.map +1 -1
- package/dist/context-continuity-B9n0t0v-.d.ts +53 -0
- package/dist/index.d.ts +6002 -2816
- package/dist/index.mjs +21719 -11296
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +140 -0
- package/dist/pr-provenance.mjs +106 -0
- package/dist/pr-provenance.mjs.map +1 -0
- package/dist/resume-strategies.d.ts +39 -2
- package/dist/resume-strategies.mjs +765 -28
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/session-config-DIf6wYYP.d.ts +192 -0
- package/dist/session-story-panel.d.ts +26 -0
- package/dist/session-story-panel.mjs +970 -0
- package/dist/session-story-panel.mjs.map +1 -0
- package/dist/session-story.d.ts +12 -2
- package/dist/spawn-defaults-7uHRnYH1.d.ts +414 -0
- package/dist/telegram-proxy.d.ts +35 -0
- package/dist/telegram-proxy.mjs +115 -0
- package/dist/telegram-proxy.mjs.map +1 -0
- package/dist/user-presets.d.ts +91 -0
- package/dist/user-presets.mjs +84 -0
- package/dist/user-presets.mjs.map +1 -0
- package/package.json +48 -23
- package/dist/config-BRKy_SAF.d.ts +0 -569
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ A per-boot bearer token is generated automatically and written into `<workspace>
|
|
|
45
45
|
| Sessions list | `GET /sessions` / `GET /sessions/:id` | id-or-name in `:id` |
|
|
46
46
|
| Agent spawn | `POST /sessions/agent` | Long-lived ACP agent (needs `resolveAgentAdapter`) |
|
|
47
47
|
| Interrupt turn | `POST /sessions/:id/interrupt` | Cancel the in-flight turn; session stays alive |
|
|
48
|
+
| Terminal input | `POST /sessions/:id/terminal/input` | Write raw input into a live PTY session |
|
|
49
|
+
| Rename session | `PATCH /sessions/:id` | Set or clear the session's user-facing `title`/`label`|
|
|
48
50
|
| **PTY spawn** | **`POST /sessions/terminal`** | Needs `spawnPty` factory |
|
|
49
51
|
| **PTY attach** | **`WS /sessions/:id/pty`** | JSON frames `{kind:data|input|resize|exit|ping|pong}`; multi-subscriber, min-size resize, ring-buffer replay |
|
|
50
52
|
| SSE attach | `GET /sessions/:id/stream` | Line-by-line text events |
|
|
@@ -52,7 +54,7 @@ A per-boot bearer token is generated automatically and written into `<workspace>
|
|
|
52
54
|
|
|
53
55
|
### Auth model
|
|
54
56
|
|
|
55
|
-
- `Authorization: Bearer <token>` required on **mutating** `/sessions/*` routes (POST/DELETE) and the PTY WS upgrade.
|
|
57
|
+
- `Authorization: Bearer <token>` required on **mutating** `/sessions/*` routes (POST/PATCH/DELETE) and the PTY WS upgrade.
|
|
56
58
|
- **No loopback bypass** for those routes — the threat being defended against is a browser fetch from a localhost-loaded page, which IS loopback. A browser can't read `runtime.json` (mode 0600); a same-user process can.
|
|
57
59
|
- Read routes (`GET /sessions`, SSE `/stream`) stay open for read-only telemetry compatibility.
|
|
58
60
|
- The optional `auth?: AuthOptions` field on `createGateway` is for the *tunnel* bearer (Cloudflare-fronted public surface), independent of the per-boot token. It gates `/mcp`, `/events`, `/conversations*`, and the heartbeat tick route, with a loopback bypass for requests that never crossed a tunnel (127.0.0.1/::1 with no `X-Forwarded-For`).
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { AuthProfile } from '@agentproto/auth';
|
|
2
|
+
import { A as AdapterAuthDescriptor } from './spawn-defaults-7uHRnYH1.js';
|
|
3
|
+
import { R as RouteSpec } from './session-config-DIf6wYYP.js';
|
|
4
|
+
import '@agentproto/model-catalog';
|
|
5
|
+
import './context-continuity-B9n0t0v-.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Read-only catalog/vendor endpoint (`agentproto-session-config-axes`
|
|
9
|
+
* SPEC §5) — `GET /catalog/models` + `catalog_models` MCP tool wire into
|
|
10
|
+
* {@link buildCatalogModels}, the pure join this module owns.
|
|
11
|
+
*
|
|
12
|
+
* Reuses three already-shipped pieces instead of rebuilding them:
|
|
13
|
+
* - the vendor/product/route model + router widening (OpenRouter/
|
|
14
|
+
* Requesty/HuggingFace) from `@agentproto/model-catalog/route-identity`
|
|
15
|
+
* (`resolveLlmModelRoute`, `route-identity/index.ts:396-511`) — this is
|
|
16
|
+
* what keeps the catalog from being capped at any one adapter's
|
|
17
|
+
* `models.allowed` list (SPEC §5.1);
|
|
18
|
+
* - the profile eligibility predicate shipped in #470
|
|
19
|
+
* (`@agentproto/auth`'s `eligibleProfiles`, `packages/auth/src/
|
|
20
|
+
* eligibility.ts:81-89`) for the profile-aware `runnable` flag (SPEC
|
|
21
|
+
* §5.3) — the old bare `hasKey` check (`packages/cli/src/commands/
|
|
22
|
+
* models.ts:113-117`) is the degenerate one-profile-per-provider case
|
|
23
|
+
* this predicate subsumes;
|
|
24
|
+
* - `AdapterAuthDescriptor` (`spawn-defaults.ts:226`), the SAME
|
|
25
|
+
* provider/authSubscription projection `resolveAuthSpec` reads, as the
|
|
26
|
+
* source for which auth methods an adapter can present on its direct
|
|
27
|
+
* route (SPEC §3.4's derivable replacement for a hand-maintained
|
|
28
|
+
* `authSubscription` boolean).
|
|
29
|
+
*
|
|
30
|
+
* A gateway/router route (anything where the resolved route differs from
|
|
31
|
+
* the model's vendor — `openrouter`, `requesty`, `huggingface`, or an
|
|
32
|
+
* adapter's own gateway mode id like `moonshot`) always bills against the
|
|
33
|
+
* route's own id and is always reached with an api-key credential — never
|
|
34
|
+
* oauth-bearer, since no third-party gateway has an Anthropic-style
|
|
35
|
+
* subscription bearer path (SPEC §1c: "a moonshot profile, not the Claude
|
|
36
|
+
* sub"). That structural rule is what lets this module compute
|
|
37
|
+
* `runnable`/`eligibleProfiles` for the widened, non-curated rows without
|
|
38
|
+
* per-adapter gateway-vendor tables.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/** One model entry as declared in an adapter's `models.allowed`
|
|
42
|
+
* (`AdapterModelInfo`, `packages/cli/src/registry/resolve.ts:134-142`) —
|
|
43
|
+
* the subset this module needs. */
|
|
44
|
+
interface CatalogAdapterModelInput {
|
|
45
|
+
/** Model id exactly as declared — bare (`"claude-opus-4-8"`) or
|
|
46
|
+
* `vendor/product` form. */
|
|
47
|
+
id: string;
|
|
48
|
+
/** The billing provider/route this model entry reaches on (`
|
|
49
|
+
* AdapterModelInfo.provider`) — e.g. `"moonshot"` or `"openrouter"`.
|
|
50
|
+
* Takes precedence over the id's own implied vendor route. */
|
|
51
|
+
provider?: string;
|
|
52
|
+
/** The adapter mode id that must be applied to reach this model on a
|
|
53
|
+
* non-direct route (`AdapterModelInfo.mode`) — e.g. `"moonshot"`.
|
|
54
|
+
* Undefined ⇒ direct route (the model's own vendor, or `provider`). */
|
|
55
|
+
mode?: string;
|
|
56
|
+
}
|
|
57
|
+
/** One installed adapter's contribution to the catalog. */
|
|
58
|
+
interface CatalogAdapterInput {
|
|
59
|
+
slug: string;
|
|
60
|
+
models: readonly CatalogAdapterModelInput[];
|
|
61
|
+
/** This adapter's billing-auth capability on its DIRECT route — the same
|
|
62
|
+
* projection `resolveAuthSpec` reads (`spawn-defaults.ts:226`). Omitted
|
|
63
|
+
* ⇒ the adapter presents no auth method, so rows it curates are
|
|
64
|
+
* discoverable but never runnable through it alone. */
|
|
65
|
+
authDescriptor?: AdapterAuthDescriptor;
|
|
66
|
+
/** How this adapter's spawn ROUTE relates to the chosen model (AIP-45
|
|
67
|
+
* launch-menu drill-down). `"free"` = the adapter can route arbitrary
|
|
68
|
+
* models through gateways (`base_url`). `"derived-from-model"` = the
|
|
69
|
+
* endpoint falls out of the model id's vendor prefix. Absent/undefined
|
|
70
|
+
* ⇒ fixed single-provider adapter; widened gateway routes are not
|
|
71
|
+
* attached to it. */
|
|
72
|
+
routeSelection?: "free" | "derived-from-model";
|
|
73
|
+
}
|
|
74
|
+
interface CatalogModelsQuery {
|
|
75
|
+
/** Keep only routes reachable via this adapter slug. */
|
|
76
|
+
adapter?: string;
|
|
77
|
+
/** Keep only this vendor's entry. */
|
|
78
|
+
vendor?: string;
|
|
79
|
+
/** Keep only routes with this route id. */
|
|
80
|
+
route?: string;
|
|
81
|
+
/** Drop every route with `runnable: false`. */
|
|
82
|
+
runnableOnly?: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface CatalogPricing {
|
|
85
|
+
inPer1M: number;
|
|
86
|
+
outPer1M: number;
|
|
87
|
+
}
|
|
88
|
+
interface CatalogRoute {
|
|
89
|
+
route: string;
|
|
90
|
+
ref: string;
|
|
91
|
+
baseUrl: string | null;
|
|
92
|
+
pricing: CatalogPricing | null;
|
|
93
|
+
runnable: boolean;
|
|
94
|
+
eligibleProfiles: string[];
|
|
95
|
+
adapterModes: string[];
|
|
96
|
+
adapters: string[];
|
|
97
|
+
curated: boolean;
|
|
98
|
+
/**
|
|
99
|
+
* True when MORE THAN ONE distinct model is servable on this route across
|
|
100
|
+
* the whole catalog join (AIP-45 launch-menu drill-down, SPEC §3/§5.3).
|
|
101
|
+
* Derived from the model×route join, never a hand-maintained table:
|
|
102
|
+
* `anthropic`/`openrouter`/`llm-endpoint` serve many models (`true`),
|
|
103
|
+
* a single-model gateway like `moonshot` serves one (`false`). A
|
|
104
|
+
* single-model route pins every model tier to its one model downstream,
|
|
105
|
+
* and gates the custom-gateway A-vs-B promotion (SPEC D5). Independent of
|
|
106
|
+
* the caller's query filters — this is the route's intrinsic capacity, so
|
|
107
|
+
* the same route reports the same `multiModel` in a filtered response.
|
|
108
|
+
*/
|
|
109
|
+
multiModel: boolean;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* One route's servable-model index (SPEC §3.9) — the flat, per-route view
|
|
113
|
+
* that lets a capability-derived UI derive tier pinning without re-walking
|
|
114
|
+
* the vendor/product tree. `servableModels` is the set of `vendor/product`
|
|
115
|
+
* model identities reachable on this route across the whole join;
|
|
116
|
+
* `multiModel` is `servableModels.length > 1` (the same value carried on
|
|
117
|
+
* every {@link CatalogRoute} with this id). Always the full, unfiltered
|
|
118
|
+
* catalog capacity, independent of the query — a route's model-count is
|
|
119
|
+
* intrinsic, not a view of the filtered result.
|
|
120
|
+
*/
|
|
121
|
+
interface CatalogRouteSummary {
|
|
122
|
+
route: string;
|
|
123
|
+
servableModels: string[];
|
|
124
|
+
multiModel: boolean;
|
|
125
|
+
}
|
|
126
|
+
interface CatalogProduct {
|
|
127
|
+
product: string;
|
|
128
|
+
routes: CatalogRoute[];
|
|
129
|
+
}
|
|
130
|
+
interface CatalogVendor {
|
|
131
|
+
vendor: string;
|
|
132
|
+
products: CatalogProduct[];
|
|
133
|
+
}
|
|
134
|
+
interface CatalogModelsResponse {
|
|
135
|
+
vendors: CatalogVendor[];
|
|
136
|
+
/**
|
|
137
|
+
* Flat servable-models-per-route index (SPEC §3.9), one entry per distinct
|
|
138
|
+
* route id in the catalog, sorted by route. Exposes the same join the
|
|
139
|
+
* nested `multiModel` flags derive from, so a capability layer can compute
|
|
140
|
+
* tier pinning per route without re-walking the vendor tree. Always the
|
|
141
|
+
* full, unfiltered catalog capacity (see {@link CatalogRouteSummary}).
|
|
142
|
+
*/
|
|
143
|
+
routes: CatalogRouteSummary[];
|
|
144
|
+
}
|
|
145
|
+
interface BuildCatalogModelsInput {
|
|
146
|
+
adapters: readonly CatalogAdapterInput[];
|
|
147
|
+
profiles: readonly AuthProfile[];
|
|
148
|
+
query?: CatalogModelsQuery;
|
|
149
|
+
}
|
|
150
|
+
/** The pure join (SPEC §5): adapter-declared models + router widening +
|
|
151
|
+
* the #470 eligibility predicate → the vendor/product/route tree. No I/O —
|
|
152
|
+
* callers (the HTTP route / MCP tool) own loading adapters + profiles. */
|
|
153
|
+
declare function buildCatalogModels(input: BuildCatalogModelsInput): CatalogModelsResponse;
|
|
154
|
+
/**
|
|
155
|
+
* Resolve the effective billing route for a session: a parseable model ref's
|
|
156
|
+
* own route is authoritative, `routeGateway` is only consulted when the model
|
|
157
|
+
* carries no explicit `@route` or is unparseable (including router-prefixed ids
|
|
158
|
+
* like `openrouter/vendor/product`, which are normalized to the canonical
|
|
159
|
+
* suffix form first).
|
|
160
|
+
*/
|
|
161
|
+
declare function resolveEffectiveRoute(model: string | undefined, routeGateway: string | undefined): string | undefined;
|
|
162
|
+
/** Rewrite `model`'s `@route` suffix to `route`. Bare/unparseable ids are
|
|
163
|
+
* returned unchanged because they have no suffix to rewrite. */
|
|
164
|
+
declare function modelWithRoute(model: string, route: string): string;
|
|
165
|
+
/**
|
|
166
|
+
* Reconcile independent `model` and `route` overrides so they never describe
|
|
167
|
+
* two different billing endpoints. Returns the effective values; mutating
|
|
168
|
+
* callers replace their effective model/route with these results.
|
|
169
|
+
*
|
|
170
|
+
* Rules:
|
|
171
|
+
* - explicit `@route` on the model wins over a stale/conflicting route.gateway;
|
|
172
|
+
* - a route-only override rewrites a parseable model string to match;
|
|
173
|
+
* - a model-only override synthesizes a route field whenever the model's
|
|
174
|
+
* resolved route differs from the previous route.gateway;
|
|
175
|
+
* - passing BOTH overrides that contradict each other is a caller bug → throw.
|
|
176
|
+
*/
|
|
177
|
+
declare function reconcileModelRoute(input: {
|
|
178
|
+
prevModel?: string;
|
|
179
|
+
prevRoute?: RouteSpec;
|
|
180
|
+
model?: string;
|
|
181
|
+
route?: RouteSpec;
|
|
182
|
+
}): {
|
|
183
|
+
model?: string;
|
|
184
|
+
route?: RouteSpec;
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* The billing routes that can service `model`, reusing the SAME route-identity
|
|
188
|
+
* resolution this module's catalog join uses (SPEC §1c serviceability) — never
|
|
189
|
+
* a parallel per-model provider table. A route R services the model iff:
|
|
190
|
+
* - R is the model's catalog billing provider (`getModelProvider` — the native
|
|
191
|
+
* vendor SDK for a bare/direct id, or the router for a gateway-only slash id
|
|
192
|
+
* like `deepseek/deepseek-v4-pro` ⇒ `openrouter`),
|
|
193
|
+
* - R is an explicit `@route` carried in the id (`…@openrouter`), or
|
|
194
|
+
* - the model resolves on R as one of the {@link WIDENING_ROUTES} router routes
|
|
195
|
+
* (catches requesty-only ids like `sference/…` that carry no pricing-catalog
|
|
196
|
+
* `provider` for `getModelProvider` to return).
|
|
197
|
+
*
|
|
198
|
+
* Returns the DISTINCT serviceable routes; EMPTY when the model is unknown to
|
|
199
|
+
* the catalog — a mismatch cannot then be positively proven, so a money-safety
|
|
200
|
+
* caller MUST NOT reject on an empty result (only on a non-empty set that
|
|
201
|
+
* excludes the resolved wallet). See {@link checkModelWalletEligibility}.
|
|
202
|
+
*/
|
|
203
|
+
declare function serviceableModelRoutes(model: string): string[];
|
|
204
|
+
/**
|
|
205
|
+
* Closest known catalog model ids for a slug UNKNOWN to the local catalog —
|
|
206
|
+
* the "did you mean" set behind the spawn-time model advisory (session-spawn.ts).
|
|
207
|
+
*
|
|
208
|
+
* Deliberately narrow to keep false positives near zero: a known id qualifies
|
|
209
|
+
* ONLY when its {@link bareProduct} equals the input's while the full id
|
|
210
|
+
* differs — i.e. the caller used the wrong-or-missing vendor/route prefix
|
|
211
|
+
* (`deepseek-chat` → `deepseek/deepseek-chat`, `glm-5.2` → `z-ai/glm-5.2`,
|
|
212
|
+
* `moonshot/kimi-k2` → `moonshotai/kimi-k2`). A genuinely-new model on a known
|
|
213
|
+
* vendor (`qwen/qwen4-max` when only `qwen3-max` is catalogued) shares no bare
|
|
214
|
+
* product and yields nothing — so a new / free-form slug never earns a spurious
|
|
215
|
+
* suggestion, matching the money-safety guard's own never-reject-an-unknown-
|
|
216
|
+
* model rule. Sourced from the catalog itself (`LLM_PRICING_CATALOG` +
|
|
217
|
+
* `MODEL_ALIASES` keys), never a hand-maintained second list.
|
|
218
|
+
*/
|
|
219
|
+
declare function suggestModelSlugs(model: string): string[];
|
|
220
|
+
/** Verdict of the spawn-time money-safety guard ({@link checkModelWalletEligibility}). */
|
|
221
|
+
interface ModelWalletEligibility {
|
|
222
|
+
ok: boolean;
|
|
223
|
+
/** Serviceable routes for the model that DIFFER from the resolved wallet —
|
|
224
|
+
* the actionable set to re-spawn onto. Empty when `ok`. */
|
|
225
|
+
suggestedRoutes: string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Money-safety spawn guard (SPEC §1c): can the resolved billing wallet
|
|
229
|
+
* `walletRoute` (the gateway id when a `route.gateway` is set, else the resolved
|
|
230
|
+
* billing provider) service `model`? Reuses {@link serviceableModelRoutes} — no
|
|
231
|
+
* parallel table. Returns `ok:true` when the model is serviceable on the wallet,
|
|
232
|
+
* OR when the model is unknown to the catalog (empty serviceable set — a mismatch
|
|
233
|
+
* cannot be positively proven, so the guard must not reject a possibly-legitimate
|
|
234
|
+
* new model). Returns `ok:false` with the serviceable alternative routes ONLY
|
|
235
|
+
* when the model IS serviceable on some route but NOT the resolved wallet — the
|
|
236
|
+
* exact 404-upstream case (`deepseek/deepseek-v4-pro` on the Anthropic sub). The
|
|
237
|
+
* guard only REJECTS; it never substitutes a wallet the operator didn't name.
|
|
238
|
+
*/
|
|
239
|
+
declare function checkModelWalletEligibility(model: string, walletRoute: string): ModelWalletEligibility;
|
|
240
|
+
/**
|
|
241
|
+
* The actionable fail-fast message shared by both spawn paths (session-spawn +
|
|
242
|
+
* session-restart-core) so they never drift. Names the wallet that couldn't
|
|
243
|
+
* service the model AND the required route + api-key profile to re-spawn onto —
|
|
244
|
+
* never a wallet the guard picked for the operator.
|
|
245
|
+
*/
|
|
246
|
+
declare function modelWalletIneligibleMessage(opts: {
|
|
247
|
+
prefix: string;
|
|
248
|
+
adapter: string;
|
|
249
|
+
model: string;
|
|
250
|
+
walletRoute: string;
|
|
251
|
+
walletMode?: "subscription" | "api-key";
|
|
252
|
+
suggestedRoutes: string[];
|
|
253
|
+
}): string;
|
|
254
|
+
|
|
255
|
+
export { type BuildCatalogModelsInput, type CatalogAdapterInput, type CatalogAdapterModelInput, type CatalogModelsQuery, type CatalogModelsResponse, type CatalogPricing, type CatalogProduct, type CatalogRoute, type CatalogRouteSummary, type CatalogVendor, type ModelWalletEligibility, RouteSpec, buildCatalogModels, checkModelWalletEligibility, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { eligibleProfiles } from '@agentproto/auth';
|
|
2
|
+
import { tryParseModelRef, formatModelRef, resolveLlmModelRoute } from '@agentproto/model-catalog/route-identity';
|
|
3
|
+
import { getModelProvider, resolvePricingExact, LLM_PRICING_CATALOG, MODEL_ALIASES } from '@agentproto/model-catalog/llm';
|
|
4
|
+
import { getAnthropicGatewayPreset } from '@agentproto/provider-presets';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @agentproto/runtime v0.1.0-alpha
|
|
8
|
+
* Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
var WIDENING_ROUTES = ["openrouter", "requesty", "huggingface"];
|
|
12
|
+
var VENDOR_COMPATIBILITY_ROUTES = {
|
|
13
|
+
xai: ["xai", "xai-anthropic"]
|
|
14
|
+
};
|
|
15
|
+
function normalizeRouterPrefixedId(id) {
|
|
16
|
+
const firstSlash = id.indexOf("/");
|
|
17
|
+
if (firstSlash === -1) return id;
|
|
18
|
+
const head = id.slice(0, firstSlash);
|
|
19
|
+
if (!WIDENING_ROUTES.includes(head)) return id;
|
|
20
|
+
const remainder = id.slice(firstSlash + 1);
|
|
21
|
+
if (!remainder.includes("/") || remainder.includes("@")) return id;
|
|
22
|
+
return `${remainder}@${head}`;
|
|
23
|
+
}
|
|
24
|
+
function tryResolveLlmModelRoute(id) {
|
|
25
|
+
try {
|
|
26
|
+
return resolveLlmModelRoute(id);
|
|
27
|
+
} catch {
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function vendorFromIdPrefix(bareId) {
|
|
32
|
+
if (/^claude[-/]/.test(bareId)) return "anthropic";
|
|
33
|
+
if (/^(gpt[-/]|o[1-9](-|$)|chatgpt)/.test(bareId)) return "openai";
|
|
34
|
+
if (/^gemini[-/]/.test(bareId)) return "google";
|
|
35
|
+
if (/^grok[-/]/.test(bareId)) return "x-ai";
|
|
36
|
+
if (/^deepseek[-/]/.test(bareId)) return "deepseek";
|
|
37
|
+
return void 0;
|
|
38
|
+
}
|
|
39
|
+
function resolveModelId(id) {
|
|
40
|
+
const normalized = normalizeRouterPrefixedId(id);
|
|
41
|
+
const resolved = tryResolveLlmModelRoute(normalized);
|
|
42
|
+
if (resolved) {
|
|
43
|
+
return {
|
|
44
|
+
vendor: resolved.vendor,
|
|
45
|
+
product: resolved.product,
|
|
46
|
+
directRoute: resolved.route,
|
|
47
|
+
ref: formatModelRef(resolved.ref),
|
|
48
|
+
baseUrl: resolved.transport.baseUrl ?? null,
|
|
49
|
+
pricing: {
|
|
50
|
+
inPer1M: resolved.pricing.inputPer1M,
|
|
51
|
+
outPer1M: resolved.pricing.outputPer1M
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const parsed = tryParseModelRef(normalized);
|
|
56
|
+
if (parsed) {
|
|
57
|
+
return {
|
|
58
|
+
vendor: parsed.vendor,
|
|
59
|
+
product: parsed.product,
|
|
60
|
+
directRoute: parsed.route,
|
|
61
|
+
ref: formatModelRef(parsed),
|
|
62
|
+
baseUrl: null,
|
|
63
|
+
pricing: null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const vendor = vendorFromIdPrefix(id) ?? "unknown";
|
|
67
|
+
return {
|
|
68
|
+
vendor,
|
|
69
|
+
product: id,
|
|
70
|
+
directRoute: vendor,
|
|
71
|
+
ref: `${vendor}/${id}`,
|
|
72
|
+
baseUrl: null,
|
|
73
|
+
pricing: null
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function methodsForDirect(descriptor) {
|
|
77
|
+
const methods = [];
|
|
78
|
+
if (descriptor?.authSubscription) methods.push("oauth-bearer");
|
|
79
|
+
if (descriptor?.provider || descriptor?.modelDerivedApiKey) methods.push("api-key");
|
|
80
|
+
return methods;
|
|
81
|
+
}
|
|
82
|
+
function isDirectRoute(route, mode, resolved) {
|
|
83
|
+
return mode === void 0 && route === resolved.vendor && resolved.directRoute === resolved.vendor;
|
|
84
|
+
}
|
|
85
|
+
function curatedContributions(adapters) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const adapter of adapters) {
|
|
88
|
+
for (const model of adapter.models) {
|
|
89
|
+
const resolved = resolveModelId(model.id);
|
|
90
|
+
const route = model.mode ?? model.provider ?? resolved.directRoute;
|
|
91
|
+
const methods = isDirectRoute(route, model.mode, resolved) ? methodsForDirect(adapter.authDescriptor) : ["api-key"];
|
|
92
|
+
out.push({
|
|
93
|
+
vendor: resolved.vendor,
|
|
94
|
+
product: resolved.product,
|
|
95
|
+
route,
|
|
96
|
+
ref: resolved.ref,
|
|
97
|
+
baseUrl: resolved.baseUrl,
|
|
98
|
+
pricing: resolved.pricing,
|
|
99
|
+
curated: true,
|
|
100
|
+
adapterSlug: adapter.slug,
|
|
101
|
+
...model.mode ? { adapterMode: model.mode } : {},
|
|
102
|
+
methods
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function widenedContributions(adapters, curated) {
|
|
109
|
+
const freeAdapters = adapters.filter((a) => a.routeSelection === "free").map((a) => a.slug);
|
|
110
|
+
if (freeAdapters.length === 0) return [];
|
|
111
|
+
const seenProducts = /* @__PURE__ */ new Map();
|
|
112
|
+
for (const c of curated) {
|
|
113
|
+
const key = `${c.vendor}/${c.product}`;
|
|
114
|
+
const routes = seenProducts.get(key) ?? /* @__PURE__ */ new Set();
|
|
115
|
+
routes.add(c.route);
|
|
116
|
+
seenProducts.set(key, routes);
|
|
117
|
+
}
|
|
118
|
+
const out = [];
|
|
119
|
+
for (const [key, existingRoutes] of seenProducts) {
|
|
120
|
+
const [vendor, product] = key.split("/", 2);
|
|
121
|
+
for (const router of WIDENING_ROUTES) {
|
|
122
|
+
if (existingRoutes.has(router)) continue;
|
|
123
|
+
const resolved = resolveLlmModelRoute(`${vendor}/${product}@${router}`);
|
|
124
|
+
if (!resolved) continue;
|
|
125
|
+
for (const adapterSlug of freeAdapters) {
|
|
126
|
+
out.push({
|
|
127
|
+
vendor,
|
|
128
|
+
product,
|
|
129
|
+
route: router,
|
|
130
|
+
ref: formatModelRef(resolved.ref),
|
|
131
|
+
baseUrl: resolved.transport.baseUrl ?? null,
|
|
132
|
+
pricing: {
|
|
133
|
+
inPer1M: resolved.pricing.inputPer1M,
|
|
134
|
+
outPer1M: resolved.pricing.outputPer1M
|
|
135
|
+
},
|
|
136
|
+
curated: false,
|
|
137
|
+
adapterSlug,
|
|
138
|
+
methods: ["api-key"]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
function compatibilityContributions(adapters) {
|
|
146
|
+
const freeAdapters = adapters.filter((a) => a.routeSelection === "free").map((a) => a.slug);
|
|
147
|
+
const out = [];
|
|
148
|
+
for (const [product, pricing] of Object.entries(LLM_PRICING_CATALOG)) {
|
|
149
|
+
if (pricing.provider !== "xai") continue;
|
|
150
|
+
const vendor = pricing.vendor ?? "xai";
|
|
151
|
+
const compatRoutes = VENDOR_COMPATIBILITY_ROUTES[vendor];
|
|
152
|
+
if (!compatRoutes) continue;
|
|
153
|
+
const direct = resolveLlmModelRoute(`${vendor}/${product}`);
|
|
154
|
+
if (!direct) continue;
|
|
155
|
+
for (const route of compatRoutes) {
|
|
156
|
+
const ref = route === vendor ? `${vendor}/${product}` : `${vendor}/${product}@${route}`;
|
|
157
|
+
const baseUrl = route === "xai-anthropic" ? getAnthropicGatewayPreset("xai-anthropic").baseUrl : null;
|
|
158
|
+
const rowBase = {
|
|
159
|
+
vendor,
|
|
160
|
+
product,
|
|
161
|
+
route,
|
|
162
|
+
ref,
|
|
163
|
+
baseUrl,
|
|
164
|
+
pricing: {
|
|
165
|
+
inPer1M: direct.pricing.inputPer1M,
|
|
166
|
+
outPer1M: direct.pricing.outputPer1M
|
|
167
|
+
},
|
|
168
|
+
curated: false,
|
|
169
|
+
methods: ["api-key"]
|
|
170
|
+
};
|
|
171
|
+
if (route === "xai-anthropic" && freeAdapters.length > 0) {
|
|
172
|
+
for (const adapterSlug of freeAdapters) {
|
|
173
|
+
out.push({ ...rowBase, adapterSlug });
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
out.push(rowBase);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
function mergeContributions(contributions) {
|
|
183
|
+
const rows = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const c of contributions) {
|
|
185
|
+
const key = `${c.vendor}\0${c.product}\0${c.route}`;
|
|
186
|
+
const existing = rows.get(key);
|
|
187
|
+
if (!existing) {
|
|
188
|
+
rows.set(key, {
|
|
189
|
+
vendor: c.vendor,
|
|
190
|
+
product: c.product,
|
|
191
|
+
route: c.route,
|
|
192
|
+
ref: c.ref,
|
|
193
|
+
baseUrl: c.baseUrl,
|
|
194
|
+
pricing: c.pricing,
|
|
195
|
+
curated: c.curated,
|
|
196
|
+
adapters: c.adapterSlug ? [c.adapterSlug] : [],
|
|
197
|
+
adapterModes: c.adapterMode ? [c.adapterMode] : [],
|
|
198
|
+
methods: [...c.methods]
|
|
199
|
+
});
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
existing.curated = existing.curated || c.curated;
|
|
203
|
+
existing.baseUrl = existing.baseUrl ?? c.baseUrl;
|
|
204
|
+
existing.pricing = existing.pricing ?? c.pricing;
|
|
205
|
+
if (c.adapterSlug && !existing.adapters.includes(c.adapterSlug)) {
|
|
206
|
+
existing.adapters.push(c.adapterSlug);
|
|
207
|
+
}
|
|
208
|
+
if (c.adapterMode && !existing.adapterModes.includes(c.adapterMode)) {
|
|
209
|
+
existing.adapterModes.push(c.adapterMode);
|
|
210
|
+
}
|
|
211
|
+
for (const m of c.methods) {
|
|
212
|
+
if (!existing.methods.includes(m)) existing.methods.push(m);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return [...rows.values()];
|
|
216
|
+
}
|
|
217
|
+
function billedVendor(vendor, route) {
|
|
218
|
+
return route === vendor ? vendor : route;
|
|
219
|
+
}
|
|
220
|
+
function profileAllowsModel(profile, ref, vendorProduct) {
|
|
221
|
+
const curation = profile.models;
|
|
222
|
+
if (!curation || curation.mode === "all") return true;
|
|
223
|
+
const slash = vendorProduct.indexOf("/");
|
|
224
|
+
const product = slash === -1 ? vendorProduct : vendorProduct.slice(slash + 1);
|
|
225
|
+
const isDirect = !ref.includes("@");
|
|
226
|
+
return curation.ids.includes(ref) || curation.ids.includes(vendorProduct) || isDirect && curation.ids.includes(product);
|
|
227
|
+
}
|
|
228
|
+
function buildCatalogModels(input) {
|
|
229
|
+
const curated = curatedContributions(input.adapters);
|
|
230
|
+
const contributions = [
|
|
231
|
+
...curated,
|
|
232
|
+
...widenedContributions(input.adapters, curated),
|
|
233
|
+
...compatibilityContributions(input.adapters)
|
|
234
|
+
];
|
|
235
|
+
const merged = mergeContributions(contributions);
|
|
236
|
+
const query = input.query ?? {};
|
|
237
|
+
const servableByRoute = /* @__PURE__ */ new Map();
|
|
238
|
+
for (const row of merged) {
|
|
239
|
+
const set = servableByRoute.get(row.route) ?? /* @__PURE__ */ new Set();
|
|
240
|
+
set.add(`${row.vendor}/${row.product}`);
|
|
241
|
+
servableByRoute.set(row.route, set);
|
|
242
|
+
}
|
|
243
|
+
const isMultiModel = (route) => (servableByRoute.get(route)?.size ?? 0) > 1;
|
|
244
|
+
const vendors = /* @__PURE__ */ new Map();
|
|
245
|
+
for (const row of merged) {
|
|
246
|
+
if (query.vendor && row.vendor !== query.vendor) continue;
|
|
247
|
+
if (query.route && row.route !== query.route) continue;
|
|
248
|
+
if (query.adapter && !row.adapters.includes(query.adapter)) continue;
|
|
249
|
+
const isDirectVendorRoute = row.route === row.vendor && row.adapterModes.length === 0;
|
|
250
|
+
const walletEligible = !isDirectVendorRoute || checkModelWalletEligibility(`${row.vendor}/${row.product}`, row.route).ok;
|
|
251
|
+
const manifest = {
|
|
252
|
+
id: `${row.vendor}/${row.product}@${row.route}`,
|
|
253
|
+
endpointByRoute: { [row.route]: billedVendor(row.vendor, row.route) },
|
|
254
|
+
methodsByRoute: { [row.route]: row.methods }
|
|
255
|
+
};
|
|
256
|
+
const eligible = walletEligible ? eligibleProfiles(input.profiles, manifest, row.route).filter(
|
|
257
|
+
(p) => profileAllowsModel(p, row.ref, `${row.vendor}/${row.product}`)
|
|
258
|
+
) : [];
|
|
259
|
+
const runnable = eligible.length > 0;
|
|
260
|
+
if (query.runnableOnly && !runnable) continue;
|
|
261
|
+
const route = {
|
|
262
|
+
route: row.route,
|
|
263
|
+
ref: row.ref,
|
|
264
|
+
baseUrl: row.baseUrl,
|
|
265
|
+
pricing: row.pricing,
|
|
266
|
+
runnable,
|
|
267
|
+
eligibleProfiles: eligible.map((p) => p.id),
|
|
268
|
+
adapterModes: row.adapterModes,
|
|
269
|
+
adapters: row.adapters,
|
|
270
|
+
curated: row.curated,
|
|
271
|
+
multiModel: isMultiModel(row.route)
|
|
272
|
+
};
|
|
273
|
+
const products = vendors.get(row.vendor) ?? /* @__PURE__ */ new Map();
|
|
274
|
+
vendors.set(row.vendor, products);
|
|
275
|
+
const routes2 = products.get(row.product) ?? [];
|
|
276
|
+
products.set(row.product, routes2);
|
|
277
|
+
routes2.push(route);
|
|
278
|
+
}
|
|
279
|
+
const result = [...vendors.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([vendor, products]) => ({
|
|
280
|
+
vendor,
|
|
281
|
+
products: [...products.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([product, routes2]) => ({
|
|
282
|
+
product,
|
|
283
|
+
routes: [...routes2].sort((a, b) => a.route.localeCompare(b.route))
|
|
284
|
+
}))
|
|
285
|
+
}));
|
|
286
|
+
const routes = [...servableByRoute.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([route, models]) => ({
|
|
287
|
+
route,
|
|
288
|
+
servableModels: [...models].sort((a, b) => a.localeCompare(b)),
|
|
289
|
+
multiModel: models.size > 1
|
|
290
|
+
}));
|
|
291
|
+
return { vendors: result, routes };
|
|
292
|
+
}
|
|
293
|
+
function resolveEffectiveRoute(model, routeGateway) {
|
|
294
|
+
if (!model) return routeGateway;
|
|
295
|
+
const normalized = normalizeRouterPrefixedId(model);
|
|
296
|
+
const parsed = tryParseModelRef(normalized);
|
|
297
|
+
if (!parsed) return routeGateway;
|
|
298
|
+
if (parsed.route !== parsed.vendor) return parsed.route;
|
|
299
|
+
return routeGateway ?? parsed.route;
|
|
300
|
+
}
|
|
301
|
+
function modelWithRoute(model, route) {
|
|
302
|
+
const parsed = tryParseModelRef(model);
|
|
303
|
+
if (!parsed) return model;
|
|
304
|
+
return formatModelRef({ ...parsed, route });
|
|
305
|
+
}
|
|
306
|
+
function reconcileModelRoute(input) {
|
|
307
|
+
const { prevModel, prevRoute, model: overrideModel, route: overrideRoute } = input;
|
|
308
|
+
if (overrideModel !== void 0 && overrideRoute !== void 0) {
|
|
309
|
+
const parsed = tryParseModelRef(overrideModel);
|
|
310
|
+
if (parsed && parsed.route !== parsed.vendor && parsed.route !== overrideRoute.gateway) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`reconcileModelRoute: model "${overrideModel}" pins route "${parsed.route}" but route override is "${overrideRoute.gateway}"`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
return { model: overrideModel, route: overrideRoute };
|
|
316
|
+
}
|
|
317
|
+
if (overrideModel !== void 0) {
|
|
318
|
+
const parsed = tryParseModelRef(overrideModel);
|
|
319
|
+
if (parsed) {
|
|
320
|
+
const hasExplicitRoute = parsed.route !== parsed.vendor;
|
|
321
|
+
if (hasExplicitRoute && prevRoute?.gateway !== parsed.route) {
|
|
322
|
+
return { model: overrideModel, route: { gateway: parsed.route } };
|
|
323
|
+
}
|
|
324
|
+
if (!hasExplicitRoute && prevRoute && prevRoute.gateway !== parsed.route) {
|
|
325
|
+
return { model: overrideModel, route: { gateway: parsed.route } };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return { model: overrideModel, route: prevRoute };
|
|
329
|
+
}
|
|
330
|
+
if (overrideRoute !== void 0) {
|
|
331
|
+
if (prevModel !== void 0) {
|
|
332
|
+
return { model: modelWithRoute(prevModel, overrideRoute.gateway), route: overrideRoute };
|
|
333
|
+
}
|
|
334
|
+
return { route: overrideRoute };
|
|
335
|
+
}
|
|
336
|
+
return { model: prevModel, route: prevRoute };
|
|
337
|
+
}
|
|
338
|
+
function serviceableModelRoutes(model) {
|
|
339
|
+
const routes = /* @__PURE__ */ new Set();
|
|
340
|
+
const provider = getModelProvider(model);
|
|
341
|
+
if (provider) routes.add(provider);
|
|
342
|
+
const parsed = tryParseModelRef(normalizeRouterPrefixedId(model));
|
|
343
|
+
if (parsed) {
|
|
344
|
+
if (parsed.route !== parsed.vendor) routes.add(parsed.route);
|
|
345
|
+
if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {
|
|
346
|
+
routes.add(parsed.vendor);
|
|
347
|
+
}
|
|
348
|
+
for (const router of WIDENING_ROUTES) {
|
|
349
|
+
if (tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}@${router}`)) {
|
|
350
|
+
routes.add(router);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return [...routes];
|
|
355
|
+
}
|
|
356
|
+
function bareProduct(id) {
|
|
357
|
+
const noSuffix = id.split("@")[0].split(":")[0];
|
|
358
|
+
const slash = noSuffix.lastIndexOf("/");
|
|
359
|
+
return (slash === -1 ? noSuffix : noSuffix.slice(slash + 1)).toLowerCase();
|
|
360
|
+
}
|
|
361
|
+
function suggestModelSlugs(model) {
|
|
362
|
+
const target = bareProduct(model);
|
|
363
|
+
if (!target) return [];
|
|
364
|
+
const out = /* @__PURE__ */ new Set();
|
|
365
|
+
for (const id of [...Object.keys(LLM_PRICING_CATALOG), ...Object.keys(MODEL_ALIASES)]) {
|
|
366
|
+
if (id !== model && bareProduct(id) === target) out.add(id);
|
|
367
|
+
}
|
|
368
|
+
return [...out].sort().slice(0, 5);
|
|
369
|
+
}
|
|
370
|
+
function checkModelWalletEligibility(model, walletRoute) {
|
|
371
|
+
const serviceable = serviceableModelRoutes(model);
|
|
372
|
+
if (serviceable.length === 0 || serviceable.includes(walletRoute)) {
|
|
373
|
+
return { ok: true, suggestedRoutes: [] };
|
|
374
|
+
}
|
|
375
|
+
return { ok: false, suggestedRoutes: serviceable.filter((r) => r !== walletRoute) };
|
|
376
|
+
}
|
|
377
|
+
function modelWalletIneligibleMessage(opts) {
|
|
378
|
+
const wallet = opts.walletMode ? `"${opts.walletRoute}" ${opts.walletMode} wallet` : `"${opts.walletRoute}" wallet`;
|
|
379
|
+
const primary = opts.suggestedRoutes[0] ?? "a gateway route";
|
|
380
|
+
const also = opts.suggestedRoutes.length > 1 ? ` (also serviceable via ${opts.suggestedRoutes.slice(1).map((r) => `"${r}"`).join(", ")})` : "";
|
|
381
|
+
return `${opts.prefix}: model "${opts.model}" is not serviceable on the resolved ${wallet} (adapter "${opts.adapter}") and would 404 upstream. This model bills route "${primary}"${also} \u2014 re-spawn on it: set route.gateway="${primary}" with an eligible "${primary}" api-key profile (access.profileRef). This guard only rejects; it never switches wallets for you.`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export { buildCatalogModels, checkModelWalletEligibility, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
|
385
|
+
//# sourceMappingURL=catalog-models.mjs.map
|
|
386
|
+
//# sourceMappingURL=catalog-models.mjs.map
|