@agentproto/runtime 2.10.0 → 2.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/catalog-models.d.ts +7 -1
- package/dist/catalog-models.mjs +1 -1
- package/dist/catalog-models.mjs.map +1 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +78 -57
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/catalog-models.d.ts
CHANGED
|
@@ -38,6 +38,12 @@ import './context-continuity-ib9_bVYM.js';
|
|
|
38
38
|
* per-adapter gateway-vendor tables.
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
+
/** Routers the catalog probes to widen beyond any adapter's declared model
|
|
42
|
+
* list (SPEC §5.1) — same three route-identity widens, `route-identity/
|
|
43
|
+
* index.ts:54-59`. Exported so `model-wire.ts` can reuse the SAME set when
|
|
44
|
+
* deciding whether a wire model needs a literal router-prefix (never a
|
|
45
|
+
* second hand-maintained list). */
|
|
46
|
+
declare const WIDENING_ROUTES: readonly ["openrouter", "requesty", "huggingface"];
|
|
41
47
|
/** One model entry as declared in an adapter's `models.allowed`
|
|
42
48
|
* (`AdapterModelInfo`, `packages/cli/src/registry/resolve.ts:134-142`) —
|
|
43
49
|
* the subset this module needs. */
|
|
@@ -312,4 +318,4 @@ declare function modelWalletIneligibleMessage(opts: {
|
|
|
312
318
|
suggestedRoutes: string[];
|
|
313
319
|
}): string;
|
|
314
320
|
|
|
315
|
-
export { type BuildCatalogModelsInput, type CatalogAdapterInput, type CatalogAdapterModelInput, type CatalogModelsQuery, type CatalogModelsResponse, type CatalogPricing, type CatalogProduct, type CatalogRoute, type CatalogRouteSummary, type CatalogVendor, type ModelAdapterEligibility, type ModelWalletEligibility, RouteSpec, buildCatalogModels, checkModelAdapterEligibility, checkModelWalletEligibility, modelAdapterIncompatibleMessage, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
|
321
|
+
export { type BuildCatalogModelsInput, type CatalogAdapterInput, type CatalogAdapterModelInput, type CatalogModelsQuery, type CatalogModelsResponse, type CatalogPricing, type CatalogProduct, type CatalogRoute, type CatalogRouteSummary, type CatalogVendor, type ModelAdapterEligibility, type ModelWalletEligibility, RouteSpec, WIDENING_ROUTES, buildCatalogModels, checkModelAdapterEligibility, checkModelWalletEligibility, modelAdapterIncompatibleMessage, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
package/dist/catalog-models.mjs
CHANGED
|
@@ -430,6 +430,6 @@ function modelWalletIneligibleMessage(opts) {
|
|
|
430
430
|
return `${opts.prefix}: model "${opts.model}" is not serviceable on the resolved ${wallet} (adapter "${opts.adapter}") and would 404 upstream. This model bills route "${primary}"${also} \u2014 re-spawn on it: set route.gateway="${primary}" with an eligible "${primary}" api-key profile (access.profileRef). This guard only rejects; it never switches wallets for you.`;
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
export { buildCatalogModels, checkModelAdapterEligibility, checkModelWalletEligibility, modelAdapterIncompatibleMessage, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
|
433
|
+
export { WIDENING_ROUTES, buildCatalogModels, checkModelAdapterEligibility, checkModelWalletEligibility, modelAdapterIncompatibleMessage, modelWalletIneligibleMessage, modelWithRoute, reconcileModelRoute, resolveEffectiveRoute, serviceableModelRoutes, suggestModelSlugs };
|
|
434
434
|
//# sourceMappingURL=catalog-models.mjs.map
|
|
435
435
|
//# sourceMappingURL=catalog-models.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/providers-store.ts","../src/spawn-defaults.ts","../src/catalog-models.ts"],"names":["routes","getModelProvider"],"mappings":"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","file":"catalog-models.mjs","sourcesContent":["/**\n * Re-exports `@agentproto/providers-store` unchanged. Moved out of `runtime`\n * so a standalone process (e.g. `@agentproto/llm-endpoint`, which has zero\n * runtime deps) can read the provider-key store without depending on the\n * whole daemon. Kept as a same-named module (rather than deleting it) so the\n * `@agentproto/runtime/providers-store` subpath and every existing relative\n * `./providers-store.js` import in this package keep working.\n */\nexport * from \"@agentproto/providers-store\"\n","/**\n * Pure resolver for `~/.agentproto/config.json`'s `defaults` block —\n * computes the effective `skills` + `options` + `auth` for an `agent_start`\n * spawn before adapter-specific normalization. No fs, no adapter I/O, so\n * it's unit-testable in isolation from `session-spawn.ts` (which owns the\n * fs read + the adapter-manifest lookup).\n *\n * Precedence (lowest → highest): global `defaults` < `defaults.adapters.\n * <slug>` < the explicit `agent_start` call.\n * - `options`: shallow-merged maps, later (higher-precedence) keys win.\n * - `skills`: global ∪ per-adapter when the caller didn't pass `skills`\n * at all; an explicit `skills` (even `[]`) REPLACES the union rather\n * than merging into it — a deliberate exact set, mirroring how an\n * explicit `mcpServers: []` opts out of the hermes default in\n * `session-spawn.ts`.\n * - `auth`: surfaces the RAW billing-auth material (requested mode, both\n * candidate credentials, per-spawn provider pin, and the `explicit`\n * signal) so the descriptor-aware resolver ({@link resolveAuthSpec},\n * which also needs the adapter's provider/subscription descriptor +\n * providers.json) can decide the final mode, env var, scrub set, and\n * credential source. Credentials are named in config or the provider\n * store — never read from the ambient shell env.\n */\n\nimport { getModelProvider } from \"@agentproto/model-catalog/llm\"\nimport type { CatalogProvider } from \"@agentproto/model-catalog\"\nimport { resolveCustomRoute } from \"@agentproto/model-catalog/route-identity\"\nimport { findAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport { providerEnvVar } from \"./providers-store.js\"\nimport type { ContextContinuityPolicy } from \"./context-continuity.js\"\n\n/**\n * Deterministic billing-auth config for one adapter slug (today, only\n * claude-code interprets it — see `AgentCliStartOptions.auth` in\n * `@agentproto/driver-agent-cli`). EXPLICIT credential selection, not\n * scrub-by-absence: `token`/`apiKey` are the actual secret values, named\n * here (or supplied per-spawn) rather than inherited from the launching\n * shell. Never logged; only a fingerprint (see {@link credentialFingerprint})\n * is ever surfaced back to a caller.\n */\nexport interface DefaultsAdapterAuthConfig {\n /** `\"subscription\"` or `\"api-key\"`. Omitted ⇒ the resolver picks by\n * ordered preference (subscription first for adapters that support it —\n * see {@link resolveAuthSpec}), never a hardcoded default. */\n mode?: \"subscription\" | \"api-key\"\n /** The subscription bearer token for `\"subscription\"` mode — minted via\n * `claude setup-token` (bills the Max/Pro subscription, not API credits),\n * SET to the adapter's `authSubscription.setEnv`. */\n token?: string\n /** Opt-in SELF-REFRESHING subscription source (Mode 3). When set to\n * `\"claude-code-oauth\"` and no static `token` is supplied per-spawn, the\n * subscription bearer is read FRESH on every spawn from the local Claude\n * Code login (macOS Keychain `Claude Code-credentials` → jsonPath\n * `claudeAiOauth.accessToken`, falling back to `~/.claude/.credentials.json`)\n * via the `claude-code-oauth` provision recipe — because Claude Code keeps\n * that item refreshed, agentproto gets a fresh token each spawn. Absent ⇒\n * today's static-bearer behavior (Mode 2). Only `\"claude-code-oauth\"` is\n * understood; any other value fails LOUD (never a silent fallthrough). See\n * {@link resolveSubscriptionCredential} for the precedence with `token`. */\n source?: string\n /** Explicit API key for `\"api-key\"` mode, SET to `providerEnvVar(provider)`.\n * Wins over the `providers.json` store key for the same provider. */\n apiKey?: string\n /** Per-spawn provider PIN — overrides the adapter's fixed provider and the\n * model-derived provider (the sharp edge for by-model routers whose config\n * routes a catalog-\"anthropic\" model elsewhere). A `CatalogProvider` id. */\n provider?: CatalogProvider\n}\n\nexport interface DefaultsAdapterConfig {\n skills?: string[]\n options?: Record<string, boolean | number | string>\n auth?: DefaultsAdapterAuthConfig\n contextContinuity?: ContextContinuityPolicy\n}\n\n/** Shape of `config.json`'s top-level `defaults` block. */\nexport interface SpawnDefaultsConfig {\n skills?: string[]\n options?: Record<string, boolean | number | string>\n adapters?: Record<string, DefaultsAdapterConfig>\n contextContinuity?: ContextContinuityPolicy\n /** Depth cutoff for the role-derived default (see `resolveRole` in\n * `role.ts`) applied when an `agent_start` call omits `role`:\n * `depth < cutoff` → supervisor, `depth >= cutoff` → executor.\n * Default 1 (root spawns keep today's unrestricted behaviour; any\n * spawn made THROUGH an orchestrator defaults to executor). Tune\n * this up (e.g. to a large number) to restore the old permissive\n * behaviour for existing deep spawns wholesale. */\n defaultRoleDepthCutoff?: number\n /** Trust-boundary cap on pack-carried roles (see `role-registry.ts`'s\n * `loadRoleRegistry`): a role pack whose `toolPolicy.delegation` is\n * `\"allow\"` at a level ABOVE this cap has it forced to `\"deny\"` —\n * the pack can still declare the intent, the daemon just refuses to\n * grant it. Lets an operator install third-party role packs without\n * trusting every one of them to self-grant delegation. Undefined\n * (default) ⇒ no cap, any pack-declared level may carry\n * `delegation: \"allow\"` (back-compat: #214 had no such knob). */\n maxGrantableDelegation?: number\n /** Default per-session Langfuse tracing opt-in when an `agent_start` call\n * omits `trace`. Default false — sessions trace only when they opt in or\n * this is on. See `filterSessionObserver` / `SpawnAgentInput.trace`. */\n langfuseTracing?: boolean\n /** Redactor slug applied to traced session content before it's sent to\n * Langfuse (see `@agentproto/redaction`'s registry). Default \"secrets\"\n * (deny-list by key + value-scan for secret shapes). */\n traceRedactor?: string\n}\n\nexport interface ResolveSpawnDefaultsInput {\n /** Explicit-call `skills`. Undefined ⇒ caller expressed no preference,\n * fall through to the config union. Provided ⇒ replaces it outright. */\n skills?: string[]\n /** Explicit-call AIP-45 `options` map — wins per-key over both the\n * global and per-adapter config defaults. */\n options?: Record<string, boolean | number | string>\n /** Explicit-call `agent_start.auth` override. `mode` wins over\n * `defaults.adapters.<slug>.auth.mode`; the credential field matching the\n * RESOLVED mode wins over the matching config field. Undefined ⇒ falls\n * through entirely to the per-adapter config default. */\n auth?: DefaultsAdapterAuthConfig\n /** Explicit-call context-continuity policy — wins over global and\n * per-adapter config defaults. */\n contextContinuity?: ContextContinuityPolicy\n}\n\nexport interface ResolvedSpawnDefaults {\n skills: string[]\n options: Record<string, boolean | number | string>\n /** RAW billing-auth material (config precedence applied) — fed to the\n * descriptor-aware {@link resolveAuthSpec}, which owns the final mode /\n * env / scrub / credential-source decision. Both candidate credentials\n * are surfaced (NOT collapsed to one), since the ordered-mode selection\n * needs to know which are available before it picks the mode. */\n auth: ResolvedSpawnAuthMaterial\n contextContinuity?: ContextContinuityPolicy\n}\n\nexport interface ResolvedSpawnAuthMaterial {\n /** Operator-requested mode (per-spawn > per-adapter config), or undefined\n * ⇒ let the resolver pick by ordered preference. */\n requestedMode?: \"subscription\" | \"api-key\"\n /** True when the operator explicitly configured `auth` (per-spawn OR in\n * `defaults.adapters.<slug>.auth`). The ONLY way to tell \"set mode, no\n * key\" (fail-fast) from \"set nothing\" (ambient) — both give no credential.\n * DECISION 5. */\n explicit: boolean\n /** Subscription bearer token (per-spawn > config), if configured. STATIC\n * material only — the self-refreshing {@link subscriptionSource} is resolved\n * separately (and impurely) by the caller. */\n subscriptionCredential?: string\n /** Opt-in self-refreshing subscription source (per-spawn > config), if\n * configured — e.g. `\"claude-code-oauth\"`. Surfaced RAW; the impure caller\n * resolves it to a fresh token via {@link resolveSubscriptionCredential}. */\n subscriptionSource?: string\n /** Explicit API key (per-spawn > config), if configured — distinct from the\n * providers.json store key the resolver fetches separately. */\n apiKeyCredential?: string\n /** Per-spawn provider pin, if given. */\n provider?: CatalogProvider\n}\n\nexport function resolveSpawnDefaults(\n defaults: SpawnDefaultsConfig | undefined,\n adapterSlug: string,\n input: ResolveSpawnDefaultsInput,\n): ResolvedSpawnDefaults {\n const adapterDefaults = defaults?.adapters?.[adapterSlug]\n\n const options: Record<string, boolean | number | string> = {\n ...defaults?.options,\n ...adapterDefaults?.options,\n ...input.options,\n }\n\n const skills =\n input.skills !== undefined\n ? input.skills\n : Array.from(\n new Set([...(defaults?.skills ?? []), ...(adapterDefaults?.skills ?? [])]),\n )\n\n const requestedMode = input.auth?.mode ?? adapterDefaults?.auth?.mode\n const explicit = input.auth !== undefined || adapterDefaults?.auth !== undefined\n const subscriptionCredential = input.auth?.token ?? adapterDefaults?.auth?.token\n const subscriptionSource = input.auth?.source ?? adapterDefaults?.auth?.source\n const apiKeyCredential = input.auth?.apiKey ?? adapterDefaults?.auth?.apiKey\n const authProvider = input.auth?.provider ?? adapterDefaults?.auth?.provider\n\n const contextContinuity =\n input.contextContinuity ?? adapterDefaults?.contextContinuity ?? defaults?.contextContinuity\n\n return {\n skills,\n options,\n auth: {\n explicit,\n ...(requestedMode ? { requestedMode } : {}),\n ...(subscriptionCredential !== undefined ? { subscriptionCredential } : {}),\n ...(subscriptionSource !== undefined ? { subscriptionSource } : {}),\n ...(apiKeyCredential !== undefined ? { apiKeyCredential } : {}),\n ...(authProvider ? { provider: authProvider } : {}),\n },\n ...(contextContinuity !== undefined ? { contextContinuity } : {}),\n }\n}\n\n/**\n * Public credential key-shape prefixes, longest / most-specific first (the\n * match is first-hit, so `sk-ant-oat` must precede `sk-ant-api`/`sk-ant-`,\n * `sk-or-v1-` precede `sk-or-`, and both precede the bare `sk-`). These are\n * PUBLIC key-shape knowledge — the visible leading bytes of each provider's\n * credential format — never secret; only these + the last 4 chars are ever\n * revealed. DECISION 6: the fingerprint is derived from the credential's own\n * shape, NOT from `mode`, so it stays honest across providers (a gateway\n * `sk-or-…` key run in api-key mode reads as `api-key · sk-or-…`).\n */\nconst CREDENTIAL_FINGERPRINT_PREFIXES: readonly string[] = [\n \"sk-ant-oat\",\n \"sk-ant-api\",\n \"sk-ant-\",\n \"sk-or-v1-\",\n \"sk-or-\",\n \"sk-proj-\",\n \"sk-\",\n \"gsk_\",\n \"AIza\",\n]\n\n/**\n * Derive a SAFE, non-secret fingerprint for a resolved auth credential —\n * NEVER the raw value — for recording on the session descriptor / surfacing\n * in `agentproto sessions --watch` and `agent_sessions_list` (the\n * \"verifiability\" requirement: answer \"what was used\" without exposing the\n * secret). Format: `<mode> · <shape-prefix>…<last4>` when the shape is known\n * (e.g. `subscription · sk-ant-oat…3f9c`), else `<mode> · …<last4>`.\n *\n * The shape marker is matched from a PUBLIC key-prefix table (longest-match),\n * not derived from `mode` — see {@link CREDENTIAL_FINGERPRINT_PREFIXES}. Only\n * the matched prefix + the last 4 characters are ever surfaced, never the\n * middle (mirrors GitHub's `ghp_…abcd` style).\n */\nexport function credentialFingerprint(\n mode: \"subscription\" | \"api-key\",\n credential: string,\n): string {\n const prefix = CREDENTIAL_FINGERPRINT_PREFIXES.find(p => credential.startsWith(p))\n const last4 = credential.slice(-4)\n return prefix ? `${mode} · ${prefix}…${last4}` : `${mode} · …${last4}`\n}\n\n/**\n * One subscription (OAuth/bearer) surface — mirrors the driver's\n * `AgentCliAuthSubscription`. `external: true` (codex/gemini/mastracode/\n * opencode) ⇒ file-based: the CLI reads its own local-login file, so\n * `setEnv` is absent and the runtime injects NO bearer — it only scrubs the\n * conflicting api-key vars.\n */\nexport interface AuthSubscriptionSurface {\n /** Env var SET to the resolved subscription credential — absent for an\n * {@link external} surface. */\n setEnv?: string\n external?: boolean\n conflictEnv?: string[]\n unsetEnvAdd?: string[]\n /** Provider scope for a multi-provider adapter's surface — pi's\n * `ANTHROPIC_OAUTH_TOKEN` is an anthropic-only door; mastracode/opencode's\n * file-based ChatGPT login is an openai-only door alongside their\n * anthropic one. When set, this surface only MATCHES a spawn whose\n * resolved provider equals this id (see {@link subscriptionSurfaceFor}).\n * Omitted for a fixed-provider adapter (claude-code, codex) — their\n * descriptor-level `provider` already pins it. */\n provider?: string\n}\n\n/**\n * The adapter's billing-auth capability, projected from its AIP-45 manifest\n * (`provider` / `authEnforce` / `authSubscription`) by the host resolver. The\n * runtime reads THIS, never the manifest directly — keeping the LLM-catalog\n * coupling in the runtime and the driver mechanical.\n */\nexport interface AdapterAuthDescriptor {\n /** FIXED provider for a single-provider adapter; omitted for by-model\n * routers (provider then derives from the requested model). */\n provider?: CatalogProvider\n /** Enforcement policy — `\"always\"` engages every spawn (claude-code's\n * #312 fail-fast); `\"when-configured\"` (default) only when `explicit`. */\n authEnforce?: \"always\" | \"when-configured\"\n /** Subscription (OAuth/bearer) support. Presence ⇒ the adapter supports\n * `\"subscription\"` mode. A SINGLE surface for a fixed/single-provider\n * subscription, or an ARRAY of surfaces for an adapter with more than one\n * native OAuth login (mastracode/opencode: both an anthropic- and an\n * openai-scoped file-based login) — see {@link subscriptionSurfaceFor},\n * which resolves the matching surface for a spawn's resolved provider. */\n authSubscription?: AuthSubscriptionSurface | AuthSubscriptionSurface[]\n /** True when the adapter's api-key auth is derived from the requested\n * model rather than a fixed provider (e.g. `pi`, `opencode`). When set,\n * the adapter supports `\"api-key\"` on the model-derived direct endpoint,\n * and `spawnEligibilityManifest` includes it for direct routes. */\n modelDerivedApiKey?: boolean\n /** How THIS adapter receives a GATEWAY-routed bearer credential — see\n * `AgentCliDefinition.gatewayAuth` (the manifest field this is projected\n * from). Distinct from a gateway preset's `keyEnv` (the providers-store\n * lookup key) and from `authSubscription.setEnv` (this adapter's own\n * native bearer). When a gateway route resolves, `resolveAuthSpec` injects\n * the credential into `gatewayAuth.setEnv` instead of the preset's\n * `keyEnv` — e.g. claude-code/claude-sdk read `ANTHROPIC_AUTH_TOKEN`.\n * Omit when the adapter reads the preset's own `keyEnv` directly (hermes). */\n gatewayAuth?: { setEnv: string }\n /** Model id → the BILLING provider THIS adapter itself declares for it\n * (`AgentCliModelEntry.provider`, projected from `models.allowed`) —\n * authoritative for a model-derived-api-key adapter (`pi`, `opencode`),\n * whose `provider` above is absent so the eligibility projection would\n * otherwise fall through to the GLOBAL catalog's own (possibly different)\n * routing for the same model id. Consulted by `spawnEligibilityManifest`\n * BEFORE the catalog fallback; absent/no-match ⇒ catalog derivation,\n * unchanged. */\n modelProviders?: Readonly<Record<string, CatalogProvider>>\n}\n\n/** The fully-resolved spec the driver applies mechanically. Structurally\n * matches `@agentproto/driver-agent-cli`'s `ResolvedAuthSpec` (each package\n * owns its own copy; the object flows across the boundary by shape). */\nexport interface ResolvedAuthSpec {\n mode: \"subscription\" | \"api-key\"\n credential?: string\n setEnv: string\n unsetEnv: string[]\n explicit: boolean\n enforce: \"always\" | \"when-configured\"\n /** True when NEITHER subscription nor api-key had any credential\n * available and `mode` above is therefore an arbitrary fallback pick —\n * never true when `mode` came from an explicit request or an actually-\n * available credential. Optional (undefined ⇒ false) so existing\n * callers/fixtures that predate this field keep working unchanged.\n * Exists so a fail-fast message can enumerate BOTH auth paths instead of\n * presenting the fallback mode as though the user configured it — the\n * root cause of a zero-credential user being told to buy a subscription\n * they never asked for. */\n neitherConfigured?: boolean\n /** File-based subscription (see `AdapterAuthDescriptor.authSubscription.\n * external`): the CLI reads its OWN local-login file, so the driver injects\n * NO credential — it only applies {@link unsetEnv} and does NOT fail-fast on\n * a missing `credential`. Money-safe: no bearer ever reaches an env var. */\n externalCredential?: boolean\n /** Non-authenticating hint: true when `providers.json` HAS a key for the\n * resolved provider that is currently being ignored because auth isn't\n * explicitly configured (no `defaults.adapters.<slug>.auth` block — the\n * `explicit` gate above, PR #321). NEVER used to authenticate — set by\n * the caller (session-spawn.ts) as a read-only peek, purely so the\n * fail-fast message can say \"you already have a key, it's just not\n * wired in\" instead of staying silent about it. `resolveAuthSpec` itself\n * never sets this (it does no I/O). */\n ignoredApiKeyInStore?: boolean\n /** When a gateway preset or custom route was matched, the `base_url` to\n * inject into adapter options so the client hits the gateway endpoint. */\n baseUrl?: string\n}\n\n/** Where the resolved credential came from — the observable billing axis\n * (DECISION 10②), never inferred. `\"claude-code-oauth\"` is the Mode-3\n * self-refreshing source: the subscription bearer was read fresh from the\n * local Claude Code login via the `claude-code-oauth` provision recipe.\n * `\"cli-local-login\"` is the file-based (external) subscription: the CLI\n * (codex/gemini) reads its OWN local-login file — the runtime injected no\n * bearer, it only verified the login is present and scrubbed api-key vars. */\nexport type CredentialSource =\n | \"explicit-config\"\n | \"providers-store\"\n | \"claude-code-oauth\"\n | \"cli-local-login\"\n | \"none\"\n\n/**\n * The OBSERVABLE echo (DECISION 9③ / 10②) — recorded on the session\n * descriptor so a verifier checks the RESOLUTION, never the model's\n * self-report. Never carries the raw credential (only its fingerprint).\n */\nexport interface AuthEcho {\n /** Billing endpoint / provider recorded for observability. Kept as `string`\n * (not the catalog enum) so gateway preset ids (e.g. \"moonshot\",\n * \"openai-direct\") that are not `CatalogProvider` values can still be\n * echoed on the session descriptor. */\n provider: string\n authMode: \"subscription\" | \"api-key\"\n credentialSource: CredentialSource\n setEnv: string\n fingerprint?: string\n}\n\n/**\n * Thrown when the operator requested a billing mode the adapter can't serve —\n * today only `\"subscription\"` on an adapter with no `authSubscription`. A\n * LOUD, distinct failure (DECISION 4②), never a silent downgrade to api-key.\n */\nexport class AuthResolutionError extends Error {\n readonly code = \"unsupported_auth_mode\"\n constructor(message: string) {\n super(message)\n this.name = \"AuthResolutionError\"\n }\n}\n\nexport interface ResolveAuthSpecInput {\n descriptor: AdapterAuthDescriptor\n /** `input.model ?? adapter default model` — for model-derived provider. */\n model?: string\n /** Per-spawn provider pin (`input.auth.provider`). */\n requestedProvider?: CatalogProvider\n /** Operator-requested mode; undefined ⇒ ordered preference. */\n requestedMode?: \"subscription\" | \"api-key\"\n /** Operator explicitly configured `auth` (DECISION 5). */\n explicit: boolean\n /** Subscription bearer credential, if configured (or resolved fresh by the\n * caller from a self-refreshing source). */\n subscriptionCredential?: string\n /** Observable ORIGIN label for `subscriptionCredential`, when it did not come\n * from a plain static config token — today only `\"claude-code-oauth\"` (the\n * caller resolved it fresh from the local Claude Code login). Purely a label\n * for the echo; NEVER affects mode/credential selection. Omitted ⇒ the\n * subscription credential (if any) is treated as `\"explicit-config\"`. */\n subscriptionCredentialSource?: CredentialSource\n /** File-based subscription only: the caller (impure) verified the CLI's OWN\n * local-login file is present (e.g. `~/.codex/auth.json` has a subscription\n * token) and fails LOUD before reaching here if not. Makes an `external`\n * `authSubscription` count as an available subscription for ordered-mode\n * selection — the login file IS the credential, even though the runtime\n * injects no bearer. Ignored for a non-external (bearer) authSubscription. */\n externalSubscriptionVerified?: boolean\n /** Explicit api-key credential from config, if configured. */\n apiKeyConfigCredential?: string\n /** api-key credential from `providers.json` (fetched by the caller). */\n apiKeyStoreCredential?: string\n /** Explicit gateway route from `SessionConfig.route.gateway`. When this\n * matches a `ProviderPreset` or a registered custom route, the route's\n * `baseUrl`/`keyEnv`/`scrubEnv` drive resolution instead of the model-\n * derived or fixed provider. */\n routeGateway?: string\n}\n\n/**\n * THE billing-auth resolver (DECISIONS 4, 6, 9, 10). Pure: given the adapter\n * descriptor + raw config material + (caller-fetched) store key, it decides\n * the provider, the mode (ordered — subscription over api-key when a\n * subscription credential is present; a requested-but-unsupported mode throws\n * `unsupported_auth_mode`), the env var to SET, the derived SCRUB set, and the\n * credential + its source. Returns the driver `spec` + the observable `echo`,\n * or `undefined` when no provider resolves (⇒ ambient, no injection — never\n * guess). NEVER falls back to a default provider/model. Fail-loud on a\n * configured-but-missing credential is deferred to the driver's mechanical\n * apply (it engages then throws `missing_auth_credential`), so the `explicit`\n * / `enforce` signals are carried through on the spec.\n */\nexport function modelIdPrefixProvider(modelId: string): string | undefined {\n const slash = modelId.indexOf(\"/\")\n return slash > 0 ? modelId.slice(0, slash) : undefined\n}\n\n/**\n * Resolve WHICH of an adapter's declared subscription surface(s) applies on\n * `endpoint` — THE one lookup every subscription-eligibility site shares\n * (`resolveAuthSpec` below, plus the three mirrored direct-methods\n * projections in `session-spawn.ts` / `session-restart-core.ts` /\n * `catalog-models.ts`), so they can never drift. Returns the matching\n * surface, or `undefined` when none applies — callers that only need the\n * old boolean predicate check `!== undefined`.\n *\n * `authSubscription` is a single surface OR an array of surfaces (one\n * per provider, for an adapter with more than one native OAuth login —\n * mastracode/opencode: anthropic AND openai). For a SINGLE surface, this\n * preserves the original predicate exactly: a provider-scoped surface (pi's\n * `ANTHROPIC_OAUTH_TOKEN`) applies only on its own provider; an unscoped one\n * (fixed-provider adapters — claude-code, codex) applies wherever the\n * adapter itself does; an unknown endpoint is treated as applying — the\n * fixed-provider case, where the caller had no per-model derivation to\n * offer. For an ARRAY of surfaces, an explicit provider match wins;\n * otherwise the one unscoped surface (if any) applies. An unknown endpoint\n * against MULTIPLE provider-scoped surfaces cannot be disambiguated — unlike\n * the single-surface case, guessing here would pick a specific but WRONG\n * provider's bearer door, not just skip a scrub — so nothing matches.\n *\n * This lookup REPLACED the old `modelDerivedApiKey ⇒ subscription works`\n * assumption (\"Anthropic OATs work as API keys\"): an OAT presented on the\n * x-api-key header is rejected by Anthropic's edge regardless of account\n * validity — observed live as opencode's opaque \"Internal error: API key is\n * invalid\" when the runtime injected a subscription token into\n * `ANTHROPIC_API_KEY`. Subscription support now requires an EXPLICIT\n * `authSubscription` declaration naming the env var the CLI actually reads\n * a bearer from.\n */\nexport function subscriptionSurfaceFor(\n sub: AdapterAuthDescriptor[\"authSubscription\"],\n endpoint: string | undefined,\n): AuthSubscriptionSurface | undefined {\n if (sub === undefined) return undefined\n if (!Array.isArray(sub)) {\n return sub.provider === undefined || endpoint === undefined || sub.provider === endpoint\n ? sub\n : undefined\n }\n return (\n sub.find(s => s.provider !== undefined && s.provider === endpoint) ??\n sub.find(s => s.provider === undefined)\n )\n}\n\nexport function resolveAuthSpec(\n input: ResolveAuthSpecInput,\n): { spec: ResolvedAuthSpec; echo: AuthEcho } | undefined {\n // 0. Gateway route: a preset or custom route wins over model-derived/fixed\n // provider because the operator explicitly chose a billing rail. It drives\n // base_url, the API-key env var, and the scrub set.\n const gatewayPreset = input.routeGateway\n ? findAnthropicGatewayPreset(input.routeGateway)\n : undefined\n const customRoute =\n input.routeGateway && !gatewayPreset\n ? resolveCustomRoute(input.routeGateway)\n : undefined\n const gatewayRoute = gatewayPreset ?? customRoute\n\n // A gateway preset whose id is the adapter's own fixed native provider is a\n // native match (e.g. codex with `provider: \"openai\"` and `route.gateway:\n // \"openai\"`). The route still selects the auth profile / API-key env var, but\n // the resolved base_url would point at a proxy/alternate endpoint the adapter\n // never asked for — codex has no `base_url` option and already talks to the\n // OpenAI endpoint natively. Skip the preset base_url so session-spawn routing\n // does not try to inject it (and so restart/resume paths don't re-inject it),\n // and keep subscription mode eligible because the route is direct, not a\n // third-party gateway.\n const isNativeGatewayPreset =\n gatewayPreset !== undefined && input.routeGateway === input.descriptor.provider\n\n // 1. Provider: per-spawn pin → adapter-fixed → model-derived. None ⇒\n // ambient (no injection); an unknown/free-form model id lands here too.\n // A matched gateway route overrides all three — the route IS the provider.\n // The only exception is a native-provider gateway preset, which is treated\n // as a direct route (same provider the adapter already bills through).\n let provider: string | undefined\n let baseUrl: string | undefined\n let apiKeyEnv: string\n let gatewayScrub: string[] = []\n if (gatewayRoute) {\n provider = input.routeGateway\n baseUrl = isNativeGatewayPreset\n ? undefined\n : gatewayPreset?.baseUrl ?? customRoute?.baseUrl\n apiKeyEnv =\n gatewayPreset?.keyEnv ??\n customRoute?.authEnv ??\n (provider ? providerEnvVar(provider) : \"\")\n gatewayScrub = gatewayPreset ? [...gatewayPreset.scrubEnv] : []\n } else {\n // Same precedence as `spawnEligibilityManifest` (session-spawn.ts) —\n // deliberately kept in lockstep. A model-derived-api-key adapter has no\n // fixed `provider`, so without the `modelProviders` tier the catalog\n // fallback is the only signal here, and the catalog's route for an id can\n // legitimately differ from what THIS adapter bills it through (D3: pi\n // bills `moonshotai/kimi-k2.7-code` via `moonshot`; the catalog routes\n // that same id to `openrouter`). Consulting it in the eligibility\n // projection but NOT here would let the two disagree: the access-profile\n // check would clear a moonshot wallet while this resolver injected an\n // OPENROUTER_API_KEY — billing the wrong wallet on a spawn that passed\n // its own gate.\n provider =\n input.requestedProvider ??\n input.descriptor.provider ??\n (input.model ? input.descriptor.modelProviders?.[input.model] : undefined) ??\n (input.model && input.descriptor.modelDerivedApiKey\n ? modelIdPrefixProvider(input.model)\n : undefined) ??\n (input.model ? getModelProvider(input.model) : undefined)\n if (!provider) return undefined\n apiKeyEnv = providerEnvVar(provider)\n }\n if (!provider) return undefined\n\n // Subscription support requires an EXPLICIT, provider-matching\n // authSubscription declaration ({@link subscriptionSurfaceFor}) — the old\n // `|| modelDerivedApiKey` clause assumed \"Anthropic OATs work as API\n // keys\", which is false on the x-api-key header: it silently injected the\n // subscription token into ANTHROPIC_API_KEY for opencode/mastracode/jcode\n // and the upstream rejected it as an invalid key. A multi-provider\n // adapter that DOES have a bearer door declares it (pi:\n // `authSubscription: {setEnv: \"ANTHROPIC_OAUTH_TOKEN\", provider:\n // \"anthropic\"}`); a multi-SURFACE adapter declares one entry per provider\n // (mastracode/opencode: an anthropic AND an openai external surface) and\n // this resolves the one matching the spawn's resolved provider; the rest\n // fail fast below instead of failing upstream. Gateway routes stay\n // API-key only; a native fixed-provider gateway preset (e.g. codex +\n // route.gateway \"openai\") is a direct route, so subscription stays\n // eligible there.\n const sub = subscriptionSurfaceFor(input.descriptor.authSubscription, provider)\n const supportsSub =\n sub !== undefined && (gatewayRoute === undefined || isNativeGatewayPreset)\n const enforce = input.descriptor.authEnforce ?? \"when-configured\"\n\n // File-based (external) subscription (codex/gemini): the CLI reads its OWN\n // local-login file, so there is no bearer to inject. The login file IS the\n // credential — availability comes from the caller's fail-loud presence check\n // (`externalSubscriptionVerified`), NOT from an injected token.\n const external = supportsSub && sub?.external === true\n const subCredAvailable =\n input.subscriptionCredential !== undefined ||\n (external && input.externalSubscriptionVerified === true)\n const apiCredAvailable =\n input.apiKeyConfigCredential !== undefined || input.apiKeyStoreCredential !== undefined\n\n // 2/3. Mode: explicit request (validated) OR ordered preference (DECISION\n // 10 — subscription first when supported; never silently pick api-key\n // while a subscription credential is present and preferred). When\n // NEITHER credential is available, `mode` still needs a value (it\n // drives `setEnv`/scrub below) but is an ARBITRARY pick, not a real\n // signal — this used to silently fall back to `preference[0]`\n // (\"subscription\" for any adapter that supports it), which is exactly\n // how a zero-credential, api-key-only user got told to buy a\n // subscription. `neitherConfigured` flags that case so callers never\n // present the fallback mode as though it meant something.\n let mode: \"subscription\" | \"api-key\"\n let neitherConfigured = false\n if (input.requestedMode) {\n if (input.requestedMode === \"subscription\" && !supportsSub) {\n throw new AuthResolutionError(\n `auth mode \"subscription\" is not supported for provider \"${provider}\" on this ` +\n `adapter (no matching authSubscription surface): it presents credentials on ` +\n `the api-key header, where a subscription OAuth token is rejected upstream as ` +\n `an invalid key. Use an api-key profile (a console key) or route the model ` +\n `via a gateway profile (e.g. openrouter) instead.`,\n )\n }\n mode = input.requestedMode\n } else {\n const preference: Array<\"subscription\" | \"api-key\"> = supportsSub\n ? [\"subscription\", \"api-key\"]\n : [\"api-key\"]\n const available = preference.find(m =>\n m === \"subscription\" ? subCredAvailable : apiCredAvailable,\n )\n mode = available ?? preference[0]!\n neitherConfigured = available === undefined\n }\n\n // 4/5. setEnv + credential + source for the resolved mode.\n let setEnv: string\n let credential: string | undefined\n let credentialSource: CredentialSource\n let externalCredential = false\n if (mode === \"subscription\" && external) {\n // File-based: inject NOTHING (setEnv empty). The credential lives in the\n // CLI's own login file; the source label records that for the echo. The\n // scrub below still removes the api-key vars so a leftover key can't flip\n // billing. Money-safe by construction — no bearer is ever set. When the\n // login was NOT verified (the unconfigured ordered-preference fallback,\n // which the driver never engages because it isn't explicit), the echo\n // stays honest as \"none\" rather than claiming a local login was used.\n setEnv = \"\"\n credential = undefined\n credentialSource = subCredAvailable ? \"cli-local-login\" : \"none\"\n externalCredential = true\n } else if (mode === \"subscription\") {\n // Bearer authSubscription declares setEnv (schema-enforced) — for pi\n // that's ANTHROPIC_OAUTH_TOKEN, its documented bearer door. The\n // `?? apiKeyEnv` fallback is defensive only: subscription mode can no\n // longer resolve without a matching authSubscription (see supportsSub).\n setEnv = sub?.setEnv ?? apiKeyEnv\n credential = input.subscriptionCredential\n credentialSource =\n credential !== undefined\n ? (input.subscriptionCredentialSource ?? \"explicit-config\")\n : \"none\"\n } else {\n // `apiKeyEnv` is the preset's/provider's conventional key-env — the\n // OPERATOR's providers-store lookup key (unaffected below; the store\n // read is keyed by PROVIDER id, never by this env name). The var\n // actually INJECTED into the child is a separate fact: for a gateway\n // route, an adapter that declares `gatewayAuth` (claude-code/claude-sdk\n // → ANTHROPIC_AUTH_TOKEN) receives the credential there instead, because\n // that's the var its OWN wire protocol reads a bearer from — injecting\n // the preset's `keyEnv` would land the credential in a var nothing reads\n // (the D4 bug: OPENROUTER_API_KEY set, but the Anthropic SDK only ever\n // looks at ANTHROPIC_AUTH_TOKEN). An adapter with no `gatewayAuth`\n // (hermes) keeps `apiKeyEnv` verbatim — it genuinely reads that var.\n setEnv =\n gatewayRoute && input.descriptor.gatewayAuth?.setEnv\n ? input.descriptor.gatewayAuth.setEnv\n : apiKeyEnv\n if (input.apiKeyConfigCredential !== undefined) {\n credential = input.apiKeyConfigCredential\n credentialSource = \"explicit-config\"\n } else if (input.apiKeyStoreCredential !== undefined) {\n credential = input.apiKeyStoreCredential\n credentialSource = \"providers-store\"\n } else {\n credential = undefined\n credentialSource = \"none\"\n }\n }\n\n // 4. Derived scrub: every conflicting billing-credential var EXCEPT the one\n // being set, plus (native/subscription mode only) the adapter's gateway\n // hygiene. Single-credential provider (no authSubscription) → empty scrub\n // (the setEnv overwrite already prevents a leak). Gateway routes also\n // apply the preset's scrubEnv (e.g. ANTHROPIC_API_KEY when fronting\n // Moonshot) so native credentials don't leak to third-party hosts.\n const unsetEnvSet = new Set<string>()\n if (mode === \"subscription\" && sub?.unsetEnvAdd) {\n for (const e of sub.unsetEnvAdd) unsetEnvSet.add(e)\n }\n for (const e of gatewayScrub) unsetEnvSet.add(e)\n\n const credVars = new Set<string>([apiKeyEnv])\n if (sub) {\n if (sub.setEnv) credVars.add(sub.setEnv)\n for (const c of sub.conflictEnv ?? []) credVars.add(c)\n }\n credVars.delete(setEnv)\n for (const c of credVars) unsetEnvSet.add(c)\n const unsetEnv = [...unsetEnvSet]\n\n const spec: ResolvedAuthSpec = {\n mode,\n ...(credential !== undefined ? { credential } : {}),\n setEnv,\n unsetEnv,\n explicit: input.explicit,\n enforce,\n ...(externalCredential ? { externalCredential: true } : {}),\n ...(neitherConfigured ? { neitherConfigured } : {}),\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n }\n const echo: AuthEcho = {\n provider,\n authMode: mode,\n credentialSource,\n setEnv,\n // A verified file-based login has no injected credential to fingerprint, so\n // carry a NON-SECRET marker (`subscription · local-login`) instead — it lets\n // the descriptor echo record the spawn for verifiability (same guard as a\n // real fingerprint) without inventing a fake secret shape.\n ...(credential !== undefined\n ? { fingerprint: credentialFingerprint(mode, credential) }\n : credentialSource === \"cli-local-login\"\n ? { fingerprint: `${mode} · local-login` }\n : {}),\n }\n return { spec, echo }\n}\n\n/** The only `auth.source` value understood today (Mode 3): read the\n * subscription bearer fresh from the local Claude Code login via the\n * `claude-code-oauth` provision recipe. */\nexport const CLAUDE_CODE_OAUTH_SOURCE = \"claude-code-oauth\"\n\n/**\n * `auth.source`/profile-`source` values that are RECOGNIZED elsewhere in the\n * system (the codex/gemini adapters' own `authSubscription: { external: true\n * }` file-based login, and the \"Use my existing Codex/Gemini login\" profile\n * flow — see `verifyLocalLoginPresent`) but can never be satisfied by {@link\n * resolveSubscriptionCredential}: they name a FILE the target CLI reads\n * itself, not an extractable bearer, so there is nothing this Mode-3 resolver\n * could ever fetch for them. Used only to sharpen the `unsupported_auth_source`\n * error for THESE specific values (e.g. spawning `pi` — which has no\n * `authSubscription.external` — with `auth.source: \"codex\"`, likely inherited\n * from a codex-flavored parent session or auth profile) instead of the\n * generic \"only claude-code-oauth is supported\" message, which doesn't\n * explain why a seemingly-valid value (codex IS a real source elsewhere)\n * failed here. Does NOT change behavior — still fails loud either way. */\nexport const FILE_BASED_AUTH_SOURCES: ReadonlySet<string> = new Set([\"codex\", \"gemini\"])\n\n/**\n * Raised when `auth.source` is configured but cannot yield a credential — an\n * unknown source value (`unsupported_auth_source`) or the recipe resolving to\n * nothing / not-logged-in (`auth_source_unresolved`). A LOUD, actionable\n * failure surfaced as a spawn error, NEVER a silent fallthrough to a static or\n * ambient credential (mirrors {@link AuthResolutionError}'s discipline).\n */\nexport class SubscriptionSourceError extends Error {\n readonly code: \"unsupported_auth_source\" | \"auth_source_unresolved\"\n constructor(\n code: \"unsupported_auth_source\" | \"auth_source_unresolved\",\n message: string,\n ) {\n super(message)\n this.name = \"SubscriptionSourceError\"\n this.code = code\n }\n}\n\nexport interface ResolveSubscriptionCredentialInput {\n /** Per-spawn explicit static token (`input.auth.token`) — wins over source. */\n explicitToken?: string\n /** Effective opt-in source (`input.auth.source ?? config auth.source`). */\n source?: string\n /** Config static token (`defaults.adapters.<slug>.auth.token`) — the\n * lowest-precedence fallback, used only when NEITHER an explicit per-spawn\n * token nor a source applies. */\n fallbackStaticToken?: string\n}\n\nexport interface SubscriptionCredentialResolution {\n /** Resolved subscription bearer, or undefined ⇒ nothing configured (the\n * driver's fail-fast `missing_auth_credential` still owns that case). */\n credential?: string\n /** Observable origin of {@link credential} for the echo. */\n source?: CredentialSource\n}\n\n/**\n * Resolve the subscription (oauth-bearer) credential + its observable origin,\n * placing the self-refreshing `source` (Mode 3) BETWEEN the two static tokens\n * (SPEC §2):\n * a. explicit per-spawn `input.auth.token` (static) — wins over everything.\n * b. else `source: \"claude-code-oauth\"` → resolve FRESH via the injected\n * recipe resolver (Keychain / credentials file) ⇒ origin\n * `\"claude-code-oauth\"`.\n * c. else config.json static `auth.token` ⇒ origin `\"explicit-config\"`.\n * d. else nothing (⇒ driver fail-fast `missing_auth_credential`, unchanged).\n *\n * The recipe I/O is INJECTED (`resolveSourceToken`) so this stays pure and\n * unit-testable without touching the real Keychain — the impure resolver lives\n * in `claude-code-oauth-source.ts`. Fails LOUD ({@link SubscriptionSourceError})\n * on an unknown source or an empty recipe; never a silent fallthrough. Only\n * touches the subscription path — api-key mode never reaches here.\n */\nexport async function resolveSubscriptionCredential(\n input: ResolveSubscriptionCredentialInput,\n resolveSourceToken: (source: string) => Promise<string>,\n): Promise<SubscriptionCredentialResolution> {\n // (a) An explicit per-spawn token is a deliberate one-off override — it wins\n // even over a persisted `source`.\n if (input.explicitToken !== undefined) {\n return { credential: input.explicitToken, source: \"explicit-config\" }\n }\n // (b) Opt-in self-refreshing source.\n if (input.source !== undefined) {\n if (input.source !== CLAUDE_CODE_OAUTH_SOURCE) {\n // A file-based source (codex/gemini) is a real value elsewhere in the\n // system — just not one this bearer-fetch resolver can ever satisfy.\n // Name that explicitly so the caller understands WHY a seemingly-valid\n // source failed here, instead of implying \"claude-code-oauth\" is the\n // only auth concept that exists.\n const message = FILE_BASED_AUTH_SOURCES.has(input.source)\n ? `auth.source: \"${input.source}\" is a file-based (external) login — only ` +\n `adapters that declare \"authSubscription.external\" (e.g. codex, gemini ` +\n `themselves) can use it, and this adapter isn't one of them, so there is ` +\n `no bearer to inject. Use auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" for ` +\n `a Claude subscription, or configure a real API key for this adapter's ` +\n `target provider instead.`\n : `auth.source: \"${input.source}\" is not supported — the only supported ` +\n `value is \"${CLAUDE_CODE_OAUTH_SOURCE}\".`\n throw new SubscriptionSourceError(\"unsupported_auth_source\", message)\n }\n let token: string\n try {\n token = await resolveSourceToken(input.source)\n } catch (err) {\n throw new SubscriptionSourceError(\n \"auth_source_unresolved\",\n `auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" but no Claude Code login ` +\n `found — run \\`claude\\` and /login (or \\`claude setup-token\\`) first ` +\n `(${err instanceof Error ? err.message : String(err)}).`,\n )\n }\n if (!token) {\n throw new SubscriptionSourceError(\n \"auth_source_unresolved\",\n `auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" but no Claude Code login ` +\n `found — run \\`claude\\` and /login (or \\`claude setup-token\\`) first.`,\n )\n }\n return { credential: token, source: CLAUDE_CODE_OAUTH_SOURCE }\n }\n // (c) Config-level static token.\n if (input.fallbackStaticToken !== undefined) {\n return { credential: input.fallbackStaticToken, source: \"explicit-config\" }\n }\n // (d) Nothing configured.\n return {}\n}\n\n/** Manifest-declared AIP-45 option id + type, the minimum an adapter\n * resolver needs to expose for `normalizeSkillsOption` below. Mirrors\n * `AgentCliOption`'s `id`/`type` fields without importing\n * `@agentproto/driver-agent-cli` into the runtime package. */\nexport interface DeclaredAdapterOption {\n id: string\n type: \"boolean\" | \"integer\" | \"string\" | \"enum\"\n}\n\n/**\n * Fold the resolved `skills` list into `options.skills` using whatever\n * shape the adapter's manifest declares for that option id (today, only\n * `type: \"string\"` exists for a skills-shaped option — e.g. hermes'\n * comma-joined `--skills a,b`). Adapters with no declared `skills` option\n * (e.g. claude-code, which auto-discovers from `~/.claude/skills`) are a\n * documented no-op — the effective skills list has nowhere to go, so it's\n * dropped rather than guessing a flag the manifest didn't declare.\n *\n * An `options.skills` already present (from config defaults or the\n * explicit call) is respected as-is and never overwritten here.\n */\nexport function normalizeSkillsOption(\n skills: string[],\n options: Record<string, boolean | number | string>,\n declaredOptions: readonly DeclaredAdapterOption[] | undefined,\n): Record<string, boolean | number | string> {\n if (skills.length === 0 || \"skills\" in options) return options\n const skillsOption = declaredOptions?.find(o => o.id === \"skills\")\n if (!skillsOption || skillsOption.type !== \"string\") return options\n return { ...options, skills: skills.join(\",\") }\n}\n","/**\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 resolveContextWindow,\n formatTokens,\n LLM_PRICING_CATALOG,\n MODEL_ALIASES,\n} from \"@agentproto/model-catalog/llm\"\nimport { getAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport { subscriptionSurfaceFor, 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 /** Human-readable max input tokens (e.g. `\"1M\"`, `\"200k\"`), from the\n * live-synced CONTEXT_WINDOWS table (`resolveContextWindow`); null when\n * no synced provider carries this id. Consumers wanting the raw integer\n * can re-resolve via `resolveContextWindow(ref product)` or parse. */\n contextWindow: string | null\n /** Human-readable max output tokens (same source/format), null when the\n * source doesn't publish a completion cap for this id. */\n maxOutput: string | 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 endpoint?: string,\n): AuthMethod[] {\n const methods: AuthMethod[] = []\n // oauth-bearer requires an explicit, provider-matching subscription\n // surface — see `subscriptionSurfaceFor`'s doc in spawn-defaults.ts.\n if (subscriptionSurfaceFor(descriptor?.authSubscription, endpoint) !== undefined) {\n methods.push(\"oauth-bearer\")\n }\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, route)\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 ctx = resolveContextWindow(row.product)\n const route: CatalogRoute = {\n route: row.route,\n ref: row.ref,\n baseUrl: row.baseUrl,\n pricing: row.pricing,\n // Live-synced context window (max input) + max output, when a synced\n // provider (Anthropic/Groq/xAI/Moonshot/Mistral/Google) carries this\n // id — null otherwise. All CONTEXT_WINDOWS providers get this, not\n // only Anthropic. Formatted for display (`1M`/`200k`); consumers\n // needing the raw integer resolve it themselves.\n contextWindow: formatTokens(ctx?.contextWindow),\n maxOutput: formatTokens(ctx?.maxOutput),\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/** Verdict of the spawn-time adapter-capability guard\n * ({@link checkModelAdapterEligibility}). */\nexport interface ModelAdapterEligibility {\n ok: boolean\n /** Other installed adapters whose catalog row already curates this exact\n * model on this exact route — the actionable set to re-spawn onto. Empty\n * when `ok`, or when NO installed adapter (this one included) curates the\n * combination — nobody has proven it reachable at all, so this guard has\n * nothing to reject on. */\n compatibleAdapters: string[]\n}\n\n/**\n * Adapter-capability spawn guard: the money-safety guard above\n * ({@link checkModelWalletEligibility}) proves the resolved ROUTE can bill\n * `model`; it says nothing about whether THIS adapter's own manifest can\n * actually reach it there. A fixed hand-curated client (claude-code's ACP\n * wrapper validates every model id against its own live selector and 404s on\n * anything it doesn't recognize) can be routeSelection:\"free\" — genuinely able\n * to reach several gateways — while still only supporting a small, explicitly\n * vetted model list on each one. A pass-through client (opencode/mastracode/\n * hermes/jcode, routeSelection:\"derived-from-model\") instead auto-derives a\n * broad curated list straight from the pricing catalog, so it ends up\n * supporting far more of a gateway's models without needing a per-model\n * allowlist maintained by hand.\n *\n * Takes the SAME `CatalogModelsResponse` shape `buildCatalogModels` (and\n * therefore `catalog_models`) produces — reusing that exact join, never a\n * parallel per-adapter table — and looks up whether `adapterSlug` is among\n * the resolved (vendor, product, route) row's `adapters`.\n *\n * `ok:true` when EITHER no installed adapter's catalog row covers this exact\n * model+route (nobody has proven it servable at all — the same never-reject-\n * an-unknown-combination stance {@link checkModelWalletEligibility} takes), OR\n * `adapterSlug` is already among the row's adapters. `ok:false` only when the\n * row exists and excludes `adapterSlug` — a proven \"wrong client for this\n * model\" mismatch, with the row's other adapters (if any) as the actionable\n * alternative.\n */\nexport function checkModelAdapterEligibility(\n catalog: CatalogModelsResponse,\n adapterSlug: string,\n model: string,\n route: string,\n): ModelAdapterEligibility {\n const target = resolveModelId(model)\n const routeEntry = catalog.vendors\n .find(v => v.vendor === target.vendor)\n ?.products.find(p => p.product === target.product)\n ?.routes.find(r => r.route === route)\n if (!routeEntry) return { ok: true, compatibleAdapters: [] }\n if (routeEntry.adapters.includes(adapterSlug)) return { ok: true, compatibleAdapters: [] }\n return { ok: false, compatibleAdapters: routeEntry.adapters }\n}\n\n/**\n * The actionable fail-fast message for {@link checkModelAdapterEligibility} —\n * names the adapters that DO already curate the model on this route (when any\n * do) so the operator can re-spawn without opening the catalog by hand. Never\n * auto-switches adapters for the operator; only rejects.\n */\nexport function modelAdapterIncompatibleMessage(opts: {\n prefix: string\n adapter: string\n model: string\n route: string\n compatibleAdapters: string[]\n}): string {\n const alternative =\n opts.compatibleAdapters.length > 0\n ? `Adapters that already support it on \"${opts.route}\": ${opts.compatibleAdapters\n .map(a => `\"${a}\"`)\n .join(\", \")} — re-spawn with one of those instead.`\n : `No installed adapter currently supports it on \"${opts.route}\" either — check ` +\n `\\`catalog_models\\` for a route this model IS servable on.`\n return (\n `${opts.prefix}: adapter \"${opts.adapter}\" does not declare support for model \"${opts.model}\" ` +\n `on route \"${opts.route}\" and would 404/reject upstream even though that route can bill it. ` +\n `${alternative} This guard only rejects; it never switches adapters for you.`\n )\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/providers-store.ts","../src/spawn-defaults.ts","../src/catalog-models.ts"],"names":["routes","getModelProvider"],"mappings":"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","file":"catalog-models.mjs","sourcesContent":["/**\n * Re-exports `@agentproto/providers-store` unchanged. Moved out of `runtime`\n * so a standalone process (e.g. `@agentproto/llm-endpoint`, which has zero\n * runtime deps) can read the provider-key store without depending on the\n * whole daemon. Kept as a same-named module (rather than deleting it) so the\n * `@agentproto/runtime/providers-store` subpath and every existing relative\n * `./providers-store.js` import in this package keep working.\n */\nexport * from \"@agentproto/providers-store\"\n","/**\n * Pure resolver for `~/.agentproto/config.json`'s `defaults` block —\n * computes the effective `skills` + `options` + `auth` for an `agent_start`\n * spawn before adapter-specific normalization. No fs, no adapter I/O, so\n * it's unit-testable in isolation from `session-spawn.ts` (which owns the\n * fs read + the adapter-manifest lookup).\n *\n * Precedence (lowest → highest): global `defaults` < `defaults.adapters.\n * <slug>` < the explicit `agent_start` call.\n * - `options`: shallow-merged maps, later (higher-precedence) keys win.\n * - `skills`: global ∪ per-adapter when the caller didn't pass `skills`\n * at all; an explicit `skills` (even `[]`) REPLACES the union rather\n * than merging into it — a deliberate exact set, mirroring how an\n * explicit `mcpServers: []` opts out of the hermes default in\n * `session-spawn.ts`.\n * - `auth`: surfaces the RAW billing-auth material (requested mode, both\n * candidate credentials, per-spawn provider pin, and the `explicit`\n * signal) so the descriptor-aware resolver ({@link resolveAuthSpec},\n * which also needs the adapter's provider/subscription descriptor +\n * providers.json) can decide the final mode, env var, scrub set, and\n * credential source. Credentials are named in config or the provider\n * store — never read from the ambient shell env.\n */\n\nimport { getModelProvider } from \"@agentproto/model-catalog/llm\"\nimport type { CatalogProvider } from \"@agentproto/model-catalog\"\nimport { resolveCustomRoute } from \"@agentproto/model-catalog/route-identity\"\nimport { findAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport { providerEnvVar } from \"./providers-store.js\"\nimport type { ContextContinuityPolicy } from \"./context-continuity.js\"\n\n/**\n * Deterministic billing-auth config for one adapter slug (today, only\n * claude-code interprets it — see `AgentCliStartOptions.auth` in\n * `@agentproto/driver-agent-cli`). EXPLICIT credential selection, not\n * scrub-by-absence: `token`/`apiKey` are the actual secret values, named\n * here (or supplied per-spawn) rather than inherited from the launching\n * shell. Never logged; only a fingerprint (see {@link credentialFingerprint})\n * is ever surfaced back to a caller.\n */\nexport interface DefaultsAdapterAuthConfig {\n /** `\"subscription\"` or `\"api-key\"`. Omitted ⇒ the resolver picks by\n * ordered preference (subscription first for adapters that support it —\n * see {@link resolveAuthSpec}), never a hardcoded default. */\n mode?: \"subscription\" | \"api-key\"\n /** The subscription bearer token for `\"subscription\"` mode — minted via\n * `claude setup-token` (bills the Max/Pro subscription, not API credits),\n * SET to the adapter's `authSubscription.setEnv`. */\n token?: string\n /** Opt-in SELF-REFRESHING subscription source (Mode 3). When set to\n * `\"claude-code-oauth\"` and no static `token` is supplied per-spawn, the\n * subscription bearer is read FRESH on every spawn from the local Claude\n * Code login (macOS Keychain `Claude Code-credentials` → jsonPath\n * `claudeAiOauth.accessToken`, falling back to `~/.claude/.credentials.json`)\n * via the `claude-code-oauth` provision recipe — because Claude Code keeps\n * that item refreshed, agentproto gets a fresh token each spawn. Absent ⇒\n * today's static-bearer behavior (Mode 2). Only `\"claude-code-oauth\"` is\n * understood; any other value fails LOUD (never a silent fallthrough). See\n * {@link resolveSubscriptionCredential} for the precedence with `token`. */\n source?: string\n /** Explicit API key for `\"api-key\"` mode, SET to `providerEnvVar(provider)`.\n * Wins over the `providers.json` store key for the same provider. */\n apiKey?: string\n /** Per-spawn provider PIN — overrides the adapter's fixed provider and the\n * model-derived provider (the sharp edge for by-model routers whose config\n * routes a catalog-\"anthropic\" model elsewhere). A `CatalogProvider` id. */\n provider?: CatalogProvider\n}\n\nexport interface DefaultsAdapterConfig {\n skills?: string[]\n options?: Record<string, boolean | number | string>\n auth?: DefaultsAdapterAuthConfig\n contextContinuity?: ContextContinuityPolicy\n}\n\n/** Shape of `config.json`'s top-level `defaults` block. */\nexport interface SpawnDefaultsConfig {\n skills?: string[]\n options?: Record<string, boolean | number | string>\n adapters?: Record<string, DefaultsAdapterConfig>\n contextContinuity?: ContextContinuityPolicy\n /** Depth cutoff for the role-derived default (see `resolveRole` in\n * `role.ts`) applied when an `agent_start` call omits `role`:\n * `depth < cutoff` → supervisor, `depth >= cutoff` → executor.\n * Default 1 (root spawns keep today's unrestricted behaviour; any\n * spawn made THROUGH an orchestrator defaults to executor). Tune\n * this up (e.g. to a large number) to restore the old permissive\n * behaviour for existing deep spawns wholesale. */\n defaultRoleDepthCutoff?: number\n /** Trust-boundary cap on pack-carried roles (see `role-registry.ts`'s\n * `loadRoleRegistry`): a role pack whose `toolPolicy.delegation` is\n * `\"allow\"` at a level ABOVE this cap has it forced to `\"deny\"` —\n * the pack can still declare the intent, the daemon just refuses to\n * grant it. Lets an operator install third-party role packs without\n * trusting every one of them to self-grant delegation. Undefined\n * (default) ⇒ no cap, any pack-declared level may carry\n * `delegation: \"allow\"` (back-compat: #214 had no such knob). */\n maxGrantableDelegation?: number\n /** Default per-session Langfuse tracing opt-in when an `agent_start` call\n * omits `trace`. Default false — sessions trace only when they opt in or\n * this is on. See `filterSessionObserver` / `SpawnAgentInput.trace`. */\n langfuseTracing?: boolean\n /** Redactor slug applied to traced session content before it's sent to\n * Langfuse (see `@agentproto/redaction`'s registry). Default \"secrets\"\n * (deny-list by key + value-scan for secret shapes). */\n traceRedactor?: string\n}\n\nexport interface ResolveSpawnDefaultsInput {\n /** Explicit-call `skills`. Undefined ⇒ caller expressed no preference,\n * fall through to the config union. Provided ⇒ replaces it outright. */\n skills?: string[]\n /** Explicit-call AIP-45 `options` map — wins per-key over both the\n * global and per-adapter config defaults. */\n options?: Record<string, boolean | number | string>\n /** Explicit-call `agent_start.auth` override. `mode` wins over\n * `defaults.adapters.<slug>.auth.mode`; the credential field matching the\n * RESOLVED mode wins over the matching config field. Undefined ⇒ falls\n * through entirely to the per-adapter config default. */\n auth?: DefaultsAdapterAuthConfig\n /** Explicit-call context-continuity policy — wins over global and\n * per-adapter config defaults. */\n contextContinuity?: ContextContinuityPolicy\n}\n\nexport interface ResolvedSpawnDefaults {\n skills: string[]\n options: Record<string, boolean | number | string>\n /** RAW billing-auth material (config precedence applied) — fed to the\n * descriptor-aware {@link resolveAuthSpec}, which owns the final mode /\n * env / scrub / credential-source decision. Both candidate credentials\n * are surfaced (NOT collapsed to one), since the ordered-mode selection\n * needs to know which are available before it picks the mode. */\n auth: ResolvedSpawnAuthMaterial\n contextContinuity?: ContextContinuityPolicy\n}\n\nexport interface ResolvedSpawnAuthMaterial {\n /** Operator-requested mode (per-spawn > per-adapter config), or undefined\n * ⇒ let the resolver pick by ordered preference. */\n requestedMode?: \"subscription\" | \"api-key\"\n /** True when the operator explicitly configured `auth` (per-spawn OR in\n * `defaults.adapters.<slug>.auth`). The ONLY way to tell \"set mode, no\n * key\" (fail-fast) from \"set nothing\" (ambient) — both give no credential.\n * DECISION 5. */\n explicit: boolean\n /** Subscription bearer token (per-spawn > config), if configured. STATIC\n * material only — the self-refreshing {@link subscriptionSource} is resolved\n * separately (and impurely) by the caller. */\n subscriptionCredential?: string\n /** Opt-in self-refreshing subscription source (per-spawn > config), if\n * configured — e.g. `\"claude-code-oauth\"`. Surfaced RAW; the impure caller\n * resolves it to a fresh token via {@link resolveSubscriptionCredential}. */\n subscriptionSource?: string\n /** Explicit API key (per-spawn > config), if configured — distinct from the\n * providers.json store key the resolver fetches separately. */\n apiKeyCredential?: string\n /** Per-spawn provider pin, if given. */\n provider?: CatalogProvider\n}\n\nexport function resolveSpawnDefaults(\n defaults: SpawnDefaultsConfig | undefined,\n adapterSlug: string,\n input: ResolveSpawnDefaultsInput,\n): ResolvedSpawnDefaults {\n const adapterDefaults = defaults?.adapters?.[adapterSlug]\n\n const options: Record<string, boolean | number | string> = {\n ...defaults?.options,\n ...adapterDefaults?.options,\n ...input.options,\n }\n\n const skills =\n input.skills !== undefined\n ? input.skills\n : Array.from(\n new Set([...(defaults?.skills ?? []), ...(adapterDefaults?.skills ?? [])]),\n )\n\n const requestedMode = input.auth?.mode ?? adapterDefaults?.auth?.mode\n const explicit = input.auth !== undefined || adapterDefaults?.auth !== undefined\n const subscriptionCredential = input.auth?.token ?? adapterDefaults?.auth?.token\n const subscriptionSource = input.auth?.source ?? adapterDefaults?.auth?.source\n const apiKeyCredential = input.auth?.apiKey ?? adapterDefaults?.auth?.apiKey\n const authProvider = input.auth?.provider ?? adapterDefaults?.auth?.provider\n\n const contextContinuity =\n input.contextContinuity ?? adapterDefaults?.contextContinuity ?? defaults?.contextContinuity\n\n return {\n skills,\n options,\n auth: {\n explicit,\n ...(requestedMode ? { requestedMode } : {}),\n ...(subscriptionCredential !== undefined ? { subscriptionCredential } : {}),\n ...(subscriptionSource !== undefined ? { subscriptionSource } : {}),\n ...(apiKeyCredential !== undefined ? { apiKeyCredential } : {}),\n ...(authProvider ? { provider: authProvider } : {}),\n },\n ...(contextContinuity !== undefined ? { contextContinuity } : {}),\n }\n}\n\n/**\n * Public credential key-shape prefixes, longest / most-specific first (the\n * match is first-hit, so `sk-ant-oat` must precede `sk-ant-api`/`sk-ant-`,\n * `sk-or-v1-` precede `sk-or-`, and both precede the bare `sk-`). These are\n * PUBLIC key-shape knowledge — the visible leading bytes of each provider's\n * credential format — never secret; only these + the last 4 chars are ever\n * revealed. DECISION 6: the fingerprint is derived from the credential's own\n * shape, NOT from `mode`, so it stays honest across providers (a gateway\n * `sk-or-…` key run in api-key mode reads as `api-key · sk-or-…`).\n */\nconst CREDENTIAL_FINGERPRINT_PREFIXES: readonly string[] = [\n \"sk-ant-oat\",\n \"sk-ant-api\",\n \"sk-ant-\",\n \"sk-or-v1-\",\n \"sk-or-\",\n \"sk-proj-\",\n \"sk-\",\n \"gsk_\",\n \"AIza\",\n]\n\n/**\n * Derive a SAFE, non-secret fingerprint for a resolved auth credential —\n * NEVER the raw value — for recording on the session descriptor / surfacing\n * in `agentproto sessions --watch` and `agent_sessions_list` (the\n * \"verifiability\" requirement: answer \"what was used\" without exposing the\n * secret). Format: `<mode> · <shape-prefix>…<last4>` when the shape is known\n * (e.g. `subscription · sk-ant-oat…3f9c`), else `<mode> · …<last4>`.\n *\n * The shape marker is matched from a PUBLIC key-prefix table (longest-match),\n * not derived from `mode` — see {@link CREDENTIAL_FINGERPRINT_PREFIXES}. Only\n * the matched prefix + the last 4 characters are ever surfaced, never the\n * middle (mirrors GitHub's `ghp_…abcd` style).\n */\nexport function credentialFingerprint(\n mode: \"subscription\" | \"api-key\",\n credential: string,\n): string {\n const prefix = CREDENTIAL_FINGERPRINT_PREFIXES.find(p => credential.startsWith(p))\n const last4 = credential.slice(-4)\n return prefix ? `${mode} · ${prefix}…${last4}` : `${mode} · …${last4}`\n}\n\n/**\n * One subscription (OAuth/bearer) surface — mirrors the driver's\n * `AgentCliAuthSubscription`. `external: true` (codex/gemini/mastracode/\n * opencode) ⇒ file-based: the CLI reads its own local-login file, so\n * `setEnv` is absent and the runtime injects NO bearer — it only scrubs the\n * conflicting api-key vars.\n */\nexport interface AuthSubscriptionSurface {\n /** Env var SET to the resolved subscription credential — absent for an\n * {@link external} surface. */\n setEnv?: string\n external?: boolean\n conflictEnv?: string[]\n unsetEnvAdd?: string[]\n /** Provider scope for a multi-provider adapter's surface — pi's\n * `ANTHROPIC_OAUTH_TOKEN` is an anthropic-only door; mastracode/opencode's\n * file-based ChatGPT login is an openai-only door alongside their\n * anthropic one. When set, this surface only MATCHES a spawn whose\n * resolved provider equals this id (see {@link subscriptionSurfaceFor}).\n * Omitted for a fixed-provider adapter (claude-code, codex) — their\n * descriptor-level `provider` already pins it. */\n provider?: string\n}\n\n/**\n * The adapter's billing-auth capability, projected from its AIP-45 manifest\n * (`provider` / `authEnforce` / `authSubscription`) by the host resolver. The\n * runtime reads THIS, never the manifest directly — keeping the LLM-catalog\n * coupling in the runtime and the driver mechanical.\n */\nexport interface AdapterAuthDescriptor {\n /** FIXED provider for a single-provider adapter; omitted for by-model\n * routers (provider then derives from the requested model). */\n provider?: CatalogProvider\n /** Enforcement policy — `\"always\"` engages every spawn (claude-code's\n * #312 fail-fast); `\"when-configured\"` (default) only when `explicit`. */\n authEnforce?: \"always\" | \"when-configured\"\n /** Subscription (OAuth/bearer) support. Presence ⇒ the adapter supports\n * `\"subscription\"` mode. A SINGLE surface for a fixed/single-provider\n * subscription, or an ARRAY of surfaces for an adapter with more than one\n * native OAuth login (mastracode/opencode: both an anthropic- and an\n * openai-scoped file-based login) — see {@link subscriptionSurfaceFor},\n * which resolves the matching surface for a spawn's resolved provider. */\n authSubscription?: AuthSubscriptionSurface | AuthSubscriptionSurface[]\n /** True when the adapter's api-key auth is derived from the requested\n * model rather than a fixed provider (e.g. `pi`, `opencode`). When set,\n * the adapter supports `\"api-key\"` on the model-derived direct endpoint,\n * and `spawnEligibilityManifest` includes it for direct routes. */\n modelDerivedApiKey?: boolean\n /** How THIS adapter receives a GATEWAY-routed bearer credential — see\n * `AgentCliDefinition.gatewayAuth` (the manifest field this is projected\n * from). Distinct from a gateway preset's `keyEnv` (the providers-store\n * lookup key) and from `authSubscription.setEnv` (this adapter's own\n * native bearer). When a gateway route resolves, `resolveAuthSpec` injects\n * the credential into `gatewayAuth.setEnv` instead of the preset's\n * `keyEnv` — e.g. claude-code/claude-sdk read `ANTHROPIC_AUTH_TOKEN`.\n * Omit when the adapter reads the preset's own `keyEnv` directly (hermes). */\n gatewayAuth?: { setEnv: string }\n /** Model id → the BILLING provider THIS adapter itself declares for it\n * (`AgentCliModelEntry.provider`, projected from `models.allowed`) —\n * authoritative for a model-derived-api-key adapter (`pi`, `opencode`),\n * whose `provider` above is absent so the eligibility projection would\n * otherwise fall through to the GLOBAL catalog's own (possibly different)\n * routing for the same model id. Consulted by `spawnEligibilityManifest`\n * BEFORE the catalog fallback; absent/no-match ⇒ catalog derivation,\n * unchanged. */\n modelProviders?: Readonly<Record<string, CatalogProvider>>\n}\n\n/** The fully-resolved spec the driver applies mechanically. Structurally\n * matches `@agentproto/driver-agent-cli`'s `ResolvedAuthSpec` (each package\n * owns its own copy; the object flows across the boundary by shape). */\nexport interface ResolvedAuthSpec {\n mode: \"subscription\" | \"api-key\"\n credential?: string\n setEnv: string\n unsetEnv: string[]\n explicit: boolean\n enforce: \"always\" | \"when-configured\"\n /** True when NEITHER subscription nor api-key had any credential\n * available and `mode` above is therefore an arbitrary fallback pick —\n * never true when `mode` came from an explicit request or an actually-\n * available credential. Optional (undefined ⇒ false) so existing\n * callers/fixtures that predate this field keep working unchanged.\n * Exists so a fail-fast message can enumerate BOTH auth paths instead of\n * presenting the fallback mode as though the user configured it — the\n * root cause of a zero-credential user being told to buy a subscription\n * they never asked for. */\n neitherConfigured?: boolean\n /** File-based subscription (see `AdapterAuthDescriptor.authSubscription.\n * external`): the CLI reads its OWN local-login file, so the driver injects\n * NO credential — it only applies {@link unsetEnv} and does NOT fail-fast on\n * a missing `credential`. Money-safe: no bearer ever reaches an env var. */\n externalCredential?: boolean\n /** Non-authenticating hint: true when `providers.json` HAS a key for the\n * resolved provider that is currently being ignored because auth isn't\n * explicitly configured (no `defaults.adapters.<slug>.auth` block — the\n * `explicit` gate above, PR #321). NEVER used to authenticate — set by\n * the caller (session-spawn.ts) as a read-only peek, purely so the\n * fail-fast message can say \"you already have a key, it's just not\n * wired in\" instead of staying silent about it. `resolveAuthSpec` itself\n * never sets this (it does no I/O). */\n ignoredApiKeyInStore?: boolean\n /** When a gateway preset or custom route was matched, the `base_url` to\n * inject into adapter options so the client hits the gateway endpoint. */\n baseUrl?: string\n}\n\n/** Where the resolved credential came from — the observable billing axis\n * (DECISION 10②), never inferred. `\"claude-code-oauth\"` is the Mode-3\n * self-refreshing source: the subscription bearer was read fresh from the\n * local Claude Code login via the `claude-code-oauth` provision recipe.\n * `\"cli-local-login\"` is the file-based (external) subscription: the CLI\n * (codex/gemini) reads its OWN local-login file — the runtime injected no\n * bearer, it only verified the login is present and scrubbed api-key vars. */\nexport type CredentialSource =\n | \"explicit-config\"\n | \"providers-store\"\n | \"claude-code-oauth\"\n | \"cli-local-login\"\n | \"none\"\n\n/**\n * The OBSERVABLE echo (DECISION 9③ / 10②) — recorded on the session\n * descriptor so a verifier checks the RESOLUTION, never the model's\n * self-report. Never carries the raw credential (only its fingerprint).\n */\nexport interface AuthEcho {\n /** Billing endpoint / provider recorded for observability. Kept as `string`\n * (not the catalog enum) so gateway preset ids (e.g. \"moonshot\",\n * \"openai-direct\") that are not `CatalogProvider` values can still be\n * echoed on the session descriptor. */\n provider: string\n authMode: \"subscription\" | \"api-key\"\n credentialSource: CredentialSource\n setEnv: string\n fingerprint?: string\n}\n\n/**\n * Thrown when the operator requested a billing mode the adapter can't serve —\n * today only `\"subscription\"` on an adapter with no `authSubscription`. A\n * LOUD, distinct failure (DECISION 4②), never a silent downgrade to api-key.\n */\nexport class AuthResolutionError extends Error {\n readonly code = \"unsupported_auth_mode\"\n constructor(message: string) {\n super(message)\n this.name = \"AuthResolutionError\"\n }\n}\n\nexport interface ResolveAuthSpecInput {\n descriptor: AdapterAuthDescriptor\n /** `input.model ?? adapter default model` — for model-derived provider. */\n model?: string\n /** Per-spawn provider pin (`input.auth.provider`). */\n requestedProvider?: CatalogProvider\n /** Operator-requested mode; undefined ⇒ ordered preference. */\n requestedMode?: \"subscription\" | \"api-key\"\n /** Operator explicitly configured `auth` (DECISION 5). */\n explicit: boolean\n /** Subscription bearer credential, if configured (or resolved fresh by the\n * caller from a self-refreshing source). */\n subscriptionCredential?: string\n /** Observable ORIGIN label for `subscriptionCredential`, when it did not come\n * from a plain static config token — today only `\"claude-code-oauth\"` (the\n * caller resolved it fresh from the local Claude Code login). Purely a label\n * for the echo; NEVER affects mode/credential selection. Omitted ⇒ the\n * subscription credential (if any) is treated as `\"explicit-config\"`. */\n subscriptionCredentialSource?: CredentialSource\n /** File-based subscription only: the caller (impure) verified the CLI's OWN\n * local-login file is present (e.g. `~/.codex/auth.json` has a subscription\n * token) and fails LOUD before reaching here if not. Makes an `external`\n * `authSubscription` count as an available subscription for ordered-mode\n * selection — the login file IS the credential, even though the runtime\n * injects no bearer. Ignored for a non-external (bearer) authSubscription. */\n externalSubscriptionVerified?: boolean\n /** Explicit api-key credential from config, if configured. */\n apiKeyConfigCredential?: string\n /** api-key credential from `providers.json` (fetched by the caller). */\n apiKeyStoreCredential?: string\n /** Explicit gateway route from `SessionConfig.route.gateway`. When this\n * matches a `ProviderPreset` or a registered custom route, the route's\n * `baseUrl`/`keyEnv`/`scrubEnv` drive resolution instead of the model-\n * derived or fixed provider. */\n routeGateway?: string\n}\n\n/**\n * THE billing-auth resolver (DECISIONS 4, 6, 9, 10). Pure: given the adapter\n * descriptor + raw config material + (caller-fetched) store key, it decides\n * the provider, the mode (ordered — subscription over api-key when a\n * subscription credential is present; a requested-but-unsupported mode throws\n * `unsupported_auth_mode`), the env var to SET, the derived SCRUB set, and the\n * credential + its source. Returns the driver `spec` + the observable `echo`,\n * or `undefined` when no provider resolves (⇒ ambient, no injection — never\n * guess). NEVER falls back to a default provider/model. Fail-loud on a\n * configured-but-missing credential is deferred to the driver's mechanical\n * apply (it engages then throws `missing_auth_credential`), so the `explicit`\n * / `enforce` signals are carried through on the spec.\n */\nexport function modelIdPrefixProvider(modelId: string): string | undefined {\n const slash = modelId.indexOf(\"/\")\n return slash > 0 ? modelId.slice(0, slash) : undefined\n}\n\n/**\n * Resolve WHICH of an adapter's declared subscription surface(s) applies on\n * `endpoint` — THE one lookup every subscription-eligibility site shares\n * (`resolveAuthSpec` below, plus the three mirrored direct-methods\n * projections in `session-spawn.ts` / `session-restart-core.ts` /\n * `catalog-models.ts`), so they can never drift. Returns the matching\n * surface, or `undefined` when none applies — callers that only need the\n * old boolean predicate check `!== undefined`.\n *\n * `authSubscription` is a single surface OR an array of surfaces (one\n * per provider, for an adapter with more than one native OAuth login —\n * mastracode/opencode: anthropic AND openai). For a SINGLE surface, this\n * preserves the original predicate exactly: a provider-scoped surface (pi's\n * `ANTHROPIC_OAUTH_TOKEN`) applies only on its own provider; an unscoped one\n * (fixed-provider adapters — claude-code, codex) applies wherever the\n * adapter itself does; an unknown endpoint is treated as applying — the\n * fixed-provider case, where the caller had no per-model derivation to\n * offer. For an ARRAY of surfaces, an explicit provider match wins;\n * otherwise the one unscoped surface (if any) applies. An unknown endpoint\n * against MULTIPLE provider-scoped surfaces cannot be disambiguated — unlike\n * the single-surface case, guessing here would pick a specific but WRONG\n * provider's bearer door, not just skip a scrub — so nothing matches.\n *\n * This lookup REPLACED the old `modelDerivedApiKey ⇒ subscription works`\n * assumption (\"Anthropic OATs work as API keys\"): an OAT presented on the\n * x-api-key header is rejected by Anthropic's edge regardless of account\n * validity — observed live as opencode's opaque \"Internal error: API key is\n * invalid\" when the runtime injected a subscription token into\n * `ANTHROPIC_API_KEY`. Subscription support now requires an EXPLICIT\n * `authSubscription` declaration naming the env var the CLI actually reads\n * a bearer from.\n */\nexport function subscriptionSurfaceFor(\n sub: AdapterAuthDescriptor[\"authSubscription\"],\n endpoint: string | undefined,\n): AuthSubscriptionSurface | undefined {\n if (sub === undefined) return undefined\n if (!Array.isArray(sub)) {\n return sub.provider === undefined || endpoint === undefined || sub.provider === endpoint\n ? sub\n : undefined\n }\n return (\n sub.find(s => s.provider !== undefined && s.provider === endpoint) ??\n sub.find(s => s.provider === undefined)\n )\n}\n\nexport function resolveAuthSpec(\n input: ResolveAuthSpecInput,\n): { spec: ResolvedAuthSpec; echo: AuthEcho } | undefined {\n // 0. Gateway route: a preset or custom route wins over model-derived/fixed\n // provider because the operator explicitly chose a billing rail. It drives\n // base_url, the API-key env var, and the scrub set.\n const gatewayPreset = input.routeGateway\n ? findAnthropicGatewayPreset(input.routeGateway)\n : undefined\n const customRoute =\n input.routeGateway && !gatewayPreset\n ? resolveCustomRoute(input.routeGateway)\n : undefined\n const gatewayRoute = gatewayPreset ?? customRoute\n\n // A gateway preset whose id is the adapter's own fixed native provider is a\n // native match (e.g. codex with `provider: \"openai\"` and `route.gateway:\n // \"openai\"`). The route still selects the auth profile / API-key env var, but\n // the resolved base_url would point at a proxy/alternate endpoint the adapter\n // never asked for — codex has no `base_url` option and already talks to the\n // OpenAI endpoint natively. Skip the preset base_url so session-spawn routing\n // does not try to inject it (and so restart/resume paths don't re-inject it),\n // and keep subscription mode eligible because the route is direct, not a\n // third-party gateway.\n const isNativeGatewayPreset =\n gatewayPreset !== undefined && input.routeGateway === input.descriptor.provider\n\n // 1. Provider: per-spawn pin → adapter-fixed → model-derived. None ⇒\n // ambient (no injection); an unknown/free-form model id lands here too.\n // A matched gateway route overrides all three — the route IS the provider.\n // The only exception is a native-provider gateway preset, which is treated\n // as a direct route (same provider the adapter already bills through).\n let provider: string | undefined\n let baseUrl: string | undefined\n let apiKeyEnv: string\n let gatewayScrub: string[] = []\n if (gatewayRoute) {\n provider = input.routeGateway\n baseUrl = isNativeGatewayPreset\n ? undefined\n : gatewayPreset?.baseUrl ?? customRoute?.baseUrl\n apiKeyEnv =\n gatewayPreset?.keyEnv ??\n customRoute?.authEnv ??\n (provider ? providerEnvVar(provider) : \"\")\n gatewayScrub = gatewayPreset ? [...gatewayPreset.scrubEnv] : []\n } else {\n // Same precedence as `spawnEligibilityManifest` (session-spawn.ts) —\n // deliberately kept in lockstep. A model-derived-api-key adapter has no\n // fixed `provider`, so without the `modelProviders` tier the catalog\n // fallback is the only signal here, and the catalog's route for an id can\n // legitimately differ from what THIS adapter bills it through (D3: pi\n // bills `moonshotai/kimi-k2.7-code` via `moonshot`; the catalog routes\n // that same id to `openrouter`). Consulting it in the eligibility\n // projection but NOT here would let the two disagree: the access-profile\n // check would clear a moonshot wallet while this resolver injected an\n // OPENROUTER_API_KEY — billing the wrong wallet on a spawn that passed\n // its own gate.\n provider =\n input.requestedProvider ??\n input.descriptor.provider ??\n (input.model ? input.descriptor.modelProviders?.[input.model] : undefined) ??\n (input.model && input.descriptor.modelDerivedApiKey\n ? modelIdPrefixProvider(input.model)\n : undefined) ??\n (input.model ? getModelProvider(input.model) : undefined)\n if (!provider) return undefined\n apiKeyEnv = providerEnvVar(provider)\n }\n if (!provider) return undefined\n\n // Subscription support requires an EXPLICIT, provider-matching\n // authSubscription declaration ({@link subscriptionSurfaceFor}) — the old\n // `|| modelDerivedApiKey` clause assumed \"Anthropic OATs work as API\n // keys\", which is false on the x-api-key header: it silently injected the\n // subscription token into ANTHROPIC_API_KEY for opencode/mastracode/jcode\n // and the upstream rejected it as an invalid key. A multi-provider\n // adapter that DOES have a bearer door declares it (pi:\n // `authSubscription: {setEnv: \"ANTHROPIC_OAUTH_TOKEN\", provider:\n // \"anthropic\"}`); a multi-SURFACE adapter declares one entry per provider\n // (mastracode/opencode: an anthropic AND an openai external surface) and\n // this resolves the one matching the spawn's resolved provider; the rest\n // fail fast below instead of failing upstream. Gateway routes stay\n // API-key only; a native fixed-provider gateway preset (e.g. codex +\n // route.gateway \"openai\") is a direct route, so subscription stays\n // eligible there.\n const sub = subscriptionSurfaceFor(input.descriptor.authSubscription, provider)\n const supportsSub =\n sub !== undefined && (gatewayRoute === undefined || isNativeGatewayPreset)\n const enforce = input.descriptor.authEnforce ?? \"when-configured\"\n\n // File-based (external) subscription (codex/gemini): the CLI reads its OWN\n // local-login file, so there is no bearer to inject. The login file IS the\n // credential — availability comes from the caller's fail-loud presence check\n // (`externalSubscriptionVerified`), NOT from an injected token.\n const external = supportsSub && sub?.external === true\n const subCredAvailable =\n input.subscriptionCredential !== undefined ||\n (external && input.externalSubscriptionVerified === true)\n const apiCredAvailable =\n input.apiKeyConfigCredential !== undefined || input.apiKeyStoreCredential !== undefined\n\n // 2/3. Mode: explicit request (validated) OR ordered preference (DECISION\n // 10 — subscription first when supported; never silently pick api-key\n // while a subscription credential is present and preferred). When\n // NEITHER credential is available, `mode` still needs a value (it\n // drives `setEnv`/scrub below) but is an ARBITRARY pick, not a real\n // signal — this used to silently fall back to `preference[0]`\n // (\"subscription\" for any adapter that supports it), which is exactly\n // how a zero-credential, api-key-only user got told to buy a\n // subscription. `neitherConfigured` flags that case so callers never\n // present the fallback mode as though it meant something.\n let mode: \"subscription\" | \"api-key\"\n let neitherConfigured = false\n if (input.requestedMode) {\n if (input.requestedMode === \"subscription\" && !supportsSub) {\n throw new AuthResolutionError(\n `auth mode \"subscription\" is not supported for provider \"${provider}\" on this ` +\n `adapter (no matching authSubscription surface): it presents credentials on ` +\n `the api-key header, where a subscription OAuth token is rejected upstream as ` +\n `an invalid key. Use an api-key profile (a console key) or route the model ` +\n `via a gateway profile (e.g. openrouter) instead.`,\n )\n }\n mode = input.requestedMode\n } else {\n const preference: Array<\"subscription\" | \"api-key\"> = supportsSub\n ? [\"subscription\", \"api-key\"]\n : [\"api-key\"]\n const available = preference.find(m =>\n m === \"subscription\" ? subCredAvailable : apiCredAvailable,\n )\n mode = available ?? preference[0]!\n neitherConfigured = available === undefined\n }\n\n // 4/5. setEnv + credential + source for the resolved mode.\n let setEnv: string\n let credential: string | undefined\n let credentialSource: CredentialSource\n let externalCredential = false\n if (mode === \"subscription\" && external) {\n // File-based: inject NOTHING (setEnv empty). The credential lives in the\n // CLI's own login file; the source label records that for the echo. The\n // scrub below still removes the api-key vars so a leftover key can't flip\n // billing. Money-safe by construction — no bearer is ever set. When the\n // login was NOT verified (the unconfigured ordered-preference fallback,\n // which the driver never engages because it isn't explicit), the echo\n // stays honest as \"none\" rather than claiming a local login was used.\n setEnv = \"\"\n credential = undefined\n credentialSource = subCredAvailable ? \"cli-local-login\" : \"none\"\n externalCredential = true\n } else if (mode === \"subscription\") {\n // Bearer authSubscription declares setEnv (schema-enforced) — for pi\n // that's ANTHROPIC_OAUTH_TOKEN, its documented bearer door. The\n // `?? apiKeyEnv` fallback is defensive only: subscription mode can no\n // longer resolve without a matching authSubscription (see supportsSub).\n setEnv = sub?.setEnv ?? apiKeyEnv\n credential = input.subscriptionCredential\n credentialSource =\n credential !== undefined\n ? (input.subscriptionCredentialSource ?? \"explicit-config\")\n : \"none\"\n } else {\n // `apiKeyEnv` is the preset's/provider's conventional key-env — the\n // OPERATOR's providers-store lookup key (unaffected below; the store\n // read is keyed by PROVIDER id, never by this env name). The var\n // actually INJECTED into the child is a separate fact: for a gateway\n // route, an adapter that declares `gatewayAuth` (claude-code/claude-sdk\n // → ANTHROPIC_AUTH_TOKEN) receives the credential there instead, because\n // that's the var its OWN wire protocol reads a bearer from — injecting\n // the preset's `keyEnv` would land the credential in a var nothing reads\n // (the D4 bug: OPENROUTER_API_KEY set, but the Anthropic SDK only ever\n // looks at ANTHROPIC_AUTH_TOKEN). An adapter with no `gatewayAuth`\n // (hermes) keeps `apiKeyEnv` verbatim — it genuinely reads that var.\n setEnv =\n gatewayRoute && input.descriptor.gatewayAuth?.setEnv\n ? input.descriptor.gatewayAuth.setEnv\n : apiKeyEnv\n if (input.apiKeyConfigCredential !== undefined) {\n credential = input.apiKeyConfigCredential\n credentialSource = \"explicit-config\"\n } else if (input.apiKeyStoreCredential !== undefined) {\n credential = input.apiKeyStoreCredential\n credentialSource = \"providers-store\"\n } else {\n credential = undefined\n credentialSource = \"none\"\n }\n }\n\n // 4. Derived scrub: every conflicting billing-credential var EXCEPT the one\n // being set, plus (native/subscription mode only) the adapter's gateway\n // hygiene. Single-credential provider (no authSubscription) → empty scrub\n // (the setEnv overwrite already prevents a leak). Gateway routes also\n // apply the preset's scrubEnv (e.g. ANTHROPIC_API_KEY when fronting\n // Moonshot) so native credentials don't leak to third-party hosts.\n const unsetEnvSet = new Set<string>()\n if (mode === \"subscription\" && sub?.unsetEnvAdd) {\n for (const e of sub.unsetEnvAdd) unsetEnvSet.add(e)\n }\n for (const e of gatewayScrub) unsetEnvSet.add(e)\n\n const credVars = new Set<string>([apiKeyEnv])\n if (sub) {\n if (sub.setEnv) credVars.add(sub.setEnv)\n for (const c of sub.conflictEnv ?? []) credVars.add(c)\n }\n credVars.delete(setEnv)\n for (const c of credVars) unsetEnvSet.add(c)\n const unsetEnv = [...unsetEnvSet]\n\n const spec: ResolvedAuthSpec = {\n mode,\n ...(credential !== undefined ? { credential } : {}),\n setEnv,\n unsetEnv,\n explicit: input.explicit,\n enforce,\n ...(externalCredential ? { externalCredential: true } : {}),\n ...(neitherConfigured ? { neitherConfigured } : {}),\n ...(baseUrl !== undefined ? { baseUrl } : {}),\n }\n const echo: AuthEcho = {\n provider,\n authMode: mode,\n credentialSource,\n setEnv,\n // A verified file-based login has no injected credential to fingerprint, so\n // carry a NON-SECRET marker (`subscription · local-login`) instead — it lets\n // the descriptor echo record the spawn for verifiability (same guard as a\n // real fingerprint) without inventing a fake secret shape.\n ...(credential !== undefined\n ? { fingerprint: credentialFingerprint(mode, credential) }\n : credentialSource === \"cli-local-login\"\n ? { fingerprint: `${mode} · local-login` }\n : {}),\n }\n return { spec, echo }\n}\n\n/** The only `auth.source` value understood today (Mode 3): read the\n * subscription bearer fresh from the local Claude Code login via the\n * `claude-code-oauth` provision recipe. */\nexport const CLAUDE_CODE_OAUTH_SOURCE = \"claude-code-oauth\"\n\n/**\n * `auth.source`/profile-`source` values that are RECOGNIZED elsewhere in the\n * system (the codex/gemini adapters' own `authSubscription: { external: true\n * }` file-based login, and the \"Use my existing Codex/Gemini login\" profile\n * flow — see `verifyLocalLoginPresent`) but can never be satisfied by {@link\n * resolveSubscriptionCredential}: they name a FILE the target CLI reads\n * itself, not an extractable bearer, so there is nothing this Mode-3 resolver\n * could ever fetch for them. Used only to sharpen the `unsupported_auth_source`\n * error for THESE specific values (e.g. spawning `pi` — which has no\n * `authSubscription.external` — with `auth.source: \"codex\"`, likely inherited\n * from a codex-flavored parent session or auth profile) instead of the\n * generic \"only claude-code-oauth is supported\" message, which doesn't\n * explain why a seemingly-valid value (codex IS a real source elsewhere)\n * failed here. Does NOT change behavior — still fails loud either way. */\nexport const FILE_BASED_AUTH_SOURCES: ReadonlySet<string> = new Set([\"codex\", \"gemini\"])\n\n/**\n * Raised when `auth.source` is configured but cannot yield a credential — an\n * unknown source value (`unsupported_auth_source`) or the recipe resolving to\n * nothing / not-logged-in (`auth_source_unresolved`). A LOUD, actionable\n * failure surfaced as a spawn error, NEVER a silent fallthrough to a static or\n * ambient credential (mirrors {@link AuthResolutionError}'s discipline).\n */\nexport class SubscriptionSourceError extends Error {\n readonly code: \"unsupported_auth_source\" | \"auth_source_unresolved\"\n constructor(\n code: \"unsupported_auth_source\" | \"auth_source_unresolved\",\n message: string,\n ) {\n super(message)\n this.name = \"SubscriptionSourceError\"\n this.code = code\n }\n}\n\nexport interface ResolveSubscriptionCredentialInput {\n /** Per-spawn explicit static token (`input.auth.token`) — wins over source. */\n explicitToken?: string\n /** Effective opt-in source (`input.auth.source ?? config auth.source`). */\n source?: string\n /** Config static token (`defaults.adapters.<slug>.auth.token`) — the\n * lowest-precedence fallback, used only when NEITHER an explicit per-spawn\n * token nor a source applies. */\n fallbackStaticToken?: string\n}\n\nexport interface SubscriptionCredentialResolution {\n /** Resolved subscription bearer, or undefined ⇒ nothing configured (the\n * driver's fail-fast `missing_auth_credential` still owns that case). */\n credential?: string\n /** Observable origin of {@link credential} for the echo. */\n source?: CredentialSource\n}\n\n/**\n * Resolve the subscription (oauth-bearer) credential + its observable origin,\n * placing the self-refreshing `source` (Mode 3) BETWEEN the two static tokens\n * (SPEC §2):\n * a. explicit per-spawn `input.auth.token` (static) — wins over everything.\n * b. else `source: \"claude-code-oauth\"` → resolve FRESH via the injected\n * recipe resolver (Keychain / credentials file) ⇒ origin\n * `\"claude-code-oauth\"`.\n * c. else config.json static `auth.token` ⇒ origin `\"explicit-config\"`.\n * d. else nothing (⇒ driver fail-fast `missing_auth_credential`, unchanged).\n *\n * The recipe I/O is INJECTED (`resolveSourceToken`) so this stays pure and\n * unit-testable without touching the real Keychain — the impure resolver lives\n * in `claude-code-oauth-source.ts`. Fails LOUD ({@link SubscriptionSourceError})\n * on an unknown source or an empty recipe; never a silent fallthrough. Only\n * touches the subscription path — api-key mode never reaches here.\n */\nexport async function resolveSubscriptionCredential(\n input: ResolveSubscriptionCredentialInput,\n resolveSourceToken: (source: string) => Promise<string>,\n): Promise<SubscriptionCredentialResolution> {\n // (a) An explicit per-spawn token is a deliberate one-off override — it wins\n // even over a persisted `source`.\n if (input.explicitToken !== undefined) {\n return { credential: input.explicitToken, source: \"explicit-config\" }\n }\n // (b) Opt-in self-refreshing source.\n if (input.source !== undefined) {\n if (input.source !== CLAUDE_CODE_OAUTH_SOURCE) {\n // A file-based source (codex/gemini) is a real value elsewhere in the\n // system — just not one this bearer-fetch resolver can ever satisfy.\n // Name that explicitly so the caller understands WHY a seemingly-valid\n // source failed here, instead of implying \"claude-code-oauth\" is the\n // only auth concept that exists.\n const message = FILE_BASED_AUTH_SOURCES.has(input.source)\n ? `auth.source: \"${input.source}\" is a file-based (external) login — only ` +\n `adapters that declare \"authSubscription.external\" (e.g. codex, gemini ` +\n `themselves) can use it, and this adapter isn't one of them, so there is ` +\n `no bearer to inject. Use auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" for ` +\n `a Claude subscription, or configure a real API key for this adapter's ` +\n `target provider instead.`\n : `auth.source: \"${input.source}\" is not supported — the only supported ` +\n `value is \"${CLAUDE_CODE_OAUTH_SOURCE}\".`\n throw new SubscriptionSourceError(\"unsupported_auth_source\", message)\n }\n let token: string\n try {\n token = await resolveSourceToken(input.source)\n } catch (err) {\n throw new SubscriptionSourceError(\n \"auth_source_unresolved\",\n `auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" but no Claude Code login ` +\n `found — run \\`claude\\` and /login (or \\`claude setup-token\\`) first ` +\n `(${err instanceof Error ? err.message : String(err)}).`,\n )\n }\n if (!token) {\n throw new SubscriptionSourceError(\n \"auth_source_unresolved\",\n `auth.source: \"${CLAUDE_CODE_OAUTH_SOURCE}\" but no Claude Code login ` +\n `found — run \\`claude\\` and /login (or \\`claude setup-token\\`) first.`,\n )\n }\n return { credential: token, source: CLAUDE_CODE_OAUTH_SOURCE }\n }\n // (c) Config-level static token.\n if (input.fallbackStaticToken !== undefined) {\n return { credential: input.fallbackStaticToken, source: \"explicit-config\" }\n }\n // (d) Nothing configured.\n return {}\n}\n\n/** Manifest-declared AIP-45 option id + type, the minimum an adapter\n * resolver needs to expose for `normalizeSkillsOption` below. Mirrors\n * `AgentCliOption`'s `id`/`type` fields without importing\n * `@agentproto/driver-agent-cli` into the runtime package. */\nexport interface DeclaredAdapterOption {\n id: string\n type: \"boolean\" | \"integer\" | \"string\" | \"enum\"\n}\n\n/**\n * Fold the resolved `skills` list into `options.skills` using whatever\n * shape the adapter's manifest declares for that option id (today, only\n * `type: \"string\"` exists for a skills-shaped option — e.g. hermes'\n * comma-joined `--skills a,b`). Adapters with no declared `skills` option\n * (e.g. claude-code, which auto-discovers from `~/.claude/skills`) are a\n * documented no-op — the effective skills list has nowhere to go, so it's\n * dropped rather than guessing a flag the manifest didn't declare.\n *\n * An `options.skills` already present (from config defaults or the\n * explicit call) is respected as-is and never overwritten here.\n */\nexport function normalizeSkillsOption(\n skills: string[],\n options: Record<string, boolean | number | string>,\n declaredOptions: readonly DeclaredAdapterOption[] | undefined,\n): Record<string, boolean | number | string> {\n if (skills.length === 0 || \"skills\" in options) return options\n const skillsOption = declaredOptions?.find(o => o.id === \"skills\")\n if (!skillsOption || skillsOption.type !== \"string\") return options\n return { ...options, skills: skills.join(\",\") }\n}\n","/**\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 resolveContextWindow,\n formatTokens,\n LLM_PRICING_CATALOG,\n MODEL_ALIASES,\n} from \"@agentproto/model-catalog/llm\"\nimport { getAnthropicGatewayPreset } from \"@agentproto/provider-presets\"\nimport { subscriptionSurfaceFor, 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`. Exported so `model-wire.ts` can reuse the SAME set when\n * deciding whether a wire model needs a literal router-prefix (never a\n * second hand-maintained list). */\nexport const 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 /** Human-readable max input tokens (e.g. `\"1M\"`, `\"200k\"`), from the\n * live-synced CONTEXT_WINDOWS table (`resolveContextWindow`); null when\n * no synced provider carries this id. Consumers wanting the raw integer\n * can re-resolve via `resolveContextWindow(ref product)` or parse. */\n contextWindow: string | null\n /** Human-readable max output tokens (same source/format), null when the\n * source doesn't publish a completion cap for this id. */\n maxOutput: string | 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 endpoint?: string,\n): AuthMethod[] {\n const methods: AuthMethod[] = []\n // oauth-bearer requires an explicit, provider-matching subscription\n // surface — see `subscriptionSurfaceFor`'s doc in spawn-defaults.ts.\n if (subscriptionSurfaceFor(descriptor?.authSubscription, endpoint) !== undefined) {\n methods.push(\"oauth-bearer\")\n }\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, route)\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 ctx = resolveContextWindow(row.product)\n const route: CatalogRoute = {\n route: row.route,\n ref: row.ref,\n baseUrl: row.baseUrl,\n pricing: row.pricing,\n // Live-synced context window (max input) + max output, when a synced\n // provider (Anthropic/Groq/xAI/Moonshot/Mistral/Google) carries this\n // id — null otherwise. All CONTEXT_WINDOWS providers get this, not\n // only Anthropic. Formatted for display (`1M`/`200k`); consumers\n // needing the raw integer resolve it themselves.\n contextWindow: formatTokens(ctx?.contextWindow),\n maxOutput: formatTokens(ctx?.maxOutput),\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/** Verdict of the spawn-time adapter-capability guard\n * ({@link checkModelAdapterEligibility}). */\nexport interface ModelAdapterEligibility {\n ok: boolean\n /** Other installed adapters whose catalog row already curates this exact\n * model on this exact route — the actionable set to re-spawn onto. Empty\n * when `ok`, or when NO installed adapter (this one included) curates the\n * combination — nobody has proven it reachable at all, so this guard has\n * nothing to reject on. */\n compatibleAdapters: string[]\n}\n\n/**\n * Adapter-capability spawn guard: the money-safety guard above\n * ({@link checkModelWalletEligibility}) proves the resolved ROUTE can bill\n * `model`; it says nothing about whether THIS adapter's own manifest can\n * actually reach it there. A fixed hand-curated client (claude-code's ACP\n * wrapper validates every model id against its own live selector and 404s on\n * anything it doesn't recognize) can be routeSelection:\"free\" — genuinely able\n * to reach several gateways — while still only supporting a small, explicitly\n * vetted model list on each one. A pass-through client (opencode/mastracode/\n * hermes/jcode, routeSelection:\"derived-from-model\") instead auto-derives a\n * broad curated list straight from the pricing catalog, so it ends up\n * supporting far more of a gateway's models without needing a per-model\n * allowlist maintained by hand.\n *\n * Takes the SAME `CatalogModelsResponse` shape `buildCatalogModels` (and\n * therefore `catalog_models`) produces — reusing that exact join, never a\n * parallel per-adapter table — and looks up whether `adapterSlug` is among\n * the resolved (vendor, product, route) row's `adapters`.\n *\n * `ok:true` when EITHER no installed adapter's catalog row covers this exact\n * model+route (nobody has proven it servable at all — the same never-reject-\n * an-unknown-combination stance {@link checkModelWalletEligibility} takes), OR\n * `adapterSlug` is already among the row's adapters. `ok:false` only when the\n * row exists and excludes `adapterSlug` — a proven \"wrong client for this\n * model\" mismatch, with the row's other adapters (if any) as the actionable\n * alternative.\n */\nexport function checkModelAdapterEligibility(\n catalog: CatalogModelsResponse,\n adapterSlug: string,\n model: string,\n route: string,\n): ModelAdapterEligibility {\n const target = resolveModelId(model)\n const routeEntry = catalog.vendors\n .find(v => v.vendor === target.vendor)\n ?.products.find(p => p.product === target.product)\n ?.routes.find(r => r.route === route)\n if (!routeEntry) return { ok: true, compatibleAdapters: [] }\n if (routeEntry.adapters.includes(adapterSlug)) return { ok: true, compatibleAdapters: [] }\n return { ok: false, compatibleAdapters: routeEntry.adapters }\n}\n\n/**\n * The actionable fail-fast message for {@link checkModelAdapterEligibility} —\n * names the adapters that DO already curate the model on this route (when any\n * do) so the operator can re-spawn without opening the catalog by hand. Never\n * auto-switches adapters for the operator; only rejects.\n */\nexport function modelAdapterIncompatibleMessage(opts: {\n prefix: string\n adapter: string\n model: string\n route: string\n compatibleAdapters: string[]\n}): string {\n const alternative =\n opts.compatibleAdapters.length > 0\n ? `Adapters that already support it on \"${opts.route}\": ${opts.compatibleAdapters\n .map(a => `\"${a}\"`)\n .join(\", \")} — re-spawn with one of those instead.`\n : `No installed adapter currently supports it on \"${opts.route}\" either — check ` +\n `\\`catalog_models\\` for a route this model IS servable on.`\n return (\n `${opts.prefix}: adapter \"${opts.adapter}\" does not declare support for model \"${opts.model}\" ` +\n `on route \"${opts.route}\" and would 404/reject upstream even though that route can bill it. ` +\n `${alternative} This guard only rejects; it never switches adapters for you.`\n )\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"]}
|