@agentproto/runtime 2.4.0 → 2.6.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/dist/catalog-models.d.ts +1 -1
- package/dist/catalog-models.mjs +1 -1
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/command-allowlist.d.ts +70 -0
- package/dist/command-allowlist.mjs +104 -0
- package/dist/command-allowlist.mjs.map +1 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.mjs.map +1 -1
- package/dist/index.d.ts +380 -7
- package/dist/index.mjs +3613 -1381
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +8 -0
- package/dist/pr-provenance.mjs +8 -2
- package/dist/pr-provenance.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +8 -0
- package/dist/resume-strategies.mjs +17 -3
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{spawn-defaults-7uHRnYH1.d.ts → spawn-defaults-CYJoeHeO.d.ts} +0 -13
- package/package.json +19 -13
package/dist/catalog-models.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthProfile } from '@agentproto/auth';
|
|
2
|
-
import { A as AdapterAuthDescriptor } from './spawn-defaults-
|
|
2
|
+
import { A as AdapterAuthDescriptor } from './spawn-defaults-CYJoeHeO.js';
|
|
3
3
|
import { R as RouteSpec } from './session-config-DIf6wYYP.js';
|
|
4
4
|
import '@agentproto/model-catalog';
|
|
5
5
|
import './context-continuity-B9n0t0v-.js';
|
package/dist/catalog-models.mjs
CHANGED
|
@@ -121,7 +121,7 @@ function widenedContributions(adapters, curated) {
|
|
|
121
121
|
const [vendor, product] = key.split("/", 2);
|
|
122
122
|
for (const router of WIDENING_ROUTES) {
|
|
123
123
|
if (existingRoutes.has(router)) continue;
|
|
124
|
-
const resolved =
|
|
124
|
+
const resolved = tryResolveLlmModelRoute(`${vendor}/${product}@${router}`);
|
|
125
125
|
if (!resolved) continue;
|
|
126
126
|
for (const adapterSlug of freeAdapters) {
|
|
127
127
|
out.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/catalog-models.ts"],"names":["routes"],"mappings":"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","file":"catalog-models.mjs","sourcesContent":["/**\n * Read-only catalog/vendor endpoint (`agentproto-session-config-axes`\n * SPEC §5) — `GET /catalog/models` + `catalog_models` MCP tool wire into\n * {@link buildCatalogModels}, the pure join this module owns.\n *\n * Reuses three already-shipped pieces instead of rebuilding them:\n * - the vendor/product/route model + router widening (OpenRouter/\n * Requesty/HuggingFace) from `@agentproto/model-catalog/route-identity`\n * (`resolveLlmModelRoute`, `route-identity/index.ts:396-511`) — this is\n * what keeps the catalog from being capped at any one adapter's\n * `models.allowed` list (SPEC §5.1);\n * - the profile eligibility predicate shipped in #470\n * (`@agentproto/auth`'s `eligibleProfiles`, `packages/auth/src/\n * eligibility.ts:81-89`) for the profile-aware `runnable` flag (SPEC\n * §5.3) — the old bare `hasKey` check (`packages/cli/src/commands/\n * models.ts:113-117`) is the degenerate one-profile-per-provider case\n * this predicate subsumes;\n * - `AdapterAuthDescriptor` (`spawn-defaults.ts:226`), the SAME\n * provider/authSubscription projection `resolveAuthSpec` reads, as the\n * source for which auth methods an adapter can present on its direct\n * route (SPEC §3.4's derivable replacement for a hand-maintained\n * `authSubscription` boolean).\n *\n * A gateway/router route (anything where the resolved route differs from\n * the model's vendor — `openrouter`, `requesty`, `huggingface`, or an\n * adapter's own gateway mode id like `moonshot`) always bills against the\n * route's own id and is always reached with an api-key credential — never\n * oauth-bearer, since no third-party gateway has an Anthropic-style\n * subscription bearer path (SPEC §1c: \"a moonshot profile, not the Claude\n * sub\"). That structural rule is what lets this module compute\n * `runnable`/`eligibleProfiles` for the widened, non-curated rows without\n * per-adapter gateway-vendor tables.\n */\n\nimport type { AuthMethod, AuthProfile } from \"@agentproto/auth\"\nimport { eligibleProfiles, type AdapterAuthManifest } from \"@agentproto/auth\"\nimport {\n resolveLlmModelRoute,\n tryParseModelRef,\n formatModelRef,\n} from \"@agentproto/model-catalog/route-identity\"\nimport {\n getModelProvider,\n resolvePricingExact,\n LLM_PRICING_CATALOG,\n MODEL_ALIASES,\n} from \"@agentproto/model-catalog/llm\"\nimport { getAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport type { AdapterAuthDescriptor } from \"./spawn-defaults.js\"\nimport type { RouteSpec } from \"./session-config.js\"\n\nexport type { RouteSpec } from \"./session-config.js\"\n\n/** Routers the catalog probes to widen beyond any adapter's declared model\n * list (SPEC §5.1) — same three route-identity widens, `route-identity/\n * index.ts:54-59`. */\nconst WIDENING_ROUTES = [\"openrouter\", \"requesty\", \"huggingface\"] as const\n\n/**\n * Vendor-specific compatibility routes: canonical protocol surfaces that can\n * bill the same model family without duplicating catalog entries. A model whose\n * pricing-catalog vendor is `xai` is legitimately reachable both on the native\n * `xai` API route and on the Anthropic-compatible `xai-anthropic` route; the\n * two routes are distinct billing endpoints so the right auth profile matches\n * the right protocol.\n */\nconst VENDOR_COMPATIBILITY_ROUTES: Readonly<Record<string, readonly string[]>> = {\n xai: [\"xai\", \"xai-anthropic\"],\n}\n\n/** One model entry as declared in an adapter's `models.allowed`\n * (`AdapterModelInfo`, `packages/cli/src/registry/resolve.ts:134-142`) —\n * the subset this module needs. */\nexport interface CatalogAdapterModelInput {\n /** Model id exactly as declared — bare (`\"claude-opus-4-8\"`) or\n * `vendor/product` form. */\n id: string\n /** The billing provider/route this model entry reaches on (`\n * AdapterModelInfo.provider`) — e.g. `\"moonshot\"` or `\"openrouter\"`.\n * Takes precedence over the id's own implied vendor route. */\n provider?: string\n /** The adapter mode id that must be applied to reach this model on a\n * non-direct route (`AdapterModelInfo.mode`) — e.g. `\"moonshot\"`.\n * Undefined ⇒ direct route (the model's own vendor, or `provider`). */\n mode?: string\n}\n\n/** One installed adapter's contribution to the catalog. */\nexport interface CatalogAdapterInput {\n slug: string\n models: readonly CatalogAdapterModelInput[]\n /** This adapter's billing-auth capability on its DIRECT route — the same\n * projection `resolveAuthSpec` reads (`spawn-defaults.ts:226`). Omitted\n * ⇒ the adapter presents no auth method, so rows it curates are\n * discoverable but never runnable through it alone. */\n authDescriptor?: AdapterAuthDescriptor\n /** How this adapter's spawn ROUTE relates to the chosen model (AIP-45\n * launch-menu drill-down). `\"free\"` = the adapter can route arbitrary\n * models through gateways (`base_url`). `\"derived-from-model\"` = the\n * endpoint falls out of the model id's vendor prefix. Absent/undefined\n * ⇒ fixed single-provider adapter; widened gateway routes are not\n * attached to it. */\n routeSelection?: \"free\" | \"derived-from-model\"\n}\n\nexport interface CatalogModelsQuery {\n /** Keep only routes reachable via this adapter slug. */\n adapter?: string\n /** Keep only this vendor's entry. */\n vendor?: string\n /** Keep only routes with this route id. */\n route?: string\n /** Drop every route with `runnable: false`. */\n runnableOnly?: boolean\n}\n\nexport interface CatalogPricing {\n inPer1M: number\n outPer1M: number\n}\n\nexport interface CatalogRoute {\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n runnable: boolean\n eligibleProfiles: string[]\n adapterModes: string[]\n adapters: string[]\n curated: boolean\n /**\n * True when MORE THAN ONE distinct model is servable on this route across\n * the whole catalog join (AIP-45 launch-menu drill-down, SPEC §3/§5.3).\n * Derived from the model×route join, never a hand-maintained table:\n * `anthropic`/`openrouter`/`llm-endpoint` serve many models (`true`),\n * a single-model gateway like `moonshot` serves one (`false`). A\n * single-model route pins every model tier to its one model downstream,\n * and gates the custom-gateway A-vs-B promotion (SPEC D5). Independent of\n * the caller's query filters — this is the route's intrinsic capacity, so\n * the same route reports the same `multiModel` in a filtered response.\n */\n multiModel: boolean\n}\n\n/**\n * One route's servable-model index (SPEC §3.9) — the flat, per-route view\n * that lets a capability-derived UI derive tier pinning without re-walking\n * the vendor/product tree. `servableModels` is the set of `vendor/product`\n * model identities reachable on this route across the whole join;\n * `multiModel` is `servableModels.length > 1` (the same value carried on\n * every {@link CatalogRoute} with this id). Always the full, unfiltered\n * catalog capacity, independent of the query — a route's model-count is\n * intrinsic, not a view of the filtered result.\n */\nexport interface CatalogRouteSummary {\n route: string\n servableModels: string[]\n multiModel: boolean\n}\n\nexport interface CatalogProduct {\n product: string\n routes: CatalogRoute[]\n}\n\nexport interface CatalogVendor {\n vendor: string\n products: CatalogProduct[]\n}\n\nexport interface CatalogModelsResponse {\n vendors: CatalogVendor[]\n /**\n * Flat servable-models-per-route index (SPEC §3.9), one entry per distinct\n * route id in the catalog, sorted by route. Exposes the same join the\n * nested `multiModel` flags derive from, so a capability layer can compute\n * tier pinning per route without re-walking the vendor tree. Always the\n * full, unfiltered catalog capacity (see {@link CatalogRouteSummary}).\n */\n routes: CatalogRouteSummary[]\n}\n\nexport interface BuildCatalogModelsInput {\n adapters: readonly CatalogAdapterInput[]\n profiles: readonly AuthProfile[]\n query?: CatalogModelsQuery\n}\n\n/** A resolved model identity + pricing, independent of which adapter (if\n * any) declared it. */\ninterface ResolvedModel {\n vendor: string\n product: string\n /** The route this id resolves to on its OWN — vendor for a direct id,\n * a router name when the id already carries `@route`. */\n directRoute: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n}\n\n/** Rewrite a router-prefixed id (`<router>/<vendor>/<product>`) into the\n * canonical route-identity `<vendor>/<product>@<router>` the parser accepts.\n *\n * Mastra-style adapters (e.g. `adapters/mastra-agent`) declare their model\n * ids in `<provider>/<upstream-id>` form, and for a gateway router the\n * upstream id is itself `<vendor>/<product>` — so a native OpenRouter id like\n * `z-ai/glm-5.2` is advertised as the 3-segment `openrouter/z-ai/glm-5.2`.\n * `parseModelRef` splits on the FIRST `/` and rejects a product that still\n * contains one (`route-identity/index.ts` SEGMENT_RE), so feeding it the raw\n * 3-segment string throws and, before this normalization, 500'd the whole\n * catalog. The route-identity grammar's canonical form for such a model is\n * `<vendor>/<product>@<router>` (`z-ai/glm-5.2@openrouter`) — the `@route`\n * suffix, NOT a leading route segment — so we recompose to that. Only the\n * known gateway routers (whose native ids are `<vendor>/<product>`) are\n * peeled; every other id is returned untouched. A `:pin` variant/provider\n * suffix on the upstream id is preserved (it rides along in the remainder).\n */\nfunction normalizeRouterPrefixedId(id: string): string {\n const firstSlash = id.indexOf(\"/\")\n if (firstSlash === -1) return id\n const head = id.slice(0, firstSlash)\n if (!(WIDENING_ROUTES as readonly string[]).includes(head)) return id\n const remainder = id.slice(firstSlash + 1)\n // Only a genuine `<vendor>/<product>` upstream id (still carrying a `/`) is\n // the router-prefixed shape; a 2-segment `<router>/<product>` is left alone.\n if (!remainder.includes(\"/\") || remainder.includes(\"@\")) return id\n return `${remainder}@${head}`\n}\n\n/** {@link resolveLlmModelRoute} that never throws. `resolveLlmModelRoute`\n * parses through the strict `parseModelRef`, which throws on a still-\n * unparseable ref (e.g. an unrecognised 3-segment id that normalization\n * didn't rewrite) — defense-in-depth so a single bad id can never 500 the\n * catalog. */\nfunction tryResolveLlmModelRoute(id: string): ReturnType<typeof resolveLlmModelRoute> {\n try {\n return resolveLlmModelRoute(id)\n } catch {\n return undefined\n }\n}\n\n/** Infer a vendor from a bare id family prefix — ports `providerFromIdPrefix`\n * (`packages/cli/src/commands/models.ts:47-54`) so a model id that predates\n * its pricing-catalog entry still gets a vendor instead of `\"unknown\"`. */\nfunction vendorFromIdPrefix(bareId: string): string | undefined {\n if (/^claude[-/]/.test(bareId)) return \"anthropic\"\n if (/^(gpt[-/]|o[1-9](-|$)|chatgpt)/.test(bareId)) return \"openai\"\n if (/^gemini[-/]/.test(bareId)) return \"google\"\n if (/^grok[-/]/.test(bareId)) return \"x-ai\"\n if (/^deepseek[-/]/.test(bareId)) return \"deepseek\"\n return undefined\n}\n\n/** Resolve a model id to its vendor/product/route/pricing, in three tiers:\n * the route-identity resolver (handles both `vendor/product[@route]` and\n * legacy bare ids with pricing), then a bare vendor/product parse with no\n * pricing, then the id-prefix heuristic as a last resort. Never throws —\n * every id gets SOME vendor rather than being dropped from the catalog. */\nfunction resolveModelId(id: string): ResolvedModel {\n // A router-prefixed Mastra id (`openrouter/z-ai/glm-5.2`) becomes its\n // canonical route-identity (`z-ai/glm-5.2@openrouter`) BEFORE any parse, so\n // it resolves to a real vendor/product/route row instead of throwing.\n const normalized = normalizeRouterPrefixedId(id)\n const resolved = tryResolveLlmModelRoute(normalized)\n if (resolved) {\n return {\n vendor: resolved.vendor,\n product: resolved.product,\n directRoute: resolved.route,\n ref: formatModelRef(resolved.ref),\n baseUrl: resolved.transport.baseUrl ?? null,\n pricing: {\n inPer1M: resolved.pricing.inputPer1M,\n outPer1M: resolved.pricing.outputPer1M,\n },\n }\n }\n const parsed = tryParseModelRef(normalized)\n if (parsed) {\n return {\n vendor: parsed.vendor,\n product: parsed.product,\n directRoute: parsed.route,\n ref: formatModelRef(parsed),\n baseUrl: null,\n pricing: null,\n }\n }\n const vendor = vendorFromIdPrefix(id) ?? \"unknown\"\n return {\n vendor,\n product: id,\n directRoute: vendor,\n ref: `${vendor}/${id}`,\n baseUrl: null,\n pricing: null,\n }\n}\n\n/** Which auth methods are presentable on a model's DIRECT route — whatever\n * the adapter's descriptor declares, derivable from `authSubscription`/\n * `provider` exactly as SPEC §3.4 calls for. Only called for a genuinely\n * direct route (see `isDirectRoute`); a gateway/router route is always\n * api-key only (SPEC §1c — no third-party gateway has an oauth-bearer\n * path), regardless of what the underlying model's own vendor is. */\nfunction methodsForDirect(\n descriptor: AdapterAuthDescriptor | undefined,\n): AuthMethod[] {\n const methods: AuthMethod[] = []\n if (descriptor?.authSubscription || descriptor?.modelDerivedApiKey)\n methods.push(\"oauth-bearer\")\n if (descriptor?.provider || descriptor?.modelDerivedApiKey) methods.push(\"api-key\")\n return methods\n}\n\n/** True iff this model entry resolves to its OWN vendor with no adapter\n * mode override — i.e. reached without any gateway/router redirection.\n * An explicit `model.mode` (adapter gateway mode, e.g. `\"moonshot\"`) is\n * ALWAYS a redirection even when the mode id happens to equal the\n * model's resolved vendor (e.g. a `moonshot/kimi-…` model routed via\n * claude-code's `moonshot` mode) — SPEC §1c's \"moonshot profile, not the\n * Claude sub\" holds regardless of whose model is being served. An id\n * that already carries its own `@route` suffix (`resolved.directRoute !==\n * resolved.vendor`) is equally a router path even with no adapter mode.\n * A `model.provider` that matches the model's own vendor is still a\n * direct route; only a provider/route that differs is a redirection. */\nfunction isDirectRoute(route: string, mode: string | undefined, resolved: ResolvedModel): boolean {\n return mode === undefined && route === resolved.vendor && resolved.directRoute === resolved.vendor\n}\n\ninterface RouteContribution {\n vendor: string\n product: string\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n curated: boolean\n adapterSlug?: string\n adapterMode?: string\n methods: readonly AuthMethod[]\n}\n\n/** Curated contributions — one per adapter-declared model entry. */\nfunction curatedContributions(\n adapters: readonly CatalogAdapterInput[],\n): RouteContribution[] {\n const out: RouteContribution[] = []\n for (const adapter of adapters) {\n for (const model of adapter.models) {\n const resolved = resolveModelId(model.id)\n const route = model.mode ?? model.provider ?? resolved.directRoute\n const methods: AuthMethod[] = isDirectRoute(route, model.mode, resolved)\n ? methodsForDirect(adapter.authDescriptor)\n : [\"api-key\"]\n out.push({\n vendor: resolved.vendor,\n product: resolved.product,\n route,\n ref: resolved.ref,\n baseUrl: resolved.baseUrl,\n pricing: resolved.pricing,\n curated: true,\n adapterSlug: adapter.slug,\n ...(model.mode ? { adapterMode: model.mode } : {}),\n methods,\n })\n }\n }\n return out\n}\n\n/** Widen beyond every adapter's declared list (SPEC §5.1): for each\n * distinct (vendor, product) already known from a curated contribution,\n * probe the router routes route-identity knows about and add a\n * non-curated contribution for any that resolve and aren't already\n * covered by a curated row.\n *\n * Widened routes are only attached to adapters that explicitly declare\n * `routeSelection: \"free\"` — those are the adapters that can route an\n * arbitrary model through a gateway via `base_url`. Fixed-provider and\n * `derived-from-model` adapters must not be advertised on routes they\n * cannot truthfully choose. */\nfunction widenedContributions(\n adapters: readonly CatalogAdapterInput[],\n curated: readonly RouteContribution[],\n): RouteContribution[] {\n const freeAdapters = adapters\n .filter(a => a.routeSelection === \"free\")\n .map(a => a.slug)\n if (freeAdapters.length === 0) return []\n\n const seenProducts = new Map<string, Set<string>>() // \"vendor/product\" -> routes already present\n for (const c of curated) {\n const key = `${c.vendor}/${c.product}`\n const routes = seenProducts.get(key) ?? new Set<string>()\n routes.add(c.route)\n seenProducts.set(key, routes)\n }\n\n const out: RouteContribution[] = []\n for (const [key, existingRoutes] of seenProducts) {\n const [vendor, product] = key.split(\"/\", 2) as [string, string]\n for (const router of WIDENING_ROUTES) {\n if (existingRoutes.has(router)) continue\n const resolved = resolveLlmModelRoute(`${vendor}/${product}@${router}`)\n if (!resolved) continue\n for (const adapterSlug of freeAdapters) {\n out.push({\n vendor,\n product,\n route: router,\n ref: formatModelRef(resolved.ref),\n baseUrl: resolved.transport.baseUrl ?? null,\n pricing: {\n inPer1M: resolved.pricing.inputPer1M,\n outPer1M: resolved.pricing.outputPer1M,\n },\n curated: false,\n adapterSlug,\n methods: [\"api-key\"],\n })\n }\n }\n }\n return out\n}\n\n/**\n * Compatibility routes: canonical protocol surfaces that bill a model family\n * without duplicating pricing entries. For xAI, the same Grok models are\n * reachable on the native OpenAI-flavor `xai` route and on the Anthropic-\n * compatible `xai-anthropic` route; the catalog joins both so the matching\n * auth profile (`xai` vs `xai-anthropic`) can be endpoint-eligible for the\n * right protocol.\n *\n * Rows are generated straight from the static pricing catalog (vendor `xai`),\n * not from adapter curation, because the existing adapters only declare the\n * OpenRouter xAI ids (`x-ai/grok-*@openrouter`). The `xai-anthropic` rows are\n * attached to adapters that can route arbitrary models through an Anthropic-\n * compatible gateway (`routeSelection: \"free\"`). The direct `xai` rows carry\n * no adapter attachment because there is no agent adapter in the registry that\n * speaks the native xAI OpenAI surface today; they are still listed so the\n * `xai` profile can see the models it bills and whether they are runnable.\n */\nfunction compatibilityContributions(\n adapters: readonly CatalogAdapterInput[],\n): RouteContribution[] {\n const freeAdapters = adapters\n .filter(a => a.routeSelection === \"free\")\n .map(a => a.slug)\n const out: RouteContribution[] = []\n\n for (const [product, pricing] of Object.entries(LLM_PRICING_CATALOG)) {\n if (pricing.provider !== \"xai\") continue\n const vendor = pricing.vendor ?? \"xai\"\n const compatRoutes = VENDOR_COMPATIBILITY_ROUTES[vendor]\n if (!compatRoutes) continue\n\n const direct = resolveLlmModelRoute(`${vendor}/${product}`)\n if (!direct) continue\n\n for (const route of compatRoutes) {\n const ref = route === vendor ? `${vendor}/${product}` : `${vendor}/${product}@${route}`\n const baseUrl =\n route === \"xai-anthropic\"\n ? getAnthropicGatewayPreset(\"xai-anthropic\").baseUrl\n : null\n const rowBase: RouteContribution = {\n vendor,\n product,\n route,\n ref,\n baseUrl,\n pricing: {\n inPer1M: direct.pricing.inputPer1M,\n outPer1M: direct.pricing.outputPer1M,\n },\n curated: false,\n methods: [\"api-key\"],\n }\n if (route === \"xai-anthropic\" && freeAdapters.length > 0) {\n for (const adapterSlug of freeAdapters) {\n out.push({ ...rowBase, adapterSlug })\n }\n } else {\n out.push(rowBase)\n }\n }\n }\n return out\n}\n\ninterface MergedRow {\n vendor: string\n product: string\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n curated: boolean\n adapters: string[]\n adapterModes: string[]\n methods: AuthMethod[]\n}\n\n/** Merge contributions sharing a (vendor, product, route) key — multiple\n * adapters can curate the same route, and a widened route always merges\n * into whichever curated row already claimed it. */\nfunction mergeContributions(\n contributions: readonly RouteContribution[],\n): MergedRow[] {\n const rows = new Map<string, MergedRow>()\n for (const c of contributions) {\n const key = `${c.vendor}\u0000${c.product}\u0000${c.route}`\n const existing = rows.get(key)\n if (!existing) {\n rows.set(key, {\n vendor: c.vendor,\n product: c.product,\n route: c.route,\n ref: c.ref,\n baseUrl: c.baseUrl,\n pricing: c.pricing,\n curated: c.curated,\n adapters: c.adapterSlug ? [c.adapterSlug] : [],\n adapterModes: c.adapterMode ? [c.adapterMode] : [],\n methods: [...c.methods],\n })\n continue\n }\n existing.curated = existing.curated || c.curated\n existing.baseUrl = existing.baseUrl ?? c.baseUrl\n existing.pricing = existing.pricing ?? c.pricing\n if (c.adapterSlug && !existing.adapters.includes(c.adapterSlug)) {\n existing.adapters.push(c.adapterSlug)\n }\n if (c.adapterMode && !existing.adapterModes.includes(c.adapterMode)) {\n existing.adapterModes.push(c.adapterMode)\n }\n for (const m of c.methods) {\n if (!existing.methods.includes(m)) existing.methods.push(m)\n }\n }\n return [...rows.values()]\n}\n\n/** The billed vendor for a (vendor, route) pair — the model's own vendor on\n * its direct route, else the route's own id (SPEC §1c). */\nfunction billedVendor(vendor: string, route: string): string {\n return route === vendor ? vendor : route\n}\n\n/**\n * Per-model curation gate (WS3): does `profile`'s allowlist admit this model?\n * An ABSENT `models` field, or `mode: \"all\"`, admits everything — the exact\n * pre-curation behavior, so a profile that predates the field is unchanged\n * (the byte-identical invariant). A `mode: \"allow\"` profile admits the model\n * only when its catalog identity is in the list — matched against BOTH the\n * route-qualified `ref` (`z-ai/glm-5.2@openrouter`) and the route-independent\n * `vendor/product` (`z-ai/glm-5.2`), so a curated id in either form is honored.\n * It ALSO honors the BARE product (`claude-opus-4-8` — everything after the\n * first `/` of `vendorProduct`), but ONLY on a DIRECT route (a `ref` with no\n * `@route` suffix). The vscode \"+ Models\" picker writes bare pricing-catalog\n * ids verbatim into an allowlist for single-vendor DIRECT endpoints (anthropic,\n * moonshot, …), and every existing user allowlist for those is in that form, so\n * the bare tolerance keeps them working with zero migration. It is deliberately\n * NOT extended to multi-vendor GATEWAY endpoints (openrouter/requesty host the\n * same bare product under many vendor prefixes — `sference/glm-5.2` and\n * `z-ai/glm-5.2` are distinct rows on the same endpoint), where a bare id would\n * over-widen across sibling vendors. For those the `ref` (`z-ai/glm-5.2@openrouter`)\n * or the `vendor/product` form (`z-ai/glm-5.2`) is required — which is exactly\n * what the picker writes for gateway endpoints anyway.\n * This is deliberately downstream of `eligibleProfiles` (the endpoint/method\n * gate): a curated profile stays endpoint-eligible but services only its\n * chosen model refs.\n */\nfunction profileAllowsModel(profile: AuthProfile, ref: string, vendorProduct: string): boolean {\n const curation = profile.models\n if (!curation || curation.mode === \"all\") return true\n // The bare product is everything after the FIRST `/` (the vendor is always\n // the first segment; the product may itself contain `/`). No slash → treat\n // the whole string as the product.\n const slash = vendorProduct.indexOf(\"/\")\n const product = slash === -1 ? vendorProduct : vendorProduct.slice(slash + 1)\n // A gateway/router route carries an `@route` suffix in its ref\n // (`z-ai/glm-5.2@openrouter`); a direct route ref is bare `vendor/product`.\n // Bare-product tolerance is DIRECT-only — on a multi-vendor gateway a bare id\n // would admit every sibling vendor's same-named product.\n const isDirect = !ref.includes(\"@\")\n return (\n curation.ids.includes(ref) ||\n curation.ids.includes(vendorProduct) ||\n (isDirect && curation.ids.includes(product))\n )\n}\n\n/** The pure join (SPEC §5): adapter-declared models + router widening +\n * the #470 eligibility predicate → the vendor/product/route tree. No I/O —\n * callers (the HTTP route / MCP tool) own loading adapters + profiles. */\nexport function buildCatalogModels(\n input: BuildCatalogModelsInput,\n): CatalogModelsResponse {\n const curated = curatedContributions(input.adapters)\n const contributions = [\n ...curated,\n ...widenedContributions(input.adapters, curated),\n ...compatibilityContributions(input.adapters),\n ]\n const merged = mergeContributions(contributions)\n const query = input.query ?? {}\n\n // Servable-models-per-route (SPEC §3.9), over the FULL join — a route's\n // model-count is an intrinsic capability, not a view of the caller's\n // query, so `multiModel` stays stable under filtering. A model identity is\n // its `vendor/product` (the same key `widenedContributions` dedupes on), so\n // the same product served by several adapters counts once.\n const servableByRoute = new Map<string, Set<string>>()\n for (const row of merged) {\n const set = servableByRoute.get(row.route) ?? new Set<string>()\n set.add(`${row.vendor}/${row.product}`)\n servableByRoute.set(row.route, set)\n }\n const isMultiModel = (route: string): boolean =>\n (servableByRoute.get(route)?.size ?? 0) > 1\n\n const vendors = new Map<string, Map<string, CatalogRoute[]>>()\n for (const row of merged) {\n if (query.vendor && row.vendor !== query.vendor) continue\n if (query.route && row.route !== query.route) continue\n if (query.adapter && !row.adapters.includes(query.adapter)) continue\n\n // Per-model wallet-serviceability gate (SPEC §1c parity with the spawn\n // guard, `checkModelWalletEligibility` / session-spawn.ts:1143): a DIRECT\n // vendor route — `row.route === row.vendor` with no adapter-mode override\n // in play (`row.adapterModes.length === 0`; an override can coincidentally\n // route === vendor, e.g. a `moonshot` mode reached serving a moonshot-\n // vendor model — see `isDirectRoute`'s own doc comment) — is the catalog's\n // equivalent of spawning with no `route.gateway` named: the adapter's\n // fixed wallet is billed, and that wallet might not actually service this\n // specific model (`claude-fable-5` bills `openrouter`/`requesty`, never\n // `anthropic`, even though the `claude-code` adapter curates it). Reuses\n // the SAME predicate the spawn guard uses — no parallel per-model table.\n // A gateway/mode/router route is the catalog's equivalent of an EXPLICIT\n // `route.gateway` — the spawn guard deliberately never second-guesses\n // that (routing any model through any named gateway's api-key is\n // legitimate), so it is left ungated here too.\n const isDirectVendorRoute = row.route === row.vendor && row.adapterModes.length === 0\n const walletEligible =\n !isDirectVendorRoute ||\n checkModelWalletEligibility(`${row.vendor}/${row.product}`, row.route).ok\n\n const manifest: AdapterAuthManifest = {\n id: `${row.vendor}/${row.product}@${row.route}`,\n endpointByRoute: { [row.route]: billedVendor(row.vendor, row.route) },\n methodsByRoute: { [row.route]: row.methods },\n }\n // The endpoint/method-eligible profiles for this route (whole-profile\n // enable/disable already applied inside `eligibleProfiles`), then narrowed\n // by each profile's per-model curation allowlist (WS3). A profile with no\n // `models` field passes through untouched, so the non-curated join is\n // byte-identical to before.\n const eligible = walletEligible\n ? eligibleProfiles(input.profiles, manifest, row.route).filter(p =>\n profileAllowsModel(p, row.ref, `${row.vendor}/${row.product}`),\n )\n : []\n const runnable = eligible.length > 0\n if (query.runnableOnly && !runnable) continue\n\n const route: CatalogRoute = {\n route: row.route,\n ref: row.ref,\n baseUrl: row.baseUrl,\n pricing: row.pricing,\n runnable,\n eligibleProfiles: eligible.map(p => p.id),\n adapterModes: row.adapterModes,\n adapters: row.adapters,\n curated: row.curated,\n multiModel: isMultiModel(row.route),\n }\n\n const products = vendors.get(row.vendor) ?? new Map<string, CatalogRoute[]>()\n vendors.set(row.vendor, products)\n const routes = products.get(row.product) ?? []\n products.set(row.product, routes)\n routes.push(route)\n }\n\n const result: CatalogVendor[] = [...vendors.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([vendor, products]) => ({\n vendor,\n products: [...products.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([product, routes]) => ({\n product,\n routes: [...routes].sort((a, b) => a.route.localeCompare(b.route)),\n })),\n }))\n\n const routes: CatalogRouteSummary[] = [...servableByRoute.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([route, models]) => ({\n route,\n servableModels: [...models].sort((a, b) => a.localeCompare(b)),\n multiModel: models.size > 1,\n }))\n\n return { vendors: result, routes }\n}\n\n/**\n * Resolve the effective billing route for a session: a parseable model ref's\n * own route is authoritative, `routeGateway` is only consulted when the model\n * carries no explicit `@route` or is unparseable (including router-prefixed ids\n * like `openrouter/vendor/product`, which are normalized to the canonical\n * suffix form first).\n */\nexport function resolveEffectiveRoute(\n model: string | undefined,\n routeGateway: string | undefined,\n): string | undefined {\n if (!model) return routeGateway\n const normalized = normalizeRouterPrefixedId(model)\n const parsed = tryParseModelRef(normalized)\n if (!parsed) return routeGateway\n if (parsed.route !== parsed.vendor) return parsed.route\n return routeGateway ?? parsed.route\n}\n\n/** Rewrite `model`'s `@route` suffix to `route`. Bare/unparseable ids are\n * returned unchanged because they have no suffix to rewrite. */\nexport function modelWithRoute(model: string, route: string): string {\n const parsed = tryParseModelRef(model)\n if (!parsed) return model\n return formatModelRef({ ...parsed, route })\n}\n\n/**\n * Reconcile independent `model` and `route` overrides so they never describe\n * two different billing endpoints. Returns the effective values; mutating\n * callers replace their effective model/route with these results.\n *\n * Rules:\n * - explicit `@route` on the model wins over a stale/conflicting route.gateway;\n * - a route-only override rewrites a parseable model string to match;\n * - a model-only override synthesizes a route field whenever the model's\n * resolved route differs from the previous route.gateway;\n * - passing BOTH overrides that contradict each other is a caller bug → throw.\n */\nexport function reconcileModelRoute(input: {\n prevModel?: string\n prevRoute?: RouteSpec\n model?: string\n route?: RouteSpec\n}): { model?: string; route?: RouteSpec } {\n const { prevModel, prevRoute, model: overrideModel, route: overrideRoute } = input\n\n if (overrideModel !== undefined && overrideRoute !== undefined) {\n const parsed = tryParseModelRef(overrideModel)\n if (parsed && parsed.route !== parsed.vendor && parsed.route !== overrideRoute.gateway) {\n throw new Error(\n `reconcileModelRoute: model \"${overrideModel}\" pins route \"${parsed.route}\" ` +\n `but route override is \"${overrideRoute.gateway}\"`,\n )\n }\n return { model: overrideModel, route: overrideRoute }\n }\n\n if (overrideModel !== undefined) {\n const parsed = tryParseModelRef(overrideModel)\n if (parsed) {\n const hasExplicitRoute = parsed.route !== parsed.vendor\n // An explicit @route always deserves a matching route field; a vendor-\n // implied route only rewrites a stale/conflicting prevRoute.\n if (hasExplicitRoute && prevRoute?.gateway !== parsed.route) {\n return { model: overrideModel, route: { gateway: parsed.route } }\n }\n if (!hasExplicitRoute && prevRoute && prevRoute.gateway !== parsed.route) {\n return { model: overrideModel, route: { gateway: parsed.route } }\n }\n }\n return { model: overrideModel, route: prevRoute }\n }\n\n if (overrideRoute !== undefined) {\n if (prevModel !== undefined) {\n return { model: modelWithRoute(prevModel, overrideRoute.gateway), route: overrideRoute }\n }\n return { route: overrideRoute }\n }\n\n return { model: prevModel, route: prevRoute }\n}\n\n/**\n * The billing routes that can service `model`, reusing the SAME route-identity\n * resolution this module's catalog join uses (SPEC §1c serviceability) — never\n * a parallel per-model provider table. A route R services the model iff:\n * - R is the model's catalog billing provider (`getModelProvider` — the native\n * vendor SDK for a bare/direct id, or the router for a gateway-only slash id\n * like `deepseek/deepseek-v4-pro` ⇒ `openrouter`),\n * - R is an explicit `@route` carried in the id (`…@openrouter`), or\n * - the model resolves on R as one of the {@link WIDENING_ROUTES} router routes\n * (catches requesty-only ids like `sference/…` that carry no pricing-catalog\n * `provider` for `getModelProvider` to return).\n *\n * Returns the DISTINCT serviceable routes; EMPTY when the model is unknown to\n * the catalog — a mismatch cannot then be positively proven, so a money-safety\n * caller MUST NOT reject on an empty result (only on a non-empty set that\n * excludes the resolved wallet). See {@link checkModelWalletEligibility}.\n */\nexport function serviceableModelRoutes(model: string): string[] {\n const routes = new Set<string>()\n const provider = getModelProvider(model)\n if (provider) routes.add(provider)\n const parsed = tryParseModelRef(normalizeRouterPrefixedId(model))\n if (parsed) {\n // An explicit `@route` in the id is itself a serviceable route (and keeps\n // the `getModelProvider` quirk on `<vendor>/<product>@<router>` forms from\n // hiding the route the operator literally named).\n if (parsed.route !== parsed.vendor) routes.add(parsed.route)\n // A first-party vendor model whose `<vendor>/<product>` form COLLIDES with a\n // router-namespaced pricing key hides its own direct vendor route: OpenRouter\n // keys e.g. `anthropic/claude-sonnet-5` / `anthropic/claude-fable-5` with the\n // SAME dash spelling Anthropic uses, tagged `provider:\"openrouter\"`, so\n // `getModelProvider(\"anthropic/claude-sonnet-5\")` resolves to the ROUTER and\n // `anthropic` never enters `routes` above. Restore it: when the BARE product\n // is itself a first-party model of this vendor — a VERBATIM first-party\n // pricing key (`resolvePricingExact(product).provider === vendor`) — the\n // vendor's own SDK/wallet can bill it, so the direct vendor route genuinely\n // IS serviceable. This MUST be the exact lookup, never `resolvePricing`'s\n // substring fallback: an openrouter-only sibling variant like\n // `google/gemini-2.5-flash-image` substring-hits the unrelated\n // `gemini-2.5-flash` row (provider `google`) and would spuriously earn a\n // `google` direct route it cannot actually be billed on. A gateway-only\n // slash id (`deepseek/deepseek-v4-pro` — no bare first-party pricing row)\n // likewise yields no exact match and is left untouched, preserving the\n // money-safety guard's reject on a true router-only model. Scoped to this\n // function: `resolvePricingExact`/`getModelProvider` semantics are unchanged\n // for every other caller.\n if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {\n routes.add(parsed.vendor)\n }\n for (const router of WIDENING_ROUTES) {\n if (tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}@${router}`)) {\n routes.add(router)\n }\n }\n }\n return [...routes]\n}\n\n/** The bare product of a model id — the segment after the last `/`, stripped\n * of any `@route` / `:pin` suffix, lower-cased. `deepseek/deepseek-chat` →\n * `deepseek-chat`; `z-ai/glm-5.2@openrouter` → `glm-5.2`; `claude-opus-4-8`\n * (no slash) → `claude-opus-4-8`. This is the identity the \"did you mean\"\n * matcher keys on, so a wrong-or-missing vendor/route prefix collapses onto\n * the same product. */\nfunction bareProduct(id: string): string {\n const noSuffix = id.split(\"@\")[0]!.split(\":\")[0]!\n const slash = noSuffix.lastIndexOf(\"/\")\n return (slash === -1 ? noSuffix : noSuffix.slice(slash + 1)).toLowerCase()\n}\n\n/**\n * Closest known catalog model ids for a slug UNKNOWN to the local catalog —\n * the \"did you mean\" set behind the spawn-time model advisory (session-spawn.ts).\n *\n * Deliberately narrow to keep false positives near zero: a known id qualifies\n * ONLY when its {@link bareProduct} equals the input's while the full id\n * differs — i.e. the caller used the wrong-or-missing vendor/route prefix\n * (`deepseek-chat` → `deepseek/deepseek-chat`, `glm-5.2` → `z-ai/glm-5.2`,\n * `moonshot/kimi-k2` → `moonshotai/kimi-k2`). A genuinely-new model on a known\n * vendor (`qwen/qwen4-max` when only `qwen3-max` is catalogued) shares no bare\n * product and yields nothing — so a new / free-form slug never earns a spurious\n * suggestion, matching the money-safety guard's own never-reject-an-unknown-\n * model rule. Sourced from the catalog itself (`LLM_PRICING_CATALOG` +\n * `MODEL_ALIASES` keys), never a hand-maintained second list.\n */\nexport function suggestModelSlugs(model: string): string[] {\n const target = bareProduct(model)\n if (!target) return []\n const out = new Set<string>()\n for (const id of [...Object.keys(LLM_PRICING_CATALOG), ...Object.keys(MODEL_ALIASES)]) {\n if (id !== model && bareProduct(id) === target) out.add(id)\n }\n return [...out].sort().slice(0, 5)\n}\n\n/** Verdict of the spawn-time money-safety guard ({@link checkModelWalletEligibility}). */\nexport interface ModelWalletEligibility {\n ok: boolean\n /** Serviceable routes for the model that DIFFER from the resolved wallet —\n * the actionable set to re-spawn onto. Empty when `ok`. */\n suggestedRoutes: string[]\n}\n\n/**\n * Money-safety spawn guard (SPEC §1c): can the resolved billing wallet\n * `walletRoute` (the gateway id when a `route.gateway` is set, else the resolved\n * billing provider) service `model`? Reuses {@link serviceableModelRoutes} — no\n * parallel table. Returns `ok:true` when the model is serviceable on the wallet,\n * OR when the model is unknown to the catalog (empty serviceable set — a mismatch\n * cannot be positively proven, so the guard must not reject a possibly-legitimate\n * new model). Returns `ok:false` with the serviceable alternative routes ONLY\n * when the model IS serviceable on some route but NOT the resolved wallet — the\n * exact 404-upstream case (`deepseek/deepseek-v4-pro` on the Anthropic sub). The\n * guard only REJECTS; it never substitutes a wallet the operator didn't name.\n */\nexport function checkModelWalletEligibility(\n model: string,\n walletRoute: string,\n): ModelWalletEligibility {\n const serviceable = serviceableModelRoutes(model)\n if (serviceable.length === 0 || serviceable.includes(walletRoute)) {\n return { ok: true, suggestedRoutes: [] }\n }\n return { ok: false, suggestedRoutes: serviceable.filter(r => r !== walletRoute) }\n}\n\n/**\n * The actionable fail-fast message shared by both spawn paths (session-spawn +\n * session-restart-core) so they never drift. Names the wallet that couldn't\n * service the model AND the required route + api-key profile to re-spawn onto —\n * never a wallet the guard picked for the operator.\n */\nexport function modelWalletIneligibleMessage(opts: {\n prefix: string\n adapter: string\n model: string\n walletRoute: string\n walletMode?: \"subscription\" | \"api-key\"\n suggestedRoutes: string[]\n}): string {\n const wallet = opts.walletMode\n ? `\"${opts.walletRoute}\" ${opts.walletMode} wallet`\n : `\"${opts.walletRoute}\" wallet`\n const primary = opts.suggestedRoutes[0] ?? \"a gateway route\"\n const also =\n opts.suggestedRoutes.length > 1\n ? ` (also serviceable via ${opts.suggestedRoutes\n .slice(1)\n .map(r => `\"${r}\"`)\n .join(\", \")})`\n : \"\"\n return (\n `${opts.prefix}: model \"${opts.model}\" is not serviceable on the resolved ${wallet} ` +\n `(adapter \"${opts.adapter}\") and would 404 upstream. This model bills route \"${primary}\"${also} — ` +\n `re-spawn on it: set route.gateway=\"${primary}\" with an eligible \"${primary}\" api-key profile ` +\n `(access.profileRef). This guard only rejects; it never switches wallets for you.`\n )\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/catalog-models.ts"],"names":["routes"],"mappings":"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","file":"catalog-models.mjs","sourcesContent":["/**\n * Read-only catalog/vendor endpoint (`agentproto-session-config-axes`\n * SPEC §5) — `GET /catalog/models` + `catalog_models` MCP tool wire into\n * {@link buildCatalogModels}, the pure join this module owns.\n *\n * Reuses three already-shipped pieces instead of rebuilding them:\n * - the vendor/product/route model + router widening (OpenRouter/\n * Requesty/HuggingFace) from `@agentproto/model-catalog/route-identity`\n * (`resolveLlmModelRoute`, `route-identity/index.ts:396-511`) — this is\n * what keeps the catalog from being capped at any one adapter's\n * `models.allowed` list (SPEC §5.1);\n * - the profile eligibility predicate shipped in #470\n * (`@agentproto/auth`'s `eligibleProfiles`, `packages/auth/src/\n * eligibility.ts:81-89`) for the profile-aware `runnable` flag (SPEC\n * §5.3) — the old bare `hasKey` check (`packages/cli/src/commands/\n * models.ts:113-117`) is the degenerate one-profile-per-provider case\n * this predicate subsumes;\n * - `AdapterAuthDescriptor` (`spawn-defaults.ts:226`), the SAME\n * provider/authSubscription projection `resolveAuthSpec` reads, as the\n * source for which auth methods an adapter can present on its direct\n * route (SPEC §3.4's derivable replacement for a hand-maintained\n * `authSubscription` boolean).\n *\n * A gateway/router route (anything where the resolved route differs from\n * the model's vendor — `openrouter`, `requesty`, `huggingface`, or an\n * adapter's own gateway mode id like `moonshot`) always bills against the\n * route's own id and is always reached with an api-key credential — never\n * oauth-bearer, since no third-party gateway has an Anthropic-style\n * subscription bearer path (SPEC §1c: \"a moonshot profile, not the Claude\n * sub\"). That structural rule is what lets this module compute\n * `runnable`/`eligibleProfiles` for the widened, non-curated rows without\n * per-adapter gateway-vendor tables.\n */\n\nimport type { AuthMethod, AuthProfile } from \"@agentproto/auth\"\nimport { eligibleProfiles, type AdapterAuthManifest } from \"@agentproto/auth\"\nimport {\n resolveLlmModelRoute,\n tryParseModelRef,\n formatModelRef,\n} from \"@agentproto/model-catalog/route-identity\"\nimport {\n getModelProvider,\n resolvePricingExact,\n LLM_PRICING_CATALOG,\n MODEL_ALIASES,\n} from \"@agentproto/model-catalog/llm\"\nimport { getAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport type { AdapterAuthDescriptor } from \"./spawn-defaults.js\"\nimport type { RouteSpec } from \"./session-config.js\"\n\nexport type { RouteSpec } from \"./session-config.js\"\n\n/** Routers the catalog probes to widen beyond any adapter's declared model\n * list (SPEC §5.1) — same three route-identity widens, `route-identity/\n * index.ts:54-59`. */\nconst WIDENING_ROUTES = [\"openrouter\", \"requesty\", \"huggingface\"] as const\n\n/**\n * Vendor-specific compatibility routes: canonical protocol surfaces that can\n * bill the same model family without duplicating catalog entries. A model whose\n * pricing-catalog vendor is `xai` is legitimately reachable both on the native\n * `xai` API route and on the Anthropic-compatible `xai-anthropic` route; the\n * two routes are distinct billing endpoints so the right auth profile matches\n * the right protocol.\n */\nconst VENDOR_COMPATIBILITY_ROUTES: Readonly<Record<string, readonly string[]>> = {\n xai: [\"xai\", \"xai-anthropic\"],\n}\n\n/** One model entry as declared in an adapter's `models.allowed`\n * (`AdapterModelInfo`, `packages/cli/src/registry/resolve.ts:134-142`) —\n * the subset this module needs. */\nexport interface CatalogAdapterModelInput {\n /** Model id exactly as declared — bare (`\"claude-opus-4-8\"`) or\n * `vendor/product` form. */\n id: string\n /** The billing provider/route this model entry reaches on (`\n * AdapterModelInfo.provider`) — e.g. `\"moonshot\"` or `\"openrouter\"`.\n * Takes precedence over the id's own implied vendor route. */\n provider?: string\n /** The adapter mode id that must be applied to reach this model on a\n * non-direct route (`AdapterModelInfo.mode`) — e.g. `\"moonshot\"`.\n * Undefined ⇒ direct route (the model's own vendor, or `provider`). */\n mode?: string\n}\n\n/** One installed adapter's contribution to the catalog. */\nexport interface CatalogAdapterInput {\n slug: string\n models: readonly CatalogAdapterModelInput[]\n /** This adapter's billing-auth capability on its DIRECT route — the same\n * projection `resolveAuthSpec` reads (`spawn-defaults.ts:226`). Omitted\n * ⇒ the adapter presents no auth method, so rows it curates are\n * discoverable but never runnable through it alone. */\n authDescriptor?: AdapterAuthDescriptor\n /** How this adapter's spawn ROUTE relates to the chosen model (AIP-45\n * launch-menu drill-down). `\"free\"` = the adapter can route arbitrary\n * models through gateways (`base_url`). `\"derived-from-model\"` = the\n * endpoint falls out of the model id's vendor prefix. Absent/undefined\n * ⇒ fixed single-provider adapter; widened gateway routes are not\n * attached to it. */\n routeSelection?: \"free\" | \"derived-from-model\"\n}\n\nexport interface CatalogModelsQuery {\n /** Keep only routes reachable via this adapter slug. */\n adapter?: string\n /** Keep only this vendor's entry. */\n vendor?: string\n /** Keep only routes with this route id. */\n route?: string\n /** Drop every route with `runnable: false`. */\n runnableOnly?: boolean\n}\n\nexport interface CatalogPricing {\n inPer1M: number\n outPer1M: number\n}\n\nexport interface CatalogRoute {\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n runnable: boolean\n eligibleProfiles: string[]\n adapterModes: string[]\n adapters: string[]\n curated: boolean\n /**\n * True when MORE THAN ONE distinct model is servable on this route across\n * the whole catalog join (AIP-45 launch-menu drill-down, SPEC §3/§5.3).\n * Derived from the model×route join, never a hand-maintained table:\n * `anthropic`/`openrouter`/`llm-endpoint` serve many models (`true`),\n * a single-model gateway like `moonshot` serves one (`false`). A\n * single-model route pins every model tier to its one model downstream,\n * and gates the custom-gateway A-vs-B promotion (SPEC D5). Independent of\n * the caller's query filters — this is the route's intrinsic capacity, so\n * the same route reports the same `multiModel` in a filtered response.\n */\n multiModel: boolean\n}\n\n/**\n * One route's servable-model index (SPEC §3.9) — the flat, per-route view\n * that lets a capability-derived UI derive tier pinning without re-walking\n * the vendor/product tree. `servableModels` is the set of `vendor/product`\n * model identities reachable on this route across the whole join;\n * `multiModel` is `servableModels.length > 1` (the same value carried on\n * every {@link CatalogRoute} with this id). Always the full, unfiltered\n * catalog capacity, independent of the query — a route's model-count is\n * intrinsic, not a view of the filtered result.\n */\nexport interface CatalogRouteSummary {\n route: string\n servableModels: string[]\n multiModel: boolean\n}\n\nexport interface CatalogProduct {\n product: string\n routes: CatalogRoute[]\n}\n\nexport interface CatalogVendor {\n vendor: string\n products: CatalogProduct[]\n}\n\nexport interface CatalogModelsResponse {\n vendors: CatalogVendor[]\n /**\n * Flat servable-models-per-route index (SPEC §3.9), one entry per distinct\n * route id in the catalog, sorted by route. Exposes the same join the\n * nested `multiModel` flags derive from, so a capability layer can compute\n * tier pinning per route without re-walking the vendor tree. Always the\n * full, unfiltered catalog capacity (see {@link CatalogRouteSummary}).\n */\n routes: CatalogRouteSummary[]\n}\n\nexport interface BuildCatalogModelsInput {\n adapters: readonly CatalogAdapterInput[]\n profiles: readonly AuthProfile[]\n query?: CatalogModelsQuery\n}\n\n/** A resolved model identity + pricing, independent of which adapter (if\n * any) declared it. */\ninterface ResolvedModel {\n vendor: string\n product: string\n /** The route this id resolves to on its OWN — vendor for a direct id,\n * a router name when the id already carries `@route`. */\n directRoute: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n}\n\n/** Rewrite a router-prefixed id (`<router>/<vendor>/<product>`) into the\n * canonical route-identity `<vendor>/<product>@<router>` the parser accepts.\n *\n * Mastra-style adapters (e.g. `adapters/mastra-agent`) declare their model\n * ids in `<provider>/<upstream-id>` form, and for a gateway router the\n * upstream id is itself `<vendor>/<product>` — so a native OpenRouter id like\n * `z-ai/glm-5.2` is advertised as the 3-segment `openrouter/z-ai/glm-5.2`.\n * `parseModelRef` splits on the FIRST `/` and rejects a product that still\n * contains one (`route-identity/index.ts` SEGMENT_RE), so feeding it the raw\n * 3-segment string throws and, before this normalization, 500'd the whole\n * catalog. The route-identity grammar's canonical form for such a model is\n * `<vendor>/<product>@<router>` (`z-ai/glm-5.2@openrouter`) — the `@route`\n * suffix, NOT a leading route segment — so we recompose to that. Only the\n * known gateway routers (whose native ids are `<vendor>/<product>`) are\n * peeled; every other id is returned untouched. A `:pin` variant/provider\n * suffix on the upstream id is preserved (it rides along in the remainder).\n */\nfunction normalizeRouterPrefixedId(id: string): string {\n const firstSlash = id.indexOf(\"/\")\n if (firstSlash === -1) return id\n const head = id.slice(0, firstSlash)\n if (!(WIDENING_ROUTES as readonly string[]).includes(head)) return id\n const remainder = id.slice(firstSlash + 1)\n // Only a genuine `<vendor>/<product>` upstream id (still carrying a `/`) is\n // the router-prefixed shape; a 2-segment `<router>/<product>` is left alone.\n if (!remainder.includes(\"/\") || remainder.includes(\"@\")) return id\n return `${remainder}@${head}`\n}\n\n/** {@link resolveLlmModelRoute} that never throws. `resolveLlmModelRoute`\n * parses through the strict `parseModelRef`, which throws on a still-\n * unparseable ref (e.g. an unrecognised 3-segment id that normalization\n * didn't rewrite) — defense-in-depth so a single bad id can never 500 the\n * catalog. */\nfunction tryResolveLlmModelRoute(id: string): ReturnType<typeof resolveLlmModelRoute> {\n try {\n return resolveLlmModelRoute(id)\n } catch {\n return undefined\n }\n}\n\n/** Infer a vendor from a bare id family prefix — ports `providerFromIdPrefix`\n * (`packages/cli/src/commands/models.ts:47-54`) so a model id that predates\n * its pricing-catalog entry still gets a vendor instead of `\"unknown\"`. */\nfunction vendorFromIdPrefix(bareId: string): string | undefined {\n if (/^claude[-/]/.test(bareId)) return \"anthropic\"\n if (/^(gpt[-/]|o[1-9](-|$)|chatgpt)/.test(bareId)) return \"openai\"\n if (/^gemini[-/]/.test(bareId)) return \"google\"\n if (/^grok[-/]/.test(bareId)) return \"x-ai\"\n if (/^deepseek[-/]/.test(bareId)) return \"deepseek\"\n return undefined\n}\n\n/** Resolve a model id to its vendor/product/route/pricing, in three tiers:\n * the route-identity resolver (handles both `vendor/product[@route]` and\n * legacy bare ids with pricing), then a bare vendor/product parse with no\n * pricing, then the id-prefix heuristic as a last resort. Never throws —\n * every id gets SOME vendor rather than being dropped from the catalog. */\nfunction resolveModelId(id: string): ResolvedModel {\n // A router-prefixed Mastra id (`openrouter/z-ai/glm-5.2`) becomes its\n // canonical route-identity (`z-ai/glm-5.2@openrouter`) BEFORE any parse, so\n // it resolves to a real vendor/product/route row instead of throwing.\n const normalized = normalizeRouterPrefixedId(id)\n const resolved = tryResolveLlmModelRoute(normalized)\n if (resolved) {\n return {\n vendor: resolved.vendor,\n product: resolved.product,\n directRoute: resolved.route,\n ref: formatModelRef(resolved.ref),\n baseUrl: resolved.transport.baseUrl ?? null,\n pricing: {\n inPer1M: resolved.pricing.inputPer1M,\n outPer1M: resolved.pricing.outputPer1M,\n },\n }\n }\n const parsed = tryParseModelRef(normalized)\n if (parsed) {\n return {\n vendor: parsed.vendor,\n product: parsed.product,\n directRoute: parsed.route,\n ref: formatModelRef(parsed),\n baseUrl: null,\n pricing: null,\n }\n }\n const vendor = vendorFromIdPrefix(id) ?? \"unknown\"\n return {\n vendor,\n product: id,\n directRoute: vendor,\n ref: `${vendor}/${id}`,\n baseUrl: null,\n pricing: null,\n }\n}\n\n/** Which auth methods are presentable on a model's DIRECT route — whatever\n * the adapter's descriptor declares, derivable from `authSubscription`/\n * `provider` exactly as SPEC §3.4 calls for. Only called for a genuinely\n * direct route (see `isDirectRoute`); a gateway/router route is always\n * api-key only (SPEC §1c — no third-party gateway has an oauth-bearer\n * path), regardless of what the underlying model's own vendor is. */\nfunction methodsForDirect(\n descriptor: AdapterAuthDescriptor | undefined,\n): AuthMethod[] {\n const methods: AuthMethod[] = []\n if (descriptor?.authSubscription || descriptor?.modelDerivedApiKey)\n methods.push(\"oauth-bearer\")\n if (descriptor?.provider || descriptor?.modelDerivedApiKey) methods.push(\"api-key\")\n return methods\n}\n\n/** True iff this model entry resolves to its OWN vendor with no adapter\n * mode override — i.e. reached without any gateway/router redirection.\n * An explicit `model.mode` (adapter gateway mode, e.g. `\"moonshot\"`) is\n * ALWAYS a redirection even when the mode id happens to equal the\n * model's resolved vendor (e.g. a `moonshot/kimi-…` model routed via\n * claude-code's `moonshot` mode) — SPEC §1c's \"moonshot profile, not the\n * Claude sub\" holds regardless of whose model is being served. An id\n * that already carries its own `@route` suffix (`resolved.directRoute !==\n * resolved.vendor`) is equally a router path even with no adapter mode.\n * A `model.provider` that matches the model's own vendor is still a\n * direct route; only a provider/route that differs is a redirection. */\nfunction isDirectRoute(route: string, mode: string | undefined, resolved: ResolvedModel): boolean {\n return mode === undefined && route === resolved.vendor && resolved.directRoute === resolved.vendor\n}\n\ninterface RouteContribution {\n vendor: string\n product: string\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n curated: boolean\n adapterSlug?: string\n adapterMode?: string\n methods: readonly AuthMethod[]\n}\n\n/** Curated contributions — one per adapter-declared model entry. */\nfunction curatedContributions(\n adapters: readonly CatalogAdapterInput[],\n): RouteContribution[] {\n const out: RouteContribution[] = []\n for (const adapter of adapters) {\n for (const model of adapter.models) {\n const resolved = resolveModelId(model.id)\n const route = model.mode ?? model.provider ?? resolved.directRoute\n const methods: AuthMethod[] = isDirectRoute(route, model.mode, resolved)\n ? methodsForDirect(adapter.authDescriptor)\n : [\"api-key\"]\n out.push({\n vendor: resolved.vendor,\n product: resolved.product,\n route,\n ref: resolved.ref,\n baseUrl: resolved.baseUrl,\n pricing: resolved.pricing,\n curated: true,\n adapterSlug: adapter.slug,\n ...(model.mode ? { adapterMode: model.mode } : {}),\n methods,\n })\n }\n }\n return out\n}\n\n/** Widen beyond every adapter's declared list (SPEC §5.1): for each\n * distinct (vendor, product) already known from a curated contribution,\n * probe the router routes route-identity knows about and add a\n * non-curated contribution for any that resolve and aren't already\n * covered by a curated row.\n *\n * Widened routes are only attached to adapters that explicitly declare\n * `routeSelection: \"free\"` — those are the adapters that can route an\n * arbitrary model through a gateway via `base_url`. Fixed-provider and\n * `derived-from-model` adapters must not be advertised on routes they\n * cannot truthfully choose. */\nfunction widenedContributions(\n adapters: readonly CatalogAdapterInput[],\n curated: readonly RouteContribution[],\n): RouteContribution[] {\n const freeAdapters = adapters\n .filter(a => a.routeSelection === \"free\")\n .map(a => a.slug)\n if (freeAdapters.length === 0) return []\n\n const seenProducts = new Map<string, Set<string>>() // \"vendor/product\" -> routes already present\n for (const c of curated) {\n const key = `${c.vendor}/${c.product}`\n const routes = seenProducts.get(key) ?? new Set<string>()\n routes.add(c.route)\n seenProducts.set(key, routes)\n }\n\n const out: RouteContribution[] = []\n for (const [key, existingRoutes] of seenProducts) {\n const [vendor, product] = key.split(\"/\", 2) as [string, string]\n for (const router of WIDENING_ROUTES) {\n if (existingRoutes.has(router)) continue\n const resolved = tryResolveLlmModelRoute(`${vendor}/${product}@${router}`)\n if (!resolved) continue\n for (const adapterSlug of freeAdapters) {\n out.push({\n vendor,\n product,\n route: router,\n ref: formatModelRef(resolved.ref),\n baseUrl: resolved.transport.baseUrl ?? null,\n pricing: {\n inPer1M: resolved.pricing.inputPer1M,\n outPer1M: resolved.pricing.outputPer1M,\n },\n curated: false,\n adapterSlug,\n methods: [\"api-key\"],\n })\n }\n }\n }\n return out\n}\n\n/**\n * Compatibility routes: canonical protocol surfaces that bill a model family\n * without duplicating pricing entries. For xAI, the same Grok models are\n * reachable on the native OpenAI-flavor `xai` route and on the Anthropic-\n * compatible `xai-anthropic` route; the catalog joins both so the matching\n * auth profile (`xai` vs `xai-anthropic`) can be endpoint-eligible for the\n * right protocol.\n *\n * Rows are generated straight from the static pricing catalog (vendor `xai`),\n * not from adapter curation, because the existing adapters only declare the\n * OpenRouter xAI ids (`x-ai/grok-*@openrouter`). The `xai-anthropic` rows are\n * attached to adapters that can route arbitrary models through an Anthropic-\n * compatible gateway (`routeSelection: \"free\"`). The direct `xai` rows carry\n * no adapter attachment because there is no agent adapter in the registry that\n * speaks the native xAI OpenAI surface today; they are still listed so the\n * `xai` profile can see the models it bills and whether they are runnable.\n */\nfunction compatibilityContributions(\n adapters: readonly CatalogAdapterInput[],\n): RouteContribution[] {\n const freeAdapters = adapters\n .filter(a => a.routeSelection === \"free\")\n .map(a => a.slug)\n const out: RouteContribution[] = []\n\n for (const [product, pricing] of Object.entries(LLM_PRICING_CATALOG)) {\n if (pricing.provider !== \"xai\") continue\n const vendor = pricing.vendor ?? \"xai\"\n const compatRoutes = VENDOR_COMPATIBILITY_ROUTES[vendor]\n if (!compatRoutes) continue\n\n const direct = resolveLlmModelRoute(`${vendor}/${product}`)\n if (!direct) continue\n\n for (const route of compatRoutes) {\n const ref = route === vendor ? `${vendor}/${product}` : `${vendor}/${product}@${route}`\n const baseUrl =\n route === \"xai-anthropic\"\n ? getAnthropicGatewayPreset(\"xai-anthropic\").baseUrl\n : null\n const rowBase: RouteContribution = {\n vendor,\n product,\n route,\n ref,\n baseUrl,\n pricing: {\n inPer1M: direct.pricing.inputPer1M,\n outPer1M: direct.pricing.outputPer1M,\n },\n curated: false,\n methods: [\"api-key\"],\n }\n if (route === \"xai-anthropic\" && freeAdapters.length > 0) {\n for (const adapterSlug of freeAdapters) {\n out.push({ ...rowBase, adapterSlug })\n }\n } else {\n out.push(rowBase)\n }\n }\n }\n return out\n}\n\ninterface MergedRow {\n vendor: string\n product: string\n route: string\n ref: string\n baseUrl: string | null\n pricing: CatalogPricing | null\n curated: boolean\n adapters: string[]\n adapterModes: string[]\n methods: AuthMethod[]\n}\n\n/** Merge contributions sharing a (vendor, product, route) key — multiple\n * adapters can curate the same route, and a widened route always merges\n * into whichever curated row already claimed it. */\nfunction mergeContributions(\n contributions: readonly RouteContribution[],\n): MergedRow[] {\n const rows = new Map<string, MergedRow>()\n for (const c of contributions) {\n const key = `${c.vendor}\u0000${c.product}\u0000${c.route}`\n const existing = rows.get(key)\n if (!existing) {\n rows.set(key, {\n vendor: c.vendor,\n product: c.product,\n route: c.route,\n ref: c.ref,\n baseUrl: c.baseUrl,\n pricing: c.pricing,\n curated: c.curated,\n adapters: c.adapterSlug ? [c.adapterSlug] : [],\n adapterModes: c.adapterMode ? [c.adapterMode] : [],\n methods: [...c.methods],\n })\n continue\n }\n existing.curated = existing.curated || c.curated\n existing.baseUrl = existing.baseUrl ?? c.baseUrl\n existing.pricing = existing.pricing ?? c.pricing\n if (c.adapterSlug && !existing.adapters.includes(c.adapterSlug)) {\n existing.adapters.push(c.adapterSlug)\n }\n if (c.adapterMode && !existing.adapterModes.includes(c.adapterMode)) {\n existing.adapterModes.push(c.adapterMode)\n }\n for (const m of c.methods) {\n if (!existing.methods.includes(m)) existing.methods.push(m)\n }\n }\n return [...rows.values()]\n}\n\n/** The billed vendor for a (vendor, route) pair — the model's own vendor on\n * its direct route, else the route's own id (SPEC §1c). */\nfunction billedVendor(vendor: string, route: string): string {\n return route === vendor ? vendor : route\n}\n\n/**\n * Per-model curation gate (WS3): does `profile`'s allowlist admit this model?\n * An ABSENT `models` field, or `mode: \"all\"`, admits everything — the exact\n * pre-curation behavior, so a profile that predates the field is unchanged\n * (the byte-identical invariant). A `mode: \"allow\"` profile admits the model\n * only when its catalog identity is in the list — matched against BOTH the\n * route-qualified `ref` (`z-ai/glm-5.2@openrouter`) and the route-independent\n * `vendor/product` (`z-ai/glm-5.2`), so a curated id in either form is honored.\n * It ALSO honors the BARE product (`claude-opus-4-8` — everything after the\n * first `/` of `vendorProduct`), but ONLY on a DIRECT route (a `ref` with no\n * `@route` suffix). The vscode \"+ Models\" picker writes bare pricing-catalog\n * ids verbatim into an allowlist for single-vendor DIRECT endpoints (anthropic,\n * moonshot, …), and every existing user allowlist for those is in that form, so\n * the bare tolerance keeps them working with zero migration. It is deliberately\n * NOT extended to multi-vendor GATEWAY endpoints (openrouter/requesty host the\n * same bare product under many vendor prefixes — `sference/glm-5.2` and\n * `z-ai/glm-5.2` are distinct rows on the same endpoint), where a bare id would\n * over-widen across sibling vendors. For those the `ref` (`z-ai/glm-5.2@openrouter`)\n * or the `vendor/product` form (`z-ai/glm-5.2`) is required — which is exactly\n * what the picker writes for gateway endpoints anyway.\n * This is deliberately downstream of `eligibleProfiles` (the endpoint/method\n * gate): a curated profile stays endpoint-eligible but services only its\n * chosen model refs.\n */\nfunction profileAllowsModel(profile: AuthProfile, ref: string, vendorProduct: string): boolean {\n const curation = profile.models\n if (!curation || curation.mode === \"all\") return true\n // The bare product is everything after the FIRST `/` (the vendor is always\n // the first segment; the product may itself contain `/`). No slash → treat\n // the whole string as the product.\n const slash = vendorProduct.indexOf(\"/\")\n const product = slash === -1 ? vendorProduct : vendorProduct.slice(slash + 1)\n // A gateway/router route carries an `@route` suffix in its ref\n // (`z-ai/glm-5.2@openrouter`); a direct route ref is bare `vendor/product`.\n // Bare-product tolerance is DIRECT-only — on a multi-vendor gateway a bare id\n // would admit every sibling vendor's same-named product.\n const isDirect = !ref.includes(\"@\")\n return (\n curation.ids.includes(ref) ||\n curation.ids.includes(vendorProduct) ||\n (isDirect && curation.ids.includes(product))\n )\n}\n\n/** The pure join (SPEC §5): adapter-declared models + router widening +\n * the #470 eligibility predicate → the vendor/product/route tree. No I/O —\n * callers (the HTTP route / MCP tool) own loading adapters + profiles. */\nexport function buildCatalogModels(\n input: BuildCatalogModelsInput,\n): CatalogModelsResponse {\n const curated = curatedContributions(input.adapters)\n const contributions = [\n ...curated,\n ...widenedContributions(input.adapters, curated),\n ...compatibilityContributions(input.adapters),\n ]\n const merged = mergeContributions(contributions)\n const query = input.query ?? {}\n\n // Servable-models-per-route (SPEC §3.9), over the FULL join — a route's\n // model-count is an intrinsic capability, not a view of the caller's\n // query, so `multiModel` stays stable under filtering. A model identity is\n // its `vendor/product` (the same key `widenedContributions` dedupes on), so\n // the same product served by several adapters counts once.\n const servableByRoute = new Map<string, Set<string>>()\n for (const row of merged) {\n const set = servableByRoute.get(row.route) ?? new Set<string>()\n set.add(`${row.vendor}/${row.product}`)\n servableByRoute.set(row.route, set)\n }\n const isMultiModel = (route: string): boolean =>\n (servableByRoute.get(route)?.size ?? 0) > 1\n\n const vendors = new Map<string, Map<string, CatalogRoute[]>>()\n for (const row of merged) {\n if (query.vendor && row.vendor !== query.vendor) continue\n if (query.route && row.route !== query.route) continue\n if (query.adapter && !row.adapters.includes(query.adapter)) continue\n\n // Per-model wallet-serviceability gate (SPEC §1c parity with the spawn\n // guard, `checkModelWalletEligibility` / session-spawn.ts:1143): a DIRECT\n // vendor route — `row.route === row.vendor` with no adapter-mode override\n // in play (`row.adapterModes.length === 0`; an override can coincidentally\n // route === vendor, e.g. a `moonshot` mode reached serving a moonshot-\n // vendor model — see `isDirectRoute`'s own doc comment) — is the catalog's\n // equivalent of spawning with no `route.gateway` named: the adapter's\n // fixed wallet is billed, and that wallet might not actually service this\n // specific model (`claude-fable-5` bills `openrouter`/`requesty`, never\n // `anthropic`, even though the `claude-code` adapter curates it). Reuses\n // the SAME predicate the spawn guard uses — no parallel per-model table.\n // A gateway/mode/router route is the catalog's equivalent of an EXPLICIT\n // `route.gateway` — the spawn guard deliberately never second-guesses\n // that (routing any model through any named gateway's api-key is\n // legitimate), so it is left ungated here too.\n const isDirectVendorRoute = row.route === row.vendor && row.adapterModes.length === 0\n const walletEligible =\n !isDirectVendorRoute ||\n checkModelWalletEligibility(`${row.vendor}/${row.product}`, row.route).ok\n\n const manifest: AdapterAuthManifest = {\n id: `${row.vendor}/${row.product}@${row.route}`,\n endpointByRoute: { [row.route]: billedVendor(row.vendor, row.route) },\n methodsByRoute: { [row.route]: row.methods },\n }\n // The endpoint/method-eligible profiles for this route (whole-profile\n // enable/disable already applied inside `eligibleProfiles`), then narrowed\n // by each profile's per-model curation allowlist (WS3). A profile with no\n // `models` field passes through untouched, so the non-curated join is\n // byte-identical to before.\n const eligible = walletEligible\n ? eligibleProfiles(input.profiles, manifest, row.route).filter(p =>\n profileAllowsModel(p, row.ref, `${row.vendor}/${row.product}`),\n )\n : []\n const runnable = eligible.length > 0\n if (query.runnableOnly && !runnable) continue\n\n const route: CatalogRoute = {\n route: row.route,\n ref: row.ref,\n baseUrl: row.baseUrl,\n pricing: row.pricing,\n runnable,\n eligibleProfiles: eligible.map(p => p.id),\n adapterModes: row.adapterModes,\n adapters: row.adapters,\n curated: row.curated,\n multiModel: isMultiModel(row.route),\n }\n\n const products = vendors.get(row.vendor) ?? new Map<string, CatalogRoute[]>()\n vendors.set(row.vendor, products)\n const routes = products.get(row.product) ?? []\n products.set(row.product, routes)\n routes.push(route)\n }\n\n const result: CatalogVendor[] = [...vendors.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([vendor, products]) => ({\n vendor,\n products: [...products.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([product, routes]) => ({\n product,\n routes: [...routes].sort((a, b) => a.route.localeCompare(b.route)),\n })),\n }))\n\n const routes: CatalogRouteSummary[] = [...servableByRoute.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([route, models]) => ({\n route,\n servableModels: [...models].sort((a, b) => a.localeCompare(b)),\n multiModel: models.size > 1,\n }))\n\n return { vendors: result, routes }\n}\n\n/**\n * Resolve the effective billing route for a session: a parseable model ref's\n * own route is authoritative, `routeGateway` is only consulted when the model\n * carries no explicit `@route` or is unparseable (including router-prefixed ids\n * like `openrouter/vendor/product`, which are normalized to the canonical\n * suffix form first).\n */\nexport function resolveEffectiveRoute(\n model: string | undefined,\n routeGateway: string | undefined,\n): string | undefined {\n if (!model) return routeGateway\n const normalized = normalizeRouterPrefixedId(model)\n const parsed = tryParseModelRef(normalized)\n if (!parsed) return routeGateway\n if (parsed.route !== parsed.vendor) return parsed.route\n return routeGateway ?? parsed.route\n}\n\n/** Rewrite `model`'s `@route` suffix to `route`. Bare/unparseable ids are\n * returned unchanged because they have no suffix to rewrite. */\nexport function modelWithRoute(model: string, route: string): string {\n const parsed = tryParseModelRef(model)\n if (!parsed) return model\n return formatModelRef({ ...parsed, route })\n}\n\n/**\n * Reconcile independent `model` and `route` overrides so they never describe\n * two different billing endpoints. Returns the effective values; mutating\n * callers replace their effective model/route with these results.\n *\n * Rules:\n * - explicit `@route` on the model wins over a stale/conflicting route.gateway;\n * - a route-only override rewrites a parseable model string to match;\n * - a model-only override synthesizes a route field whenever the model's\n * resolved route differs from the previous route.gateway;\n * - passing BOTH overrides that contradict each other is a caller bug → throw.\n */\nexport function reconcileModelRoute(input: {\n prevModel?: string\n prevRoute?: RouteSpec\n model?: string\n route?: RouteSpec\n}): { model?: string; route?: RouteSpec } {\n const { prevModel, prevRoute, model: overrideModel, route: overrideRoute } = input\n\n if (overrideModel !== undefined && overrideRoute !== undefined) {\n const parsed = tryParseModelRef(overrideModel)\n if (parsed && parsed.route !== parsed.vendor && parsed.route !== overrideRoute.gateway) {\n throw new Error(\n `reconcileModelRoute: model \"${overrideModel}\" pins route \"${parsed.route}\" ` +\n `but route override is \"${overrideRoute.gateway}\"`,\n )\n }\n return { model: overrideModel, route: overrideRoute }\n }\n\n if (overrideModel !== undefined) {\n const parsed = tryParseModelRef(overrideModel)\n if (parsed) {\n const hasExplicitRoute = parsed.route !== parsed.vendor\n // An explicit @route always deserves a matching route field; a vendor-\n // implied route only rewrites a stale/conflicting prevRoute.\n if (hasExplicitRoute && prevRoute?.gateway !== parsed.route) {\n return { model: overrideModel, route: { gateway: parsed.route } }\n }\n if (!hasExplicitRoute && prevRoute && prevRoute.gateway !== parsed.route) {\n return { model: overrideModel, route: { gateway: parsed.route } }\n }\n }\n return { model: overrideModel, route: prevRoute }\n }\n\n if (overrideRoute !== undefined) {\n if (prevModel !== undefined) {\n return { model: modelWithRoute(prevModel, overrideRoute.gateway), route: overrideRoute }\n }\n return { route: overrideRoute }\n }\n\n return { model: prevModel, route: prevRoute }\n}\n\n/**\n * The billing routes that can service `model`, reusing the SAME route-identity\n * resolution this module's catalog join uses (SPEC §1c serviceability) — never\n * a parallel per-model provider table. A route R services the model iff:\n * - R is the model's catalog billing provider (`getModelProvider` — the native\n * vendor SDK for a bare/direct id, or the router for a gateway-only slash id\n * like `deepseek/deepseek-v4-pro` ⇒ `openrouter`),\n * - R is an explicit `@route` carried in the id (`…@openrouter`), or\n * - the model resolves on R as one of the {@link WIDENING_ROUTES} router routes\n * (catches requesty-only ids like `sference/…` that carry no pricing-catalog\n * `provider` for `getModelProvider` to return).\n *\n * Returns the DISTINCT serviceable routes; EMPTY when the model is unknown to\n * the catalog — a mismatch cannot then be positively proven, so a money-safety\n * caller MUST NOT reject on an empty result (only on a non-empty set that\n * excludes the resolved wallet). See {@link checkModelWalletEligibility}.\n */\nexport function serviceableModelRoutes(model: string): string[] {\n const routes = new Set<string>()\n const provider = getModelProvider(model)\n if (provider) routes.add(provider)\n const parsed = tryParseModelRef(normalizeRouterPrefixedId(model))\n if (parsed) {\n // An explicit `@route` in the id is itself a serviceable route (and keeps\n // the `getModelProvider` quirk on `<vendor>/<product>@<router>` forms from\n // hiding the route the operator literally named).\n if (parsed.route !== parsed.vendor) routes.add(parsed.route)\n // A first-party vendor model whose `<vendor>/<product>` form COLLIDES with a\n // router-namespaced pricing key hides its own direct vendor route: OpenRouter\n // keys e.g. `anthropic/claude-sonnet-5` / `anthropic/claude-fable-5` with the\n // SAME dash spelling Anthropic uses, tagged `provider:\"openrouter\"`, so\n // `getModelProvider(\"anthropic/claude-sonnet-5\")` resolves to the ROUTER and\n // `anthropic` never enters `routes` above. Restore it: when the BARE product\n // is itself a first-party model of this vendor — a VERBATIM first-party\n // pricing key (`resolvePricingExact(product).provider === vendor`) — the\n // vendor's own SDK/wallet can bill it, so the direct vendor route genuinely\n // IS serviceable. This MUST be the exact lookup, never `resolvePricing`'s\n // substring fallback: an openrouter-only sibling variant like\n // `google/gemini-2.5-flash-image` substring-hits the unrelated\n // `gemini-2.5-flash` row (provider `google`) and would spuriously earn a\n // `google` direct route it cannot actually be billed on. A gateway-only\n // slash id (`deepseek/deepseek-v4-pro` — no bare first-party pricing row)\n // likewise yields no exact match and is left untouched, preserving the\n // money-safety guard's reject on a true router-only model. Scoped to this\n // function: `resolvePricingExact`/`getModelProvider` semantics are unchanged\n // for every other caller.\n if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {\n routes.add(parsed.vendor)\n }\n for (const router of WIDENING_ROUTES) {\n if (tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}@${router}`)) {\n routes.add(router)\n }\n }\n }\n return [...routes]\n}\n\n/** The bare product of a model id — the segment after the last `/`, stripped\n * of any `@route` / `:pin` suffix, lower-cased. `deepseek/deepseek-chat` →\n * `deepseek-chat`; `z-ai/glm-5.2@openrouter` → `glm-5.2`; `claude-opus-4-8`\n * (no slash) → `claude-opus-4-8`. This is the identity the \"did you mean\"\n * matcher keys on, so a wrong-or-missing vendor/route prefix collapses onto\n * the same product. */\nfunction bareProduct(id: string): string {\n const noSuffix = id.split(\"@\")[0]!.split(\":\")[0]!\n const slash = noSuffix.lastIndexOf(\"/\")\n return (slash === -1 ? noSuffix : noSuffix.slice(slash + 1)).toLowerCase()\n}\n\n/**\n * Closest known catalog model ids for a slug UNKNOWN to the local catalog —\n * the \"did you mean\" set behind the spawn-time model advisory (session-spawn.ts).\n *\n * Deliberately narrow to keep false positives near zero: a known id qualifies\n * ONLY when its {@link bareProduct} equals the input's while the full id\n * differs — i.e. the caller used the wrong-or-missing vendor/route prefix\n * (`deepseek-chat` → `deepseek/deepseek-chat`, `glm-5.2` → `z-ai/glm-5.2`,\n * `moonshot/kimi-k2` → `moonshotai/kimi-k2`). A genuinely-new model on a known\n * vendor (`qwen/qwen4-max` when only `qwen3-max` is catalogued) shares no bare\n * product and yields nothing — so a new / free-form slug never earns a spurious\n * suggestion, matching the money-safety guard's own never-reject-an-unknown-\n * model rule. Sourced from the catalog itself (`LLM_PRICING_CATALOG` +\n * `MODEL_ALIASES` keys), never a hand-maintained second list.\n */\nexport function suggestModelSlugs(model: string): string[] {\n const target = bareProduct(model)\n if (!target) return []\n const out = new Set<string>()\n for (const id of [...Object.keys(LLM_PRICING_CATALOG), ...Object.keys(MODEL_ALIASES)]) {\n if (id !== model && bareProduct(id) === target) out.add(id)\n }\n return [...out].sort().slice(0, 5)\n}\n\n/** Verdict of the spawn-time money-safety guard ({@link checkModelWalletEligibility}). */\nexport interface ModelWalletEligibility {\n ok: boolean\n /** Serviceable routes for the model that DIFFER from the resolved wallet —\n * the actionable set to re-spawn onto. Empty when `ok`. */\n suggestedRoutes: string[]\n}\n\n/**\n * Money-safety spawn guard (SPEC §1c): can the resolved billing wallet\n * `walletRoute` (the gateway id when a `route.gateway` is set, else the resolved\n * billing provider) service `model`? Reuses {@link serviceableModelRoutes} — no\n * parallel table. Returns `ok:true` when the model is serviceable on the wallet,\n * OR when the model is unknown to the catalog (empty serviceable set — a mismatch\n * cannot be positively proven, so the guard must not reject a possibly-legitimate\n * new model). Returns `ok:false` with the serviceable alternative routes ONLY\n * when the model IS serviceable on some route but NOT the resolved wallet — the\n * exact 404-upstream case (`deepseek/deepseek-v4-pro` on the Anthropic sub). The\n * guard only REJECTS; it never substitutes a wallet the operator didn't name.\n */\nexport function checkModelWalletEligibility(\n model: string,\n walletRoute: string,\n): ModelWalletEligibility {\n const serviceable = serviceableModelRoutes(model)\n if (serviceable.length === 0 || serviceable.includes(walletRoute)) {\n return { ok: true, suggestedRoutes: [] }\n }\n return { ok: false, suggestedRoutes: serviceable.filter(r => r !== walletRoute) }\n}\n\n/**\n * The actionable fail-fast message shared by both spawn paths (session-spawn +\n * session-restart-core) so they never drift. Names the wallet that couldn't\n * service the model AND the required route + api-key profile to re-spawn onto —\n * never a wallet the guard picked for the operator.\n */\nexport function modelWalletIneligibleMessage(opts: {\n prefix: string\n adapter: string\n model: string\n walletRoute: string\n walletMode?: \"subscription\" | \"api-key\"\n suggestedRoutes: string[]\n}): string {\n const wallet = opts.walletMode\n ? `\"${opts.walletRoute}\" ${opts.walletMode} wallet`\n : `\"${opts.walletRoute}\" wallet`\n const primary = opts.suggestedRoutes[0] ?? \"a gateway route\"\n const also =\n opts.suggestedRoutes.length > 1\n ? ` (also serviceable via ${opts.suggestedRoutes\n .slice(1)\n .map(r => `\"${r}\"`)\n .join(\", \")})`\n : \"\"\n return (\n `${opts.prefix}: model \"${opts.model}\" is not serviceable on the resolved ${wallet} ` +\n `(adapter \"${opts.adapter}\") and would 404 upstream. This model bills route \"${primary}\"${also} — ` +\n `re-spawn on it: set route.gateway=\"${primary}\" with an eligible \"${primary}\" api-key profile ` +\n `(access.profileRef). This guard only rejects; it never switches wallets for you.`\n )\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, dependency-light workspace command-allowlist logic, shared by the
|
|
3
|
+
* daemon's `command_execute` MCP tool (`command-tools.ts`) and any other
|
|
4
|
+
* host that needs to gate shell execution against the same
|
|
5
|
+
* `<workspace>/.agentproto/allowed-commands.json` file — e.g. the
|
|
6
|
+
* `mastra-agent` adapter's own `command_execute` workspace tool, which runs
|
|
7
|
+
* out-of-process from the daemon and has no other way to reach this logic.
|
|
8
|
+
*
|
|
9
|
+
* Kept free of daemon-only concerns (session recording, PR provenance,
|
|
10
|
+
* OS-level sandboxing) so it stays cheap to import from a spawned child
|
|
11
|
+
* process — no `SessionsRegistry`, no `@agentproto/command-sandbox`.
|
|
12
|
+
*
|
|
13
|
+
* Allowlist file shape — each entry in `commands` is either a plain
|
|
14
|
+
* basename string (unconstrained args) or an object constraining the argv
|
|
15
|
+
* prefix:
|
|
16
|
+
* {
|
|
17
|
+
* "version": 1,
|
|
18
|
+
* "commands": [
|
|
19
|
+
* "claude",
|
|
20
|
+
* "node",
|
|
21
|
+
* { "command": "git", "args": ["status"] }
|
|
22
|
+
* ]
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* Match is by command BASENAME. A plain string entry allows that basename
|
|
26
|
+
* with ANY args. An object entry with `args` only allows invocations whose
|
|
27
|
+
* argv starts with exactly that token sequence (a prefix match). When a
|
|
28
|
+
* basename has BOTH a plain entry and constrained entries, the plain entry
|
|
29
|
+
* wins.
|
|
30
|
+
*/
|
|
31
|
+
declare const ALLOWLIST_REL = ".agentproto/allowed-commands.json";
|
|
32
|
+
/** One normalized allowlist entry — a basename with an optional argv-prefix
|
|
33
|
+
* constraint. `args` absent ⇒ unconstrained (any args), matching a plain
|
|
34
|
+
* basename-string entry in the JSON file. */
|
|
35
|
+
interface AllowlistEntry {
|
|
36
|
+
command: string;
|
|
37
|
+
args?: string[];
|
|
38
|
+
}
|
|
39
|
+
/** Load the workspace's allowlist as normalized entries (basename +
|
|
40
|
+
* optional argv-prefix constraint). Cheap stat-cached, same as
|
|
41
|
+
* `loadAllowlist`; both share one cache keyed on the file's mtime. */
|
|
42
|
+
declare function loadAllowlistEntries(workspace: string): Promise<AllowlistEntry[]>;
|
|
43
|
+
/** Basename-only view of the allowlist — every basename that has AT LEAST
|
|
44
|
+
* ONE entry, constrained or not. Existing callers (cron-scheduler.ts,
|
|
45
|
+
* task-ledger.ts, supervisor.ts) gate on basename alone, same as before
|
|
46
|
+
* this change; only `command_execute` itself enforces argv constraints
|
|
47
|
+
* (see `isCommandAllowed`). */
|
|
48
|
+
declare function loadAllowlist(workspace: string): Promise<Set<string>>;
|
|
49
|
+
/** Argv-aware allowlist check used by `command_execute`. A basename with
|
|
50
|
+
* no matching entry ⇒ denied. A basename with any unconstrained entry
|
|
51
|
+
* (plain string, or object with no `args`) ⇒ allowed regardless of args.
|
|
52
|
+
* Otherwise every matching entry is constrained, so `args` must match
|
|
53
|
+
* one of them as a prefix. */
|
|
54
|
+
declare function isCommandAllowed(entries: readonly AllowlistEntry[], baseName: string, args: readonly string[]): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Command basenames that execute arbitrary code and read the filesystem
|
|
57
|
+
* unrestricted. Allowlisting one grants a caller full host code execution +
|
|
58
|
+
* FS read — the workspace cwd-anchor bounds only the working directory, not
|
|
59
|
+
* what the interpreter itself opens. Used to surface a one-time warning
|
|
60
|
+
* (see `isInterpreterBasename` / `interpreterExecWarning`) until an OS-level
|
|
61
|
+
* command sandbox lands.
|
|
62
|
+
*/
|
|
63
|
+
declare const INTERPRETER_BASENAMES: ReadonlySet<string>;
|
|
64
|
+
/** True when `name` (a command basename) is a code interpreter. Case-insensitive
|
|
65
|
+
* so `Rscript`/`RSCRIPT` match. */
|
|
66
|
+
declare function isInterpreterBasename(name: string): boolean;
|
|
67
|
+
/** Human-readable warning for allowlisting/running an interpreter. */
|
|
68
|
+
declare function interpreterExecWarning(baseName: string): string;
|
|
69
|
+
|
|
70
|
+
export { ALLOWLIST_REL, type AllowlistEntry, INTERPRETER_BASENAMES, interpreterExecWarning, isCommandAllowed, isInterpreterBasename, loadAllowlist, loadAllowlistEntries };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { stat, readFile } from 'fs/promises';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @agentproto/runtime v0.1.0-alpha
|
|
7
|
+
* Long-running gateway: MCP server + HTTP transport + HEARTBEAT autonomy + conversation persistence over a workspace dir.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var ALLOWLIST_REL = ".agentproto/allowed-commands.json";
|
|
11
|
+
var allowlistCache = null;
|
|
12
|
+
function normalizeAllowlistEntry(raw) {
|
|
13
|
+
if (typeof raw === "string") {
|
|
14
|
+
const command2 = raw.trim();
|
|
15
|
+
return command2.length > 0 ? { command: command2 } : void 0;
|
|
16
|
+
}
|
|
17
|
+
if (typeof raw.command !== "string" || raw.command.trim().length === 0) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
const command = raw.command.trim();
|
|
21
|
+
if (raw.args === void 0) return { command };
|
|
22
|
+
if (!Array.isArray(raw.args) || !raw.args.every((a) => typeof a === "string")) {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
return { command, args: [...raw.args] };
|
|
26
|
+
}
|
|
27
|
+
async function loadAllowlistEntries(workspace) {
|
|
28
|
+
const path = resolve(workspace, ALLOWLIST_REL);
|
|
29
|
+
if (!existsSync(path)) {
|
|
30
|
+
allowlistCache = null;
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const s = await stat(path);
|
|
35
|
+
if (allowlistCache && allowlistCache.path === path && allowlistCache.entry.mtimeMs === s.mtimeMs) {
|
|
36
|
+
return allowlistCache.entry.entries;
|
|
37
|
+
}
|
|
38
|
+
const raw = await readFile(path, "utf8");
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
const list = Array.isArray(parsed.commands) ? parsed.commands : [];
|
|
41
|
+
const entries = list.map(normalizeAllowlistEntry).filter((e) => e !== void 0);
|
|
42
|
+
allowlistCache = { path, entry: { mtimeMs: s.mtimeMs, entries } };
|
|
43
|
+
return entries;
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(
|
|
46
|
+
`[command-allowlist] failed to load ${ALLOWLIST_REL} (will deny all):`,
|
|
47
|
+
err
|
|
48
|
+
);
|
|
49
|
+
allowlistCache = null;
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function loadAllowlist(workspace) {
|
|
54
|
+
const entries = await loadAllowlistEntries(workspace);
|
|
55
|
+
return new Set(entries.map((e) => e.command));
|
|
56
|
+
}
|
|
57
|
+
function argsMatchPrefix(pattern, actual) {
|
|
58
|
+
if (pattern.length > actual.length) return false;
|
|
59
|
+
return pattern.every((tok, i) => actual[i] === tok);
|
|
60
|
+
}
|
|
61
|
+
function isCommandAllowed(entries, baseName, args) {
|
|
62
|
+
const matching = entries.filter((e) => e.command === baseName);
|
|
63
|
+
if (matching.length === 0) return false;
|
|
64
|
+
if (matching.some((e) => e.args === void 0)) return true;
|
|
65
|
+
return matching.some((e) => argsMatchPrefix(e.args, args));
|
|
66
|
+
}
|
|
67
|
+
var INTERPRETER_BASENAMES = /* @__PURE__ */ new Set([
|
|
68
|
+
"bash",
|
|
69
|
+
"sh",
|
|
70
|
+
"zsh",
|
|
71
|
+
"dash",
|
|
72
|
+
"ksh",
|
|
73
|
+
"fish",
|
|
74
|
+
"node",
|
|
75
|
+
"deno",
|
|
76
|
+
"bun",
|
|
77
|
+
"tsx",
|
|
78
|
+
"ts-node",
|
|
79
|
+
"python",
|
|
80
|
+
"python2",
|
|
81
|
+
"python3",
|
|
82
|
+
"ruby",
|
|
83
|
+
"perl",
|
|
84
|
+
"php",
|
|
85
|
+
"rscript",
|
|
86
|
+
"osascript",
|
|
87
|
+
"env",
|
|
88
|
+
"xargs",
|
|
89
|
+
"make",
|
|
90
|
+
"npx",
|
|
91
|
+
"uv",
|
|
92
|
+
"uvx",
|
|
93
|
+
"pipx"
|
|
94
|
+
]);
|
|
95
|
+
function isInterpreterBasename(name) {
|
|
96
|
+
return INTERPRETER_BASENAMES.has(name.toLowerCase());
|
|
97
|
+
}
|
|
98
|
+
function interpreterExecWarning(baseName) {
|
|
99
|
+
return `command_execute ran the interpreter '${baseName}', which executes arbitrary code and can read files outside the workspace \u2014 the cwd anchor does not confine it. Allowlisting interpreters grants full host code execution; prefer allowlisting specific tools. An OS-level command sandbox is planned as the real confinement.`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export { ALLOWLIST_REL, INTERPRETER_BASENAMES, interpreterExecWarning, isCommandAllowed, isInterpreterBasename, loadAllowlist, loadAllowlistEntries };
|
|
103
|
+
//# sourceMappingURL=command-allowlist.mjs.map
|
|
104
|
+
//# sourceMappingURL=command-allowlist.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/command-allowlist.ts"],"names":["command"],"mappings":"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","file":"command-allowlist.mjs","sourcesContent":["/**\n * Pure, dependency-light workspace command-allowlist logic, shared by the\n * daemon's `command_execute` MCP tool (`command-tools.ts`) and any other\n * host that needs to gate shell execution against the same\n * `<workspace>/.agentproto/allowed-commands.json` file — e.g. the\n * `mastra-agent` adapter's own `command_execute` workspace tool, which runs\n * out-of-process from the daemon and has no other way to reach this logic.\n *\n * Kept free of daemon-only concerns (session recording, PR provenance,\n * OS-level sandboxing) so it stays cheap to import from a spawned child\n * process — no `SessionsRegistry`, no `@agentproto/command-sandbox`.\n *\n * Allowlist file shape — each entry in `commands` is either a plain\n * basename string (unconstrained args) or an object constraining the argv\n * prefix:\n * {\n * \"version\": 1,\n * \"commands\": [\n * \"claude\",\n * \"node\",\n * { \"command\": \"git\", \"args\": [\"status\"] }\n * ]\n * }\n *\n * Match is by command BASENAME. A plain string entry allows that basename\n * with ANY args. An object entry with `args` only allows invocations whose\n * argv starts with exactly that token sequence (a prefix match). When a\n * basename has BOTH a plain entry and constrained entries, the plain entry\n * wins.\n */\n\nimport { existsSync } from \"node:fs\"\nimport { readFile, stat } from \"node:fs/promises\"\nimport { resolve } from \"node:path\"\n\nexport const ALLOWLIST_REL = \".agentproto/allowed-commands.json\"\n\n/** One normalized allowlist entry — a basename with an optional argv-prefix\n * constraint. `args` absent ⇒ unconstrained (any args), matching a plain\n * basename-string entry in the JSON file. */\nexport interface AllowlistEntry {\n command: string\n args?: string[]\n}\n\ninterface AllowlistFile {\n version?: number\n commands?: Array<string | { command?: unknown; args?: unknown }>\n}\n\ninterface AllowlistCacheEntry {\n mtimeMs: number\n entries: AllowlistEntry[]\n}\n\nlet allowlistCache: { path: string; entry: AllowlistCacheEntry } | null = null\n\nfunction normalizeAllowlistEntry(\n raw: string | { command?: unknown; args?: unknown },\n): AllowlistEntry | undefined {\n if (typeof raw === \"string\") {\n const command = raw.trim()\n return command.length > 0 ? { command } : undefined\n }\n if (typeof raw.command !== \"string\" || raw.command.trim().length === 0) {\n return undefined\n }\n const command = raw.command.trim()\n if (raw.args === undefined) return { command }\n if (!Array.isArray(raw.args) || !raw.args.every(a => typeof a === \"string\")) {\n // Malformed `args` (not a string array) — drop the constraint rather\n // than silently allowlisting an unintended shape; the operator gets a\n // basename-only entry, which is at least not MORE permissive than\n // the array they wrote.\n return undefined\n }\n return { command, args: [...raw.args] }\n}\n\n/** Load the workspace's allowlist as normalized entries (basename +\n * optional argv-prefix constraint). Cheap stat-cached, same as\n * `loadAllowlist`; both share one cache keyed on the file's mtime. */\nexport async function loadAllowlistEntries(\n workspace: string,\n): Promise<AllowlistEntry[]> {\n const path = resolve(workspace, ALLOWLIST_REL)\n if (!existsSync(path)) {\n allowlistCache = null\n return []\n }\n try {\n const s = await stat(path)\n if (\n allowlistCache &&\n allowlistCache.path === path &&\n allowlistCache.entry.mtimeMs === s.mtimeMs\n ) {\n return allowlistCache.entry.entries\n }\n const raw = await readFile(path, \"utf8\")\n const parsed = JSON.parse(raw) as AllowlistFile\n const list = Array.isArray(parsed.commands) ? parsed.commands : []\n const entries = list\n .map(normalizeAllowlistEntry)\n .filter((e): e is AllowlistEntry => e !== undefined)\n allowlistCache = { path, entry: { mtimeMs: s.mtimeMs, entries } }\n return entries\n } catch (err) {\n // Bad JSON / unreadable file ⇒ deny all and surface in the error\n // the next caller gets. Don't poison the cache.\n console.error(\n `[command-allowlist] failed to load ${ALLOWLIST_REL} (will deny all):`,\n err,\n )\n allowlistCache = null\n return []\n }\n}\n\n/** Basename-only view of the allowlist — every basename that has AT LEAST\n * ONE entry, constrained or not. Existing callers (cron-scheduler.ts,\n * task-ledger.ts, supervisor.ts) gate on basename alone, same as before\n * this change; only `command_execute` itself enforces argv constraints\n * (see `isCommandAllowed`). */\nexport async function loadAllowlist(workspace: string): Promise<Set<string>> {\n const entries = await loadAllowlistEntries(workspace)\n return new Set(entries.map(e => e.command))\n}\n\n/** True when `pattern` (an allowed argv prefix) matches the start of\n * `actual` token-for-token. An empty `pattern` matches anything. */\nfunction argsMatchPrefix(pattern: readonly string[], actual: readonly string[]): boolean {\n if (pattern.length > actual.length) return false\n return pattern.every((tok, i) => actual[i] === tok)\n}\n\n/** Argv-aware allowlist check used by `command_execute`. A basename with\n * no matching entry ⇒ denied. A basename with any unconstrained entry\n * (plain string, or object with no `args`) ⇒ allowed regardless of args.\n * Otherwise every matching entry is constrained, so `args` must match\n * one of them as a prefix. */\nexport function isCommandAllowed(\n entries: readonly AllowlistEntry[],\n baseName: string,\n args: readonly string[],\n): boolean {\n const matching = entries.filter(e => e.command === baseName)\n if (matching.length === 0) return false\n if (matching.some(e => e.args === undefined)) return true\n return matching.some(e => argsMatchPrefix(e.args!, args))\n}\n\n/**\n * Command basenames that execute arbitrary code and read the filesystem\n * unrestricted. Allowlisting one grants a caller full host code execution +\n * FS read — the workspace cwd-anchor bounds only the working directory, not\n * what the interpreter itself opens. Used to surface a one-time warning\n * (see `isInterpreterBasename` / `interpreterExecWarning`) until an OS-level\n * command sandbox lands.\n */\nexport const INTERPRETER_BASENAMES: ReadonlySet<string> = new Set([\n \"bash\", \"sh\", \"zsh\", \"dash\", \"ksh\", \"fish\",\n \"node\", \"deno\", \"bun\", \"tsx\", \"ts-node\",\n \"python\", \"python2\", \"python3\", \"ruby\", \"perl\", \"php\", \"rscript\", \"osascript\",\n \"env\", \"xargs\", \"make\", \"npx\", \"uv\", \"uvx\", \"pipx\",\n])\n\n/** True when `name` (a command basename) is a code interpreter. Case-insensitive\n * so `Rscript`/`RSCRIPT` match. */\nexport function isInterpreterBasename(name: string): boolean {\n return INTERPRETER_BASENAMES.has(name.toLowerCase())\n}\n\n/** Human-readable warning for allowlisting/running an interpreter. */\nexport function interpreterExecWarning(baseName: string): string {\n return (\n `command_execute ran the interpreter '${baseName}', which executes ` +\n `arbitrary code and can read files outside the workspace — the cwd anchor ` +\n `does not confine it. Allowlisting interpreters grants full host code ` +\n `execution; prefer allowlisting specific tools. An OS-level command ` +\n `sandbox is planned as the real confinement.`\n )\n}\n"]}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as SpawnDefaultsConfig } from './spawn-defaults-
|
|
1
|
+
import { S as SpawnDefaultsConfig } from './spawn-defaults-CYJoeHeO.js';
|
|
2
2
|
import '@agentproto/model-catalog';
|
|
3
3
|
import './context-continuity-B9n0t0v-.js';
|
|
4
4
|
|
|
@@ -155,6 +155,11 @@ interface FeaturesConfig {
|
|
|
155
155
|
/** Hint that PTY is desired — informational; the daemon still
|
|
156
156
|
* detects node-pty's presence at runtime. */
|
|
157
157
|
pty?: boolean;
|
|
158
|
+
/** Enable the local LLM Endpoint proxy sidecar (route registration,
|
|
159
|
+
* MCP tools, child-process lifecycle). Default false — the endpoint is
|
|
160
|
+
* an opt-in feature; when off, the `llm-endpoint` custom route is not
|
|
161
|
+
* registered and the `llm_endpoint_*` MCP tools are not exposed. */
|
|
162
|
+
llmEndpoint?: boolean;
|
|
158
163
|
}
|
|
159
164
|
/**
|
|
160
165
|
* Policy for `agentproto worktree new` (PLAN.md §1.4 — config carries
|