@happyvertical/smrt-inference 0.60.15 → 0.60.16

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.
Files changed (57) hide show
  1. package/AGENTS.md +10 -5
  2. package/agents/fusions.md +16 -3
  3. package/agents/hosted.md +29 -13
  4. package/agents/settings.md +30 -7
  5. package/dist/chunks/{InferenceOverrideCollection-wl7sI-3C.js → InferenceOverrideCollection-Dat4TrK5.js} +29 -4
  6. package/dist/chunks/InferenceOverrideCollection-Dat4TrK5.js.map +1 -0
  7. package/dist/chunks/{provider-config-4Bx8N52X.js → provider-config-BSpIQRVM.js} +37 -11
  8. package/dist/chunks/{provider-config-4Bx8N52X.js.map → provider-config-BSpIQRVM.js.map} +1 -1
  9. package/dist/fusion.d.ts.map +1 -1
  10. package/dist/hosted.d.ts +31 -24
  11. package/dist/hosted.d.ts.map +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/manifest.json +1 -1
  14. package/dist/models/InferenceOverride.d.ts.map +1 -1
  15. package/dist/models.js +1 -1
  16. package/dist/node.js +86 -33
  17. package/dist/node.js.map +1 -1
  18. package/dist/register.d.ts.map +1 -1
  19. package/dist/resolver.d.ts.map +1 -1
  20. package/dist/resolver.js +4 -1
  21. package/dist/resolver.js.map +1 -1
  22. package/dist/server.d.ts +3 -2
  23. package/dist/server.d.ts.map +1 -1
  24. package/dist/server.js +194 -41
  25. package/dist/server.js.map +1 -1
  26. package/dist/settings/layers.d.ts +14 -0
  27. package/dist/settings/layers.d.ts.map +1 -1
  28. package/dist/settings/override-service.d.ts.map +1 -1
  29. package/dist/settings/resolver.d.ts +7 -0
  30. package/dist/settings/resolver.d.ts.map +1 -1
  31. package/dist/settings/settings-service.d.ts +80 -7
  32. package/dist/settings/settings-service.d.ts.map +1 -1
  33. package/dist/settings/targets.d.ts +21 -2
  34. package/dist/settings/targets.d.ts.map +1 -1
  35. package/dist/settings/transcripts.d.ts +33 -0
  36. package/dist/settings/transcripts.d.ts.map +1 -0
  37. package/dist/smrt-knowledge.json +17 -11
  38. package/dist/smrt-knowledge.okf.md +2 -2
  39. package/dist/svelte/components/InferenceFusionList.svelte +6 -0
  40. package/dist/svelte/components/InferenceFusionList.svelte.d.ts.map +1 -1
  41. package/dist/svelte/components/InferenceSettingsPanel.svelte +49 -2
  42. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts +21 -2
  43. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts.map +1 -1
  44. package/dist/svelte/components/InferenceTranscriptSettings.svelte +165 -0
  45. package/dist/svelte/components/InferenceTranscriptSettings.svelte.d.ts +18 -0
  46. package/dist/svelte/components/InferenceTranscriptSettings.svelte.d.ts.map +1 -0
  47. package/dist/svelte/components/InferenceUsageTable.svelte +64 -14
  48. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts +10 -4
  49. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts.map +1 -1
  50. package/dist/svelte/index.d.ts +1 -1
  51. package/dist/svelte/index.d.ts.map +1 -1
  52. package/dist/svelte/index.js +1 -1
  53. package/dist/svelte/types.d.ts +66 -1
  54. package/dist/svelte/types.d.ts.map +1 -1
  55. package/dist/svelte/types.js +28 -0
  56. package/package.json +10 -10
  57. package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js.map +0 -1
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","names":["model","next","audit","result"],"sources":["../src/hosted.ts","../src/node.ts"],"sourcesContent":["/**\n * Hosted inference provisioning (#4759), Node-only (`./node`).\n *\n * When a member gains `inference.hosted`, SMRT mints them a per-user virtual\n * key on the operator's AI gateway (`bifrost` or `litellm`, through the\n * `ai.admin` surface of `@happyvertical/ai`; SMRT never talks to a gateway's\n * admin API itself) and stores it where the BYO path already looks: the\n * user's `inference.provider-config` secret (#4756) with `source: 'hosted'`\n * and `mode: 'hosted'` (#4757). Switching to hosted therefore changes only\n * the stored provider config.\n *\n * Storage (all smrt-secrets rows under `context = tenantId`, every write and\n * delete audited through `SecretAuditLog` with an `operation` detail):\n * - `inference.provider-config:user:<userId>`: the hosted binding (holds the\n * virtual key; the only place it lives).\n * - `inference.hosted:user:<userId>`: provisioning state: status, lifecycle\n * generation, the key's non-secret id and alias, the budget, and the\n * user's prior provider config (restored on revoke).\n * - `inference.hosted:project`: the tenant's gateway project id.\n *\n * Idempotency: provisioning runs under `runOnce()` per `(tenantId, userId,\n * generation)`, so concurrent deliveries create one key; the generation\n * advances on every revoke, so a later re-provision runs again. Lifecycle\n * operations run one at a time per process and, on PostgreSQL, under a\n * per-tenant advisory lock across processes. A revoke ends local\n * use of the key whatever the gateway answers; deletions the gateway could\n * not perform are kept as `pendingGatewayRevocations` and retried by\n * `revoke()` / `reconcileTenant()`.\n *\n * The key never appears in returned objects, errors, logs, usage tags,\n * audit details or `runOnce()` results: usage rows carry `source: hosted`\n * and the key's alias (`gatewayKeyAlias`) only.\n */\nimport type {\n AIAdminBudget,\n AIAdminInterface,\n AIAdminProject,\n AIVirtualKey,\n CreateAIVirtualKeyOptions,\n} from '@happyvertical/ai';\nimport {\n ensureSystemTables,\n INFERENCE_TIERS,\n type InferenceTier,\n isPostgresDatabase,\n runOnce,\n} from '@happyvertical/smrt-core';\nimport { SmrtError } from '@happyvertical/smrt-core/smrt-error';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport { withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { classifyInferenceFailure } from './classify.js';\nimport {\n INFERENCE_PROVIDER_CONFIG_SECRET_TYPE,\n type InferenceProviderBinding,\n type InferenceProviderConfig,\n inferenceProviderBindingFor,\n inferenceProviderConfigSecretName,\n parseInferenceProviderConfig,\n} from './provider-config.js';\nimport { invalidateUserInferenceResolvers } from './resolver.js';\n\n/** The feature key that entitles hosted inference (`smrt-features`' `FEATURE_INFERENCE_HOSTED`). */\nexport const HOSTED_INFERENCE_FEATURE_KEY = 'inference.hosted';\n\n/** smrt-secrets category of the hosted state and project rows. */\nexport const HOSTED_INFERENCE_SECRET_CATEGORY = 'inference.hosted';\n\n/** Gateway providers of `@happyvertical/ai` that expose `ai.admin`. */\nexport const HOSTED_GATEWAY_PROVIDERS = ['bifrost', 'litellm'] as const;\n\nexport type HostedGatewayProvider = (typeof HOSTED_GATEWAY_PROVIDERS)[number];\n\n/** Budget reset windows; mapped to the gateway's reset duration. */\nexport const HOSTED_BUDGET_WINDOWS = ['day', 'week', 'month', 'year'] as const;\n\nexport type HostedBudgetWindow = (typeof HOSTED_BUDGET_WINDOWS)[number];\n\nconst WINDOW_DURATION: Record<HostedBudgetWindow, string> = {\n day: '1d',\n week: '7d',\n month: '30d',\n year: '365d',\n};\n\n/**\n * A hosted user's spend cap. Money is an integer: micro-dollars (USD ×\n * 1,000,000), the unit #4760's spend gate meters. Converted to the gateway's\n * USD `maxLimit` only at the admin call.\n */\nexport interface HostedInferenceBudget {\n limitMicros: number;\n /** Reset window. Default `month`. */\n window?: HostedBudgetWindow;\n}\n\n/** Error codes of {@link HostedInferenceError}. */\nexport type HostedInferenceErrorCode =\n | 'HOSTED_INFERENCE_INVALID'\n | 'HOSTED_GATEWAY_NOT_CONFIGURED'\n | 'HOSTED_GATEWAY_UNSUPPORTED'\n | 'HOSTED_GATEWAY_FAILED'\n | 'HOSTED_STATE_INVALID'\n | 'HOSTED_NOT_ENTITLED';\n\n/**\n * A provisioning failure. Messages name fields and operations, never a key,\n * an admin credential or a gateway response body.\n */\nexport class HostedInferenceError extends SmrtError {\n constructor(\n message: string,\n code: HostedInferenceErrorCode,\n details: Record<string, unknown> = {},\n ) {\n super(message, code, 'configuration', details);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Gateway configuration and admin client\n// ---------------------------------------------------------------------------\n\n/**\n * The operator's gateway. Admin credentials stay in this object (built once\n * from the host environment) and are only handed to `@happyvertical/ai`.\n */\nexport interface HostedGatewayConfig {\n provider: HostedGatewayProvider;\n /** OpenAI-compatible inference endpoint the virtual keys are used against. */\n baseUrl: string;\n /** Admin API root, when it differs from `baseUrl`. */\n adminUrl?: string;\n /** LiteLLM master key, or a Bifrost admin virtual key. */\n adminApiKey?: string;\n /** Bifrost governance Basic-auth user. */\n adminUser?: string;\n /** Bifrost governance Basic-auth password. */\n adminPassword?: string;\n /** Default model of the stored binding. */\n model?: string;\n /** Per-tier models of the stored binding. */\n tiers?: Partial<Record<InferenceTier, string>>;\n /** Models the virtual key may use. Default: `model` plus the tier models. */\n models?: string[];\n /** Key lifetime (`30d`, `permanent`, ...), where the gateway supports it. */\n keyDuration?: string;\n /**\n * Deadline for each admin call in ms (default\n * {@link HOSTED_GATEWAY_ADMIN_TIMEOUT_MS}). A call past it counts as\n * failed, so a stalled gateway never holds the lifecycle lock.\n */\n adminTimeoutMs?: number;\n}\n\n/** Default deadline of one gateway admin call. */\nexport const HOSTED_GATEWAY_ADMIN_TIMEOUT_MS = 30_000;\n\n/**\n * The admin calls provisioning uses. `createProject` and `createVirtualKey`\n * are `AIAdminInterface`; `updateVirtualKey` and `deleteVirtualKey` are\n * feature-detected because `@happyvertical/ai` does not ship them yet\n * (happyvertical/sdk#1494): without them budget sync and gateway revocation\n * report `unsupported`.\n */\nexport interface HostedGatewayAdmin\n extends Pick<AIAdminInterface, 'createProject' | 'createVirtualKey'> {\n updateVirtualKey?(\n id: string,\n options: {\n budget?: AIAdminBudget;\n models?: string[];\n isActive?: boolean;\n },\n ): Promise<AIVirtualKey>;\n deleteVirtualKey?(id: string): Promise<boolean>;\n}\n\n/** Environment variables {@link hostedGatewayConfigFromEnv} reads. */\nexport const HOSTED_GATEWAY_ENV = {\n provider: 'SMRT_INFERENCE_HOSTED_PROVIDER',\n baseUrl: 'SMRT_INFERENCE_HOSTED_BASE_URL',\n adminUrl: 'SMRT_INFERENCE_HOSTED_ADMIN_URL',\n adminApiKey: 'SMRT_INFERENCE_HOSTED_ADMIN_KEY',\n adminUser: 'SMRT_INFERENCE_HOSTED_ADMIN_USER',\n adminPassword: 'SMRT_INFERENCE_HOSTED_ADMIN_PASSWORD',\n model: 'SMRT_INFERENCE_HOSTED_MODEL',\n models: 'SMRT_INFERENCE_HOSTED_MODELS',\n keyDuration: 'SMRT_INFERENCE_HOSTED_KEY_DURATION',\n adminTimeoutMs: 'SMRT_INFERENCE_HOSTED_ADMIN_TIMEOUT_MS',\n} as const;\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\nfunction isPlainHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n (url.protocol === 'http:' || url.protocol === 'https:') &&\n !url.username &&\n !url.password &&\n !url.search &&\n !url.hash\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a gateway config. Throws `HOSTED_GATEWAY_NOT_CONFIGURED` naming\n * the missing or invalid fields (never their values).\n */\nexport function parseHostedGatewayConfig(\n value: Partial<HostedGatewayConfig>,\n): HostedGatewayConfig {\n const issues: string[] = [];\n const provider = text(value.provider)?.toLowerCase();\n if (\n !provider ||\n !(HOSTED_GATEWAY_PROVIDERS as readonly string[]).includes(provider)\n ) {\n issues.push(\n `provider must be one of ${HOSTED_GATEWAY_PROVIDERS.join(', ')}`,\n );\n }\n const baseUrl = text(value.baseUrl);\n if (!baseUrl || !isPlainHttpUrl(baseUrl)) {\n issues.push(\n 'baseUrl must be an http(s) URL without credentials, query or fragment',\n );\n }\n const adminUrl = text(value.adminUrl);\n if (adminUrl && !isPlainHttpUrl(adminUrl)) {\n issues.push(\n 'adminUrl must be an http(s) URL without credentials, query or fragment',\n );\n }\n const adminApiKey = text(value.adminApiKey);\n const adminUser = text(value.adminUser);\n const adminPassword = text(value.adminPassword);\n if (provider === 'litellm' && !adminApiKey) {\n issues.push('adminApiKey (the LiteLLM master key) is required for litellm');\n }\n if (provider === 'bifrost' && !adminApiKey && !(adminUser && adminPassword)) {\n issues.push(\n 'bifrost needs adminApiKey or both adminUser and adminPassword',\n );\n }\n const tiers: Partial<Record<InferenceTier, string>> = {};\n for (const tier of INFERENCE_TIERS) {\n const model = text(value.tiers?.[tier]);\n if (model) tiers[tier] = model;\n }\n const adminTimeoutMs = value.adminTimeoutMs;\n if (\n adminTimeoutMs !== undefined &&\n (!Number.isSafeInteger(adminTimeoutMs) || adminTimeoutMs <= 0)\n ) {\n issues.push('adminTimeoutMs must be a positive integer (ms)');\n }\n const model = text(value.model);\n const models = (value.models ?? [])\n .map((entry) => text(entry))\n .filter((entry): entry is string => !!entry);\n if (issues.length > 0) {\n throw new HostedInferenceError(\n `Hosted inference gateway is not configured: ${issues.join('; ')}`,\n 'HOSTED_GATEWAY_NOT_CONFIGURED',\n { issues },\n );\n }\n return {\n provider: provider as HostedGatewayProvider,\n baseUrl: baseUrl as string,\n ...(adminUrl ? { adminUrl } : {}),\n ...(adminApiKey ? { adminApiKey } : {}),\n ...(adminUser ? { adminUser } : {}),\n ...(adminPassword ? { adminPassword } : {}),\n ...(model ? { model } : {}),\n ...(Object.keys(tiers).length > 0 ? { tiers } : {}),\n ...(models.length > 0 ? { models } : {}),\n ...(text(value.keyDuration)\n ? { keyDuration: text(value.keyDuration) }\n : {}),\n ...(adminTimeoutMs !== undefined ? { adminTimeoutMs } : {}),\n };\n}\n\n/**\n * Build the gateway config from environment variables\n * ({@link HOSTED_GATEWAY_ENV}, plus `SMRT_INFERENCE_HOSTED_<TIER>_MODEL`).\n * `undefined` when `SMRT_INFERENCE_HOSTED_PROVIDER` is unset (hosted\n * inference off); throws when it is set but incomplete.\n */\nexport function hostedGatewayConfigFromEnv(\n env: Record<string, string | undefined> = process.env,\n): HostedGatewayConfig | undefined {\n if (!text(env[HOSTED_GATEWAY_ENV.provider])) return undefined;\n const tiers: Partial<Record<InferenceTier, string>> = {};\n for (const tier of INFERENCE_TIERS) {\n const model = text(\n env[`SMRT_INFERENCE_HOSTED_${tier.toUpperCase()}_MODEL`],\n );\n if (model) tiers[tier] = model;\n }\n return parseHostedGatewayConfig({\n provider: env[HOSTED_GATEWAY_ENV.provider] as HostedGatewayProvider,\n baseUrl: env[HOSTED_GATEWAY_ENV.baseUrl],\n adminUrl: env[HOSTED_GATEWAY_ENV.adminUrl],\n adminApiKey: env[HOSTED_GATEWAY_ENV.adminApiKey],\n adminUser: env[HOSTED_GATEWAY_ENV.adminUser],\n adminPassword: env[HOSTED_GATEWAY_ENV.adminPassword],\n model: env[HOSTED_GATEWAY_ENV.model],\n tiers,\n models: (env[HOSTED_GATEWAY_ENV.models] ?? '').split(','),\n keyDuration: env[HOSTED_GATEWAY_ENV.keyDuration],\n ...(text(env[HOSTED_GATEWAY_ENV.adminTimeoutMs])\n ? {\n adminTimeoutMs: Number(text(env[HOSTED_GATEWAY_ENV.adminTimeoutMs])),\n }\n : {}),\n });\n}\n\nlet envConfig: { value: HostedGatewayConfig | undefined } | undefined;\n\n/**\n * The process gateway config, read from the environment once and cached.\n * Throws `HOSTED_GATEWAY_NOT_CONFIGURED` when hosted inference is not set up.\n */\nexport function getHostedGatewayConfig(): HostedGatewayConfig {\n envConfig ??= { value: hostedGatewayConfigFromEnv() };\n if (!envConfig.value) {\n throw new HostedInferenceError(\n `Hosted inference gateway is not configured: set ${HOSTED_GATEWAY_ENV.provider} (bifrost or litellm), ${HOSTED_GATEWAY_ENV.baseUrl} and the admin credentials.`,\n 'HOSTED_GATEWAY_NOT_CONFIGURED',\n );\n }\n return envConfig.value;\n}\n\n/** Forget the cached environment config (tests). */\nexport function resetHostedGatewayConfig(): void {\n envConfig = undefined;\n}\n\n/**\n * The `ai.admin` surface of the configured gateway provider, built through\n * `getAI()` of `@happyvertical/ai`. Throws `HOSTED_GATEWAY_UNSUPPORTED` when\n * the installed SDK's provider exposes no admin surface.\n */\nexport async function createHostedGatewayAdmin(\n config: HostedGatewayConfig,\n): Promise<HostedGatewayAdmin> {\n const { getAI } = await import('@happyvertical/ai');\n const common = {\n baseUrl: config.baseUrl,\n // The client is only used for admin calls; the inference key is\n // per-user and stored separately.\n apiKey: config.adminApiKey ?? 'smrt-admin',\n timeout: config.adminTimeoutMs ?? HOSTED_GATEWAY_ADMIN_TIMEOUT_MS,\n ...(config.adminUrl ? { adminUrl: config.adminUrl } : {}),\n ...(config.adminApiKey ? { adminApiKey: config.adminApiKey } : {}),\n };\n const ai =\n config.provider === 'bifrost'\n ? await getAI({\n type: 'bifrost',\n ...common,\n ...(config.adminUser ? { adminUser: config.adminUser } : {}),\n ...(config.adminPassword\n ? { adminPassword: config.adminPassword }\n : {}),\n })\n : await getAI({ type: 'litellm', ...common });\n const admin = ai.admin as HostedGatewayAdmin | undefined;\n if (\n !admin ||\n typeof admin.createProject !== 'function' ||\n typeof admin.createVirtualKey !== 'function'\n ) {\n throw new HostedInferenceError(\n `The ${config.provider} provider of @happyvertical/ai exposes no admin surface.`,\n 'HOSTED_GATEWAY_UNSUPPORTED',\n { provider: config.provider },\n );\n }\n return admin;\n}\n\n// ---------------------------------------------------------------------------\n// Stored state\n// ---------------------------------------------------------------------------\n\n/** A gateway key SMRT no longer uses but could not delete yet. */\nexport interface HostedPendingGatewayRevocation {\n keyId: string | null;\n keyAlias: string;\n /** Why it is pending: the SDK has no delete call, or the call failed. */\n reason: 'unsupported' | 'failed' | 'orphaned';\n since: string;\n}\n\n/** A user's provisioning state (`inference.hosted:user:<userId>`). */\nexport interface HostedInferenceState {\n version: 1;\n status: 'active' | 'revoked';\n /** Lifecycle generation; advances on every revoke. */\n generation: number;\n provider?: HostedGatewayProvider;\n projectId?: string;\n /** The gateway's non-secret key id (update/delete handle), when returned. */\n keyId: string | null;\n /** Empty when no key was ever stored (only pending cleanups). */\n keyAlias: string;\n budget?: Required<HostedInferenceBudget>;\n provisionedAt?: string;\n revokedAt?: string;\n /** The user's provider config before provisioning; dropped on revoke. */\n prior: InferenceProviderConfig | null;\n /**\n * Keys SMRT stopped using whose gateway deletion is still owed (no SDK\n * delete call, a failed call, or a key left by a failed provision).\n * Retried by `revoke()` and `reconcileTenant()`; never holds a key value.\n */\n pendingGatewayRevocations?: HostedPendingGatewayRevocation[];\n /**\n * The member replaced the hosted binding with their own config: reconcile\n * leaves them alone; only an explicit `provision()` re-provisions.\n */\n optedOut?: boolean;\n /**\n * Set when a budget update failed or timed out (it may still land on the\n * gateway); budget syncs re-apply the stored budget while it is set.\n */\n budgetSyncUncertainAt?: string;\n}\n\n/**\n * How long after an uncertain budget update a confirmed one must come\n * before the uncertainty is cleared (a late landing is overwritten by the\n * re-applies in between).\n */\nexport const HOSTED_BUDGET_UNCERTAIN_WINDOW_MS = 15 * 60_000;\n\n/** What provisioning and revocation return: never the key. */\nexport interface HostedInferenceDescriptor {\n tenantId: string;\n userId: string;\n status: 'active' | 'revoked' | 'none';\n generation: number;\n provider?: HostedGatewayProvider;\n projectId?: string;\n keyId?: string | null;\n keyAlias?: string;\n budget?: Required<HostedInferenceBudget>;\n /** Non-secret handles of keys whose gateway deletion is still owed. */\n pendingGatewayRevocations?: HostedPendingGatewayRevocation[];\n}\n\n/** Result of {@link HostedInferenceProvisioner.provision}. */\nexport interface ProvisionHostedInferenceResult\n extends HostedInferenceDescriptor {\n /** True when this call created the key; false for an existing grant. */\n created: boolean;\n /** Budget sync outcome when an existing grant's budget differed. */\n budgetSync?: HostedBudgetSyncStatus;\n}\n\n/** How a gateway key revocation went. */\nexport type HostedGatewayRevokeStatus =\n | 'revoked'\n | 'missing'\n | 'unsupported'\n | 'failed'\n | 'none';\n\n/** Result of {@link HostedInferenceProvisioner.revoke}. */\nexport interface RevokeHostedInferenceResult extends HostedInferenceDescriptor {\n revoked: boolean;\n /**\n * `revoked`: the gateway deleted the key; `missing`: it no longer existed;\n * `unsupported`: the SDK has no delete call (happyvertical/sdk#1494) or the\n * gateway returned no key id; `failed`: the delete call failed. For\n * `unsupported` and `failed` SMRT has already stopped using the key and\n * records it in `pendingGatewayRevocations` (and the audit log) for a\n * later retry or a manual revocation on the gateway; `none`: nothing to\n * revoke.\n */\n gateway: HostedGatewayRevokeStatus;\n /** What the user's provider config became. */\n restored?: 'prior' | 'off' | 'kept';\n}\n\n/** Budget sync outcome. */\nexport type HostedBudgetSyncStatus = 'updated' | 'unchanged' | 'unsupported';\n\n/** Result of {@link HostedInferenceProvisioner.syncBudget}. */\nexport interface SyncHostedBudgetResult extends HostedInferenceDescriptor {\n sync: HostedBudgetSyncStatus | 'none';\n}\n\n/** What {@link HostedInferenceProvisioner.reconcileTenant} should converge to. */\nexport interface HostedReconcileTarget {\n /** Whether the tenant's plan grants `inference.hosted` now. */\n entitled: boolean;\n /** The plan's budget (required to provision; syncs active grants). */\n budget?: HostedInferenceBudget;\n /** Users to hold an active grant while entitled (idempotent). */\n users?: readonly string[];\n}\n\n/** Per-user outcome of a reconcile. */\nexport interface HostedReconcileResult {\n tenantId: string;\n provisioned: ProvisionHostedInferenceResult[];\n revoked: RevokeHostedInferenceResult[];\n synced: SyncHostedBudgetResult[];\n /** Pending gateway deletions retried, and what is still pending. */\n pending: Array<{ userId: string; remaining: number }>;\n failures: Array<{ userId: string; error: unknown }>;\n}\n\n/** The secret store calls provisioning uses (`SecretService` satisfies it). */\nexport interface HostedSecretStore {\n retrieve(name: string): Promise<{ value: string }>;\n store(\n name: string,\n value: string,\n options?: {\n category?: string;\n description?: string;\n auditDetails?: Record<string, unknown>;\n },\n ): Promise<unknown>;\n delete(\n name: string,\n options?: { auditDetails?: Record<string, unknown> },\n ): Promise<boolean>;\n list(options?: { category?: string }): Promise<Array<{ name: string }>>;\n}\n\n/** Options for {@link createHostedInferenceProvisioner}. */\nexport interface HostedInferenceProvisionerOptions {\n /** Database for `runOnce()` claims, the lifecycle lock and, without `secrets`, the secrets. */\n db: DatabaseInterface;\n /** Secret store; default `SecretService.create({ db })`. */\n secrets?: HostedSecretStore;\n /** Gateway config; default {@link getHostedGatewayConfig} (environment). */\n gateway?: HostedGatewayConfig;\n /** Admin client; default {@link createHostedGatewayAdmin}. Tests inject a fake. */\n admin?: HostedGatewayAdmin;\n /** Who acts, recorded as the audit user (default `system`). */\n actor?: string;\n /** Clock seam. */\n now?: () => Date;\n /** Alias/project nonce seam (tests). Default: random. */\n nonce?: () => string;\n /**\n * Current entitlement check, run inside the tenant lock before a new key\n * is minted (e.g. the host's `checkFeature('inference.hosted')`). With it,\n * a provision that arrives after a downgrade (a stale event, a late\n * retry) refuses with `HOSTED_NOT_ENTITLED` instead of activating.\n */\n entitled?: (tenantId: string) => Promise<boolean>;\n}\n\n/** Hosted provisioning bound to one database, store and gateway. */\nexport interface HostedInferenceProvisioner {\n provision(options: {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n }): Promise<ProvisionHostedInferenceResult>;\n /**\n * Revoke the user's active grant; with none active, retry any pending\n * gateway deletions. Idempotent.\n */\n revoke(options: {\n tenantId: string;\n userId: string;\n reason?: string;\n }): Promise<RevokeHostedInferenceResult>;\n syncBudget(options: {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n }): Promise<SyncHostedBudgetResult>;\n status(options: {\n tenantId: string;\n userId: string;\n }): Promise<HostedInferenceDescriptor>;\n /** Users of the tenant with an active hosted grant. */\n listActive(tenantId: string): Promise<string[]>;\n /** Users of the tenant with any hosted state (active, revoked, pending). */\n listKnown(tenantId: string): Promise<string[]>;\n revokeTenant(\n tenantId: string,\n options?: { reason?: string },\n ): Promise<RevokeHostedInferenceResult[]>;\n syncTenantBudget(\n tenantId: string,\n budget: HostedInferenceBudget,\n ): Promise<SyncHostedBudgetResult[]>;\n /**\n * Converge the tenant to `target` (idempotent): not entitled revokes every\n * active grant; entitled provisions `users` and syncs active budgets; both\n * retry pending gateway deletions. Run it on webhook redelivery or on a\n * schedule so a lost entitlement reaction is repaired.\n */\n reconcileTenant(\n tenantId: string,\n target: HostedReconcileTarget,\n ): Promise<HostedReconcileResult>;\n /**\n * Drop an owed gateway deletion after an operator revoked that key on the\n * gateway by hand (audited). Returns whether an entry was removed.\n */\n acknowledgeGatewayRevocation(options: {\n tenantId: string;\n userId: string;\n keyAlias: string;\n }): Promise<boolean>;\n}\n\nconst STATE_PREFIX = `${HOSTED_INFERENCE_SECRET_CATEGORY}:user:`;\nconst PROJECT_NAME = `${HOSTED_INFERENCE_SECRET_CATEGORY}:project`;\nconst RUN_ONCE_ACTOR_PROJECT = 'smrt-inference-hosted';\n\n/** Secret name of a user's hosted state. */\nexport function hostedInferenceStateSecretName(userId: string): string {\n return `${STATE_PREFIX}${userId}`;\n}\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,199}$/;\n\nfunction requireId(value: unknown, field: string): string {\n const id = text(value);\n if (!id || !ID_PATTERN.test(id)) {\n throw new HostedInferenceError(\n `${field} must be 1-200 characters of letters, digits and . _ : @ -`,\n 'HOSTED_INFERENCE_INVALID',\n { field },\n );\n }\n return id;\n}\n\n/** Validate a budget; fills the default window. */\nexport function normalizeHostedBudget(\n budget: HostedInferenceBudget | undefined,\n): Required<HostedInferenceBudget> {\n const limit = budget?.limitMicros;\n if (typeof limit !== 'number' || !Number.isSafeInteger(limit) || limit <= 0) {\n throw new HostedInferenceError(\n 'budget.limitMicros must be a positive integer (micro-dollars)',\n 'HOSTED_INFERENCE_INVALID',\n { field: 'budget.limitMicros' },\n );\n }\n const window = budget?.window ?? 'month';\n if (!(HOSTED_BUDGET_WINDOWS as readonly string[]).includes(window)) {\n throw new HostedInferenceError(\n `budget.window must be one of ${HOSTED_BUDGET_WINDOWS.join(', ')}`,\n 'HOSTED_INFERENCE_INVALID',\n { field: 'budget.window' },\n );\n }\n return { limitMicros: limit, window };\n}\n\n/** The gateway's budget shape for a hosted budget (USD at the boundary). */\nexport function toGatewayBudget(\n budget: Required<HostedInferenceBudget>,\n): AIAdminBudget {\n return {\n maxLimit: budget.limitMicros / 1_000_000,\n resetDuration: WINDOW_DURATION[budget.window],\n };\n}\n\nfunction sameBudget(\n a: Required<HostedInferenceBudget> | undefined,\n b: Required<HostedInferenceBudget>,\n): boolean {\n return !!a && a.limitMicros === b.limitMicros && a.window === b.window;\n}\n\nfunction parseState(value: string): HostedInferenceState {\n let raw: unknown;\n try {\n raw = JSON.parse(value);\n } catch {\n throw new HostedInferenceError(\n 'Stored hosted inference state is not JSON',\n 'HOSTED_STATE_INVALID',\n );\n }\n const state = raw as Partial<HostedInferenceState>;\n if (\n !state ||\n typeof state !== 'object' ||\n state.version !== 1 ||\n (state.status !== 'active' && state.status !== 'revoked') ||\n typeof state.generation !== 'number' ||\n !Number.isSafeInteger(state.generation) ||\n typeof state.keyAlias !== 'string' ||\n (state.status === 'active' &&\n (typeof state.projectId !== 'string' || !state.budget))\n ) {\n throw new HostedInferenceError(\n 'Stored hosted inference state is invalid',\n 'HOSTED_STATE_INVALID',\n );\n }\n return {\n ...(state as HostedInferenceState),\n prior: state.prior ? parseInferenceProviderConfig(state.prior) : null,\n };\n}\n\nfunction isAbsentSecretError(error: unknown, name: string): boolean {\n if (!(error instanceof Error)) return false;\n return (\n error.message === `Secret '${name}' not found` ||\n error.message === 'Secret not found' ||\n error.message === 'Secret disabled' ||\n error.message === 'Secret expired'\n );\n}\n\nfunction describeState(\n tenantId: string,\n userId: string,\n state: HostedInferenceState | undefined,\n): HostedInferenceDescriptor {\n if (!state) return { tenantId, userId, status: 'none', generation: 0 };\n return {\n tenantId,\n userId,\n status: state.status,\n generation: state.generation,\n ...(state.provider ? { provider: state.provider } : {}),\n ...(state.projectId ? { projectId: state.projectId } : {}),\n keyId: state.keyId,\n ...(state.keyAlias ? { keyAlias: state.keyAlias } : {}),\n ...(state.budget ? { budget: state.budget } : {}),\n ...(state.pendingGatewayRevocations?.length\n ? { pendingGatewayRevocations: state.pendingGatewayRevocations }\n : {}),\n };\n}\n\n/** Error code of an admin call that passed its deadline. */\nconst GATEWAY_TIMEOUT_CODE = 'HOSTED_GATEWAY_TIMEOUT';\n\n/**\n * `call()` bounded by `ms`: past it the result rejects with a timeout error\n * (the call itself may still finish on the gateway; callers treat a timed\n * out create as a possibly created key).\n */\nasync function withDeadline<T>(\n call: () => Promise<T>,\n ms: number,\n operation: string,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n Object.assign(new Error(`Gateway ${operation} timed out`), {\n code: GATEWAY_TIMEOUT_CODE,\n }),\n ),\n ms,\n );\n });\n try {\n return await Promise.race([call(), deadline]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction isGatewayTimeout(error: unknown): boolean {\n return (\n !!error &&\n typeof error === 'object' &&\n (error as { code?: unknown }).code === GATEWAY_TIMEOUT_CODE\n );\n}\n\n/**\n * Whether a failed create may still have minted the key: anything but an\n * explicit refusal (credential, quota, missing model, refusal). Timeouts,\n * transport resets, 5xx and unclassified errors are ambiguous.\n */\nfunction mayHaveCreated(error: unknown): boolean {\n if (isGatewayTimeout(error)) return true;\n const failure = classifyInferenceFailure(error);\n return (\n failure !== 'credential' &&\n failure !== 'quota' &&\n failure !== 'missing-model' &&\n failure !== 'refusal'\n );\n}\n\n/** Name an admin-call failure without echoing the SDK's error text. */\nfunction gatewayFailure(\n operation: string,\n error: unknown,\n): HostedInferenceError {\n const code =\n error && typeof error === 'object' && 'code' in error\n ? String((error as { code: unknown }).code)\n : undefined;\n const name = error instanceof Error ? error.name : typeof error;\n return new HostedInferenceError(\n `Gateway ${operation} failed (${code ?? name}).`,\n 'HOSTED_GATEWAY_FAILED',\n { operation, ...(code ? { gatewayCode: code } : {}), errorName: name },\n );\n}\n\n/** The binding a hosted key stores: default plus per-tier models. */\nfunction hostedProviderConfig(\n gateway: HostedGatewayConfig,\n apiKey: string,\n keyAlias: string,\n): InferenceProviderConfig {\n const binding = (model: string | undefined): InferenceProviderBinding => ({\n provider: gateway.provider,\n baseUrl: gateway.baseUrl,\n apiKey,\n ...(model ? { model } : {}),\n });\n const tiers: Partial<Record<InferenceTier, InferenceProviderBinding>> = {};\n for (const [tier, model] of Object.entries(gateway.tiers ?? {})) {\n if (model) tiers[tier as InferenceTier] = binding(model);\n }\n return parseInferenceProviderConfig({\n version: 1,\n source: 'hosted',\n mode: 'hosted',\n gatewayKeyAlias: keyAlias,\n ...binding(gateway.model),\n ...(Object.keys(tiers).length > 0 ? { tiers } : {}),\n });\n}\n\n/** Models the key may use: explicit list, else the binding's models. */\nfunction keyModels(gateway: HostedGatewayConfig): string[] | undefined {\n if (gateway.models?.length) return gateway.models;\n const models = new Set<string>();\n if (gateway.model) models.add(gateway.model);\n for (const model of Object.values(gateway.tiers ?? {})) {\n if (model) models.add(model);\n }\n return models.size > 0 ? [...models] : undefined;\n}\n\n/** The config to restore on revoke: the prior one, or mode `off`. */\nfunction restoredConfig(prior: InferenceProviderConfig | null): {\n config: InferenceProviderConfig;\n restored: 'prior' | 'off';\n} {\n if (prior && prior.mode !== 'hosted' && prior.source !== 'hosted') {\n const hasBinding = INFERENCE_TIERS.some(\n (tier) => !!inferenceProviderBindingFor(prior, tier),\n );\n if (hasBinding || prior.mode) {\n return { config: prior, restored: 'prior' };\n }\n }\n return { config: { mode: 'off' }, restored: 'off' };\n}\n\nfunction defaultNonce(): string {\n return globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 10);\n}\n\n/**\n * Process-wide chain of hosted lifecycle operations. One at a time per\n * process: they are rare (webhooks, operators) and this bounds the pooled\n * connections an operation holds (the PostgreSQL lock transaction plus its\n * own writes) whatever the number of tenants changing at once.\n */\nlet lifecycleChain: Promise<unknown> = Promise.resolve();\n\n/**\n * Create a provisioner. One per process (or per request) is fine: lifecycle\n * operations run one at a time per process and, on PostgreSQL, hold a\n * per-tenant transaction-scoped advisory lock (cross-process), so a\n * tenant-wide revoke never misses an in-flight provision.\n */\nexport function createHostedInferenceProvisioner(\n options: HostedInferenceProvisionerOptions,\n): HostedInferenceProvisioner {\n const now = options.now ?? (() => new Date());\n const nonce = options.nonce ?? defaultNonce;\n const deadlineMs = (): number => {\n try {\n return gatewayConfig().adminTimeoutMs ?? HOSTED_GATEWAY_ADMIN_TIMEOUT_MS;\n } catch {\n return HOSTED_GATEWAY_ADMIN_TIMEOUT_MS;\n }\n };\n let secretsPromise: Promise<HostedSecretStore> | undefined;\n let adminPromise: Promise<HostedGatewayAdmin> | undefined;\n let tablesPromise: Promise<void> | undefined;\n\n const gatewayConfig = (): HostedGatewayConfig =>\n options.gateway\n ? parseHostedGatewayConfig(options.gateway)\n : getHostedGatewayConfig();\n\n const getSecrets = (): Promise<HostedSecretStore> => {\n if (options.secrets) return Promise.resolve(options.secrets);\n secretsPromise ??= SecretService.create({ db: options.db }).catch(\n (error: unknown) => {\n secretsPromise = undefined;\n throw error;\n },\n );\n return secretsPromise;\n };\n\n const getAdmin = (gateway: HostedGatewayConfig) => {\n if (options.admin) return Promise.resolve(options.admin);\n adminPromise ??= createHostedGatewayAdmin(gateway).catch(\n (error: unknown) => {\n adminPromise = undefined;\n throw error;\n },\n );\n return adminPromise;\n };\n\n /** The admin surface, or `undefined` when none is usable (revoke path). */\n const optionalAdmin = async (): Promise<HostedGatewayAdmin | undefined> => {\n try {\n return await getAdmin(gatewayConfig());\n } catch (error) {\n if (\n error instanceof HostedInferenceError &&\n (error.code === 'HOSTED_GATEWAY_NOT_CONFIGURED' ||\n error.code === 'HOSTED_GATEWAY_UNSUPPORTED')\n ) {\n return undefined;\n }\n throw error;\n }\n };\n\n const ensureTables = () => {\n tablesPromise ??= ensureSystemTables(options.db)\n .then(() => undefined)\n .catch((error: unknown) => {\n tablesPromise = undefined;\n throw error;\n });\n return tablesPromise;\n };\n\n const inTenant = <T>(tenantId: string, fn: () => Promise<T>) =>\n withTenant(\n { tenantId, ...(options.actor ? { userId: options.actor } : {}) },\n fn,\n );\n\n /**\n * Run one lifecycle operation for a tenant: after every earlier operation\n * of this process (the chain), and on PostgreSQL under a per-tenant\n * transaction-scoped advisory lock held for the whole operation, so\n * operations of other processes on the same tenant wait (the operation's\n * own writes use other pooled connections: the pool needs at least three).\n * Single-connection adapters (SQLite) serve one process, which the chain\n * covers. Callers inside a locked operation use the `*Locked` helpers,\n * never these public methods (the chain is not re-entrant).\n */\n const withTenantLock = <T>(\n tenantId: string,\n fn: () => Promise<T>,\n ): Promise<T> => {\n const key = JSON.stringify(['smrt.inference.hosted', tenantId]);\n const run = async () => {\n if (isPostgresDatabase(options.db) && options.db.transaction) {\n return options.db.transaction(async (tx) => {\n await tx.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))',\n key,\n );\n return fn();\n });\n }\n return fn();\n };\n const result = lifecycleChain.then(run, run);\n lifecycleChain = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n const read = async (\n secrets: HostedSecretStore,\n name: string,\n ): Promise<string | undefined> => {\n try {\n return (await secrets.retrieve(name)).value;\n } catch (error) {\n if (isAbsentSecretError(error, name)) return undefined;\n throw error;\n }\n };\n\n const readState = async (secrets: HostedSecretStore, userId: string) => {\n const value = await read(secrets, hostedInferenceStateSecretName(userId));\n return value === undefined ? undefined : parseState(value);\n };\n\n const readUserConfig = async (\n secrets: HostedSecretStore,\n userId: string,\n ): Promise<InferenceProviderConfig | undefined> => {\n const value = await read(\n secrets,\n inferenceProviderConfigSecretName({ userId }),\n );\n return value === undefined\n ? undefined\n : parseInferenceProviderConfig(value);\n };\n\n const writeState = (\n secrets: HostedSecretStore,\n userId: string,\n state: HostedInferenceState,\n auditDetails: Record<string, unknown>,\n ) =>\n secrets.store(\n hostedInferenceStateSecretName(userId),\n JSON.stringify(state),\n {\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n description: 'Hosted inference provisioning state',\n auditDetails,\n },\n );\n\n const writeUserConfig = (\n secrets: HostedSecretStore,\n userId: string,\n config: InferenceProviderConfig,\n auditDetails: Record<string, unknown>,\n ) =>\n secrets.store(\n inferenceProviderConfigSecretName({ userId }),\n JSON.stringify(parseInferenceProviderConfig(config)),\n {\n category: INFERENCE_PROVIDER_CONFIG_SECRET_TYPE,\n description: 'Inference provider config (user)',\n auditDetails,\n },\n );\n\n /** Whether the user's current config is this grant's hosted binding. */\n const isOurs = (\n config: InferenceProviderConfig | undefined,\n keyAlias: string,\n ) =>\n !!keyAlias &&\n config?.source === 'hosted' &&\n config.gatewayKeyAlias === keyAlias;\n\n /**\n * Try to delete a key on the gateway. Never throws: the outcome says\n * whether SMRT still owes the deletion.\n */\n const deleteGatewayKey = async (\n admin: HostedGatewayAdmin | undefined,\n keyId: string | null,\n ): Promise<'revoked' | 'missing' | 'unsupported' | 'failed'> => {\n if (!keyId || typeof admin?.deleteVirtualKey !== 'function') {\n return 'unsupported';\n }\n try {\n const deleteKey = admin.deleteVirtualKey.bind(admin);\n return (await withDeadline(\n () => deleteKey(keyId),\n deadlineMs(),\n 'deleteVirtualKey',\n ))\n ? 'revoked'\n : 'missing';\n } catch {\n return 'failed';\n }\n };\n\n /** The tenant's gateway project, created once (runOnce) on first use. */\n const ensureProject = async (\n tenantId: string,\n gateway: HostedGatewayConfig,\n admin: HostedGatewayAdmin,\n secrets: HostedSecretStore,\n ): Promise<string> => {\n const stored = await read(secrets, PROJECT_NAME);\n if (stored !== undefined) {\n try {\n const parsed = JSON.parse(stored) as {\n projectId?: unknown;\n provider?: unknown;\n };\n if (\n typeof parsed.projectId === 'string' &&\n parsed.provider === gateway.provider\n ) {\n return parsed.projectId;\n }\n } catch {\n // Unreadable project record: create (or replay) below.\n }\n }\n const result = await runOnce(\n {\n db: options.db,\n tenantId,\n actor: RUN_ONCE_ACTOR_PROJECT,\n token: 'smrt-inference-hosted:project',\n content: { provider: gateway.provider, baseUrl: gateway.baseUrl },\n },\n async () => {\n let project: AIAdminProject;\n try {\n project = await withDeadline(\n () =>\n admin.createProject({\n // LiteLLM requires an id, unique per attempt so a retry after a\n // failed attempt never collides with the team that attempt left.\n id: `smrt-${tenantId}-${nonce()}`,\n name: `smrt tenant ${tenantId}`,\n tenantId,\n metadata: { smrt: HOSTED_INFERENCE_FEATURE_KEY, tenantId },\n }),\n deadlineMs(),\n 'createProject',\n );\n } catch (error) {\n throw gatewayFailure('createProject', error);\n }\n const projectId = text(project?.id);\n if (!projectId) {\n throw new HostedInferenceError(\n 'Gateway createProject returned no project id.',\n 'HOSTED_GATEWAY_FAILED',\n { operation: 'createProject' },\n );\n }\n await secrets.store(\n PROJECT_NAME,\n JSON.stringify({\n projectId,\n provider: gateway.provider,\n createdAt: now().toISOString(),\n }),\n {\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n description: 'Hosted inference gateway project',\n auditDetails: {\n operation: 'inference.hosted.project',\n projectId,\n provider: gateway.provider,\n },\n },\n );\n return { projectId };\n },\n );\n return result.projectId;\n };\n\n /** Retry owed gateway deletions; returns the state to store (or same). */\n const retryPending = async (\n state: HostedInferenceState,\n ): Promise<HostedInferenceState> => {\n const pending = state.pendingGatewayRevocations ?? [];\n if (pending.length === 0) return state;\n const admin = await optionalAdmin();\n const remaining: HostedPendingGatewayRevocation[] = [];\n for (const entry of pending) {\n const outcome = await deleteGatewayKey(admin, entry.keyId);\n if (outcome === 'unsupported' || outcome === 'failed') {\n remaining.push(entry);\n }\n }\n if (remaining.length === pending.length) return state;\n return { ...state, pendingGatewayRevocations: remaining };\n };\n\n /**\n * Revoke an active grant (caller holds the lifecycle lock). Local access\n * ends whatever the gateway answers: the config is restored and the state\n * retired first-class; a deletion the gateway could not perform is kept in\n * `pendingGatewayRevocations` and audited.\n */\n const revokeLocked = async (\n tenantId: string,\n userId: string,\n secrets: HostedSecretStore,\n state: HostedInferenceState,\n reason: string,\n restore: boolean,\n optOut = false,\n ): Promise<RevokeHostedInferenceResult> => {\n const since = now().toISOString();\n const audit = {\n operation: 'inference.hosted.revoke',\n reason,\n keyAlias: state.keyAlias,\n ...(state.keyId ? { keyId: state.keyId } : {}),\n };\n // 1. End local use first, whatever the gateway does next.\n let restored: RevokeHostedInferenceResult['restored'] = 'kept';\n if (restore) {\n const current = await readUserConfig(secrets, userId);\n if (isOurs(current, state.keyAlias)) {\n const next = restoredConfig(state.prior);\n restored = next.restored;\n await writeUserConfig(secrets, userId, next.config, {\n ...audit,\n restored,\n });\n }\n }\n const owedEntry: HostedPendingGatewayRevocation = {\n keyId: state.keyId,\n keyAlias: state.keyAlias,\n reason: 'failed',\n since,\n };\n const retired: HostedInferenceState = {\n ...state,\n status: 'revoked',\n generation: state.generation + 1,\n revokedAt: since,\n prior: null,\n ...(optOut ? { optedOut: true } : {}),\n // Owed until the gateway confirms the deletion below.\n pendingGatewayRevocations: [\n ...(state.pendingGatewayRevocations ?? []),\n owedEntry,\n ],\n };\n await writeState(secrets, userId, retired, {\n ...audit,\n restored,\n gateway: 'pending',\n });\n // 2. Then the gateway (bounded by the admin deadline).\n const gateway = await deleteGatewayKey(await optionalAdmin(), state.keyId);\n const owed = gateway === 'unsupported' || gateway === 'failed';\n const next: HostedInferenceState = {\n ...retired,\n pendingGatewayRevocations: [\n ...(state.pendingGatewayRevocations ?? []),\n ...(owed ? [{ ...owedEntry, reason: gateway }] : []),\n ],\n };\n await writeState(secrets, userId, next, {\n ...audit,\n restored,\n gateway,\n ...(owed ? { manualGatewayRevocation: true } : {}),\n });\n return {\n ...describeState(tenantId, userId, next),\n revoked: true,\n gateway,\n restored,\n };\n };\n\n const provisionLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n gateway: HostedGatewayConfig,\n secrets: HostedSecretStore,\n ): Promise<ProvisionHostedInferenceResult> => {\n let state = await readState(secrets, userId);\n if (state?.status === 'active') {\n const current = await readUserConfig(secrets, userId);\n if (isOurs(current, state.keyAlias)) {\n const sync = sameBudget(state.budget, budget)\n ? undefined\n : await syncLocked(tenantId, userId, budget, secrets);\n const fresh = (await readState(secrets, userId)) ?? state;\n return {\n ...describeState(tenantId, userId, fresh),\n created: false,\n ...(sync && sync.sync !== 'none' ? { budgetSync: sync.sync } : {}),\n };\n }\n // The user replaced the hosted binding since: retire that key\n // (keeping their new config) and provision afresh.\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'user-replaced',\n false,\n true,\n );\n state = await readState(secrets, userId);\n }\n if (options.entitled && !(await options.entitled(tenantId))) {\n throw new HostedInferenceError(\n `Tenant ${tenantId} is not entitled to ${HOSTED_INFERENCE_FEATURE_KEY}; nothing was provisioned.`,\n 'HOSTED_NOT_ENTITLED',\n { tenantId, userId },\n );\n }\n const generation = state?.generation ?? 0;\n const admin = await getAdmin(gateway);\n const projectId = await ensureProject(tenantId, gateway, admin, secrets);\n let rollback: (() => Promise<unknown>) | undefined;\n const provisioned = await runOnce(\n {\n db: options.db,\n tenantId,\n actor: userId,\n token: 'smrt-inference-hosted:provision',\n content: { userId, generation },\n },\n async () => {\n const current = await readUserConfig(secrets, userId);\n // Keep the user's own config to restore on revoke; a leftover\n // hosted binding is never a prior.\n const prior = current && current.source !== 'hosted' ? current : null;\n // Unique per attempt: a failed attempt's key (which a create-only\n // SDK cannot delete) never blocks the retry's alias.\n const keyAlias = `smrt-${tenantId}-${userId}-g${generation}-${nonce()}`;\n const models = keyModels(gateway);\n const request: CreateAIVirtualKeyOptions = {\n name: keyAlias,\n description: `smrt hosted inference for user ${userId}`,\n projectId,\n tenantId,\n userId,\n budget: toGatewayBudget(budget),\n ...(models ? { models } : {}),\n ...(gateway.keyDuration ? { duration: gateway.keyDuration } : {}),\n ...(gateway.provider === 'bifrost' ? { keyIds: ['*'] } : {}),\n metadata: {\n smrt: HOSTED_INFERENCE_FEATURE_KEY,\n tenantId,\n userId,\n generation,\n },\n };\n let key: AIVirtualKey | undefined;\n let createError: unknown;\n try {\n key = await withDeadline(\n () => admin.createVirtualKey(request),\n deadlineMs(),\n 'createVirtualKey',\n );\n } catch (error) {\n createError = error;\n }\n const keyId = text(key?.id) ?? null;\n const apiKey = text(key?.key);\n /**\n * After the claim rolled back (a delete inside its transaction would\n * wait on it on single-connection adapters): delete the new key or\n * record it as owed, and put the previous state back so the user's\n * prior config survives for the retry.\n */\n const scheduleRollback = () => {\n const previous = state;\n rollback = async () => {\n const outcome = await deleteGatewayKey(admin, keyId);\n const owed = outcome === 'unsupported' || outcome === 'failed';\n const audit = {\n operation: 'inference.hosted.provision-rollback',\n keyAlias,\n gateway: outcome,\n ...(keyId ? { keyId } : {}),\n ...(owed ? { manualGatewayRevocation: true } : {}),\n };\n const pending = [\n ...(previous?.pendingGatewayRevocations ?? []),\n ...(owed\n ? [\n {\n keyId,\n keyAlias,\n reason: 'orphaned',\n since: now().toISOString(),\n } satisfies HostedPendingGatewayRevocation,\n ]\n : []),\n ];\n if (previous) {\n await writeState(\n secrets,\n userId,\n { ...previous, pendingGatewayRevocations: pending },\n audit,\n );\n } else if (owed) {\n await writeState(\n secrets,\n userId,\n {\n version: 1,\n status: 'revoked',\n generation,\n keyId: null,\n keyAlias: '',\n prior: null,\n pendingGatewayRevocations: pending,\n },\n audit,\n );\n } else {\n await secrets.delete(hostedInferenceStateSecretName(userId), {\n auditDetails: audit,\n });\n }\n };\n };\n if (createError !== undefined) {\n // An ambiguous failure may still mint the key on the gateway:\n // record its alias as owed. An explicit refusal created nothing.\n if (mayHaveCreated(createError)) scheduleRollback();\n throw gatewayFailure('createVirtualKey', createError);\n }\n if (!apiKey) {\n scheduleRollback();\n throw new HostedInferenceError(\n 'Gateway createVirtualKey returned no key value.',\n 'HOSTED_GATEWAY_FAILED',\n { operation: 'createVirtualKey' },\n );\n }\n const next: HostedInferenceState = {\n version: 1,\n status: 'active',\n generation,\n provider: gateway.provider,\n projectId,\n keyId,\n keyAlias,\n budget,\n provisionedAt: now().toISOString(),\n prior,\n ...(state?.pendingGatewayRevocations?.length\n ? { pendingGatewayRevocations: state.pendingGatewayRevocations }\n : {}),\n };\n const audit = {\n operation: 'inference.hosted.provision',\n keyAlias,\n projectId,\n provider: gateway.provider,\n ...(keyId ? { keyId } : {}),\n };\n try {\n await writeState(secrets, userId, next, audit);\n await writeUserConfig(\n secrets,\n userId,\n hostedProviderConfig(gateway, apiKey, keyAlias),\n audit,\n );\n } catch (error) {\n scheduleRollback();\n throw error;\n }\n const created: ProvisionHostedInferenceResult = {\n ...describeState(tenantId, userId, next),\n created: true,\n };\n return created;\n },\n ).catch(async (error: unknown) => {\n if (rollback) await rollback().catch(() => undefined);\n throw error;\n });\n // A replayed claim must describe what is stored. It does not when the\n // state was removed out of band (claims outlive it for the retention\n // window): refuse rather than report a grant that does not exist.\n const stored = await readUserConfig(secrets, userId);\n if (!isOurs(stored, provisioned.keyAlias ?? '')) {\n throw new HostedInferenceError(\n 'Hosted inference state for this user was removed outside provisioning; revoke the gateway key named in the audit log and provision again after the run-once claim expires.',\n 'HOSTED_STATE_INVALID',\n { tenantId, userId, keyAlias: provisioned.keyAlias },\n );\n }\n return provisioned;\n };\n\n /**\n * Sync one grant's gateway budget (lock held). Unchanged budgets are not\n * re-sent (a gateway update may restart the spend window), except while\n * an earlier update's outcome is uncertain (`budgetSyncUncertainAt`: it\n * failed or timed out and may still land): then the stored budget is\n * re-applied, until a confirmed update at least\n * {@link HOSTED_BUDGET_UNCERTAIN_WINDOW_MS} after the uncertain one.\n */\n const syncLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n secrets: HostedSecretStore,\n ): Promise<SyncHostedBudgetResult> => {\n const state = await readState(secrets, userId);\n if (state?.status !== 'active') {\n return { ...describeState(tenantId, userId, state), sync: 'none' };\n }\n const uncertainAt = state.budgetSyncUncertainAt;\n if (!uncertainAt && sameBudget(state.budget, budget)) {\n return { ...describeState(tenantId, userId, state), sync: 'unchanged' };\n }\n const admin = await getAdmin(gatewayConfig());\n if (!state.keyId || typeof admin.updateVirtualKey !== 'function') {\n // Recorded state keeps the old budget so a later sync retries.\n return { ...describeState(tenantId, userId, state), sync: 'unsupported' };\n }\n try {\n const update = admin.updateVirtualKey.bind(admin);\n const keyId = state.keyId;\n await withDeadline(\n () => update(keyId, { budget: toGatewayBudget(budget) }),\n deadlineMs(),\n 'updateVirtualKey',\n );\n } catch (error) {\n // The update may still land later: re-apply on the next sync.\n await writeState(\n secrets,\n userId,\n { ...state, budgetSyncUncertainAt: now().toISOString() },\n {\n operation: 'inference.hosted.budget-uncertain',\n keyAlias: state.keyAlias,\n keyId: state.keyId,\n },\n );\n throw gatewayFailure('updateVirtualKey', error);\n }\n const settled =\n !uncertainAt ||\n now().getTime() - Date.parse(uncertainAt) >=\n HOSTED_BUDGET_UNCERTAIN_WINDOW_MS;\n const { budgetSyncUncertainAt: _settled, ...rest } = state;\n const next: HostedInferenceState = {\n ...(settled ? rest : state),\n budget,\n };\n if (sameBudget(state.budget, budget) && !(settled && uncertainAt)) {\n return { ...describeState(tenantId, userId, next), sync: 'updated' };\n }\n await writeState(secrets, userId, next, {\n operation: 'inference.hosted.budget',\n keyAlias: state.keyAlias,\n keyId: state.keyId,\n limitMicros: budget.limitMicros,\n window: budget.window,\n });\n return { ...describeState(tenantId, userId, next), sync: 'updated' };\n };\n\n const listStates = async (tenantId: string) => {\n const secrets = await getSecrets();\n return inTenant(tenantId, async () => {\n const rows = await secrets.list({\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n });\n const states: Array<{ userId: string; state: HostedInferenceState }> = [];\n for (const row of rows) {\n if (!row.name.startsWith(STATE_PREFIX)) continue;\n const userId = row.name.slice(STATE_PREFIX.length);\n const state = await readState(secrets, userId);\n if (state) states.push({ userId, state });\n }\n return states.sort((a, b) => a.userId.localeCompare(b.userId));\n });\n };\n\n /** Retry a user's owed gateway deletions and persist what changed. */\n const retryPendingLocked = async (\n userId: string,\n secrets: HostedSecretStore,\n ): Promise<HostedInferenceState | undefined> => {\n const state = await readState(secrets, userId);\n if (!state?.pendingGatewayRevocations?.length) return state;\n const retried = await retryPending(state);\n if (retried === state) return state;\n await writeState(secrets, userId, retried, {\n operation: 'inference.hosted.revoke-retry',\n remaining: retried.pendingGatewayRevocations?.length ?? 0,\n });\n return retried;\n };\n\n /** Revoke one user (lock held): active grant, else owed deletions only. */\n const revokeUserLocked = async (\n tenantId: string,\n userId: string,\n secrets: HostedSecretStore,\n reason: string,\n ): Promise<RevokeHostedInferenceResult> => {\n const state = await readState(secrets, userId);\n if (state?.status !== 'active') {\n const retried = await retryPendingLocked(userId, secrets);\n return {\n ...describeState(tenantId, userId, retried),\n revoked: false,\n gateway: 'none',\n };\n }\n return revokeLocked(tenantId, userId, secrets, state, reason, true);\n };\n\n /**\n * Converge one entitled user (lock held). A user who replaced the hosted\n * binding with their own config keeps it: the stale key is retired and\n * the user marked opted out, so later reconciles leave them alone.\n */\n const reconcileUserLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n gateway: HostedGatewayConfig,\n secrets: HostedSecretStore,\n result: HostedReconcileResult,\n ): Promise<void> => {\n const state = await readState(secrets, userId);\n if (state?.status === 'revoked' && state.optedOut) return;\n if (state?.status === 'active') {\n const current = await readUserConfig(secrets, userId);\n if (!isOurs(current, state.keyAlias)) {\n result.revoked.push(\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'user-replaced',\n false,\n true,\n ),\n );\n return;\n }\n result.synced.push(await syncLocked(tenantId, userId, budget, secrets));\n return;\n }\n result.provisioned.push(\n await provisionLocked(tenantId, userId, budget, gateway, secrets),\n );\n };\n\n /** Common entry: prepare, lock the tenant, set its context. */\n const locked = async <T>(\n tenantId: string,\n fn: (secrets: HostedSecretStore) => Promise<T>,\n invalidate?: { userId?: string },\n ): Promise<T> => {\n await ensureTables();\n const secrets = await getSecrets();\n try {\n return await withTenantLock(tenantId, () =>\n inTenant(tenantId, () => fn(secrets)),\n );\n } finally {\n if (invalidate) {\n invalidateUserInferenceResolvers({\n tenantId,\n ...(invalidate.userId ? { userId: invalidate.userId } : {}),\n });\n }\n }\n };\n\n const provisioner: HostedInferenceProvisioner = {\n async provision(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const budget = normalizeHostedBudget(input.budget);\n const gateway = gatewayConfig();\n return locked(\n tenantId,\n (secrets) =>\n provisionLocked(tenantId, userId, budget, gateway, secrets),\n { userId },\n );\n },\n\n async revoke(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n return locked(\n tenantId,\n (secrets) =>\n revokeUserLocked(\n tenantId,\n userId,\n secrets,\n text(input.reason) ?? 'revoke',\n ),\n { userId },\n );\n },\n\n async syncBudget(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const budget = normalizeHostedBudget(input.budget);\n return locked(tenantId, (secrets) =>\n syncLocked(tenantId, userId, budget, secrets),\n );\n },\n\n async status(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const secrets = await getSecrets();\n return inTenant(tenantId, async () =>\n describeState(tenantId, userId, await readState(secrets, userId)),\n );\n },\n\n async listActive(tenantIdInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return (await listStates(tenantId))\n .filter(({ state }) => state.status === 'active')\n .map(({ userId }) => userId);\n },\n\n async listKnown(tenantIdInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return (await listStates(tenantId)).map(({ userId }) => userId);\n },\n\n async revokeTenant(tenantIdInput, revokeOptions = {}) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return locked(\n tenantId,\n async (secrets) => {\n const results: RevokeHostedInferenceResult[] = [];\n for (const { userId, state } of await listStates(tenantId)) {\n if (state.status !== 'active') continue;\n results.push(\n await revokeUserLocked(\n tenantId,\n userId,\n secrets,\n text(revokeOptions.reason) ?? 'revoke',\n ),\n );\n }\n return results;\n },\n {},\n );\n },\n\n async syncTenantBudget(tenantIdInput, budgetInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n const budget = normalizeHostedBudget(budgetInput);\n return locked(tenantId, async (secrets) => {\n const results: SyncHostedBudgetResult[] = [];\n for (const { userId, state } of await listStates(tenantId)) {\n if (state.status !== 'active') continue;\n results.push(await syncLocked(tenantId, userId, budget, secrets));\n }\n return results;\n });\n },\n\n async acknowledgeGatewayRevocation(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const keyAlias = text(input.keyAlias);\n if (!keyAlias) {\n throw new HostedInferenceError(\n 'keyAlias is required',\n 'HOSTED_INFERENCE_INVALID',\n { field: 'keyAlias' },\n );\n }\n return locked(tenantId, async (secrets) => {\n const state = await readState(secrets, userId);\n const pending = state?.pendingGatewayRevocations ?? [];\n const remaining = pending.filter(\n (entry) => entry.keyAlias !== keyAlias,\n );\n if (!state || remaining.length === pending.length) return false;\n await writeState(\n secrets,\n userId,\n { ...state, pendingGatewayRevocations: remaining },\n {\n operation: 'inference.hosted.revoke-acknowledged',\n keyAlias,\n },\n );\n return true;\n });\n },\n\n async reconcileTenant(tenantIdInput, target) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n const budget = target.budget\n ? normalizeHostedBudget(target.budget)\n : undefined;\n const users = (target.users ?? []).map((userId) =>\n requireId(userId, 'userId'),\n );\n if (target.entitled && users.length > 0 && !budget) {\n throw new HostedInferenceError(\n 'reconcileTenant needs a budget to provision users.',\n 'HOSTED_INFERENCE_INVALID',\n { tenantId },\n );\n }\n const gateway =\n target.entitled && users.length > 0 ? gatewayConfig() : undefined;\n return locked(\n tenantId,\n async (secrets) => {\n const result: HostedReconcileResult = {\n tenantId,\n provisioned: [],\n revoked: [],\n synced: [],\n pending: [],\n failures: [],\n };\n const attempt = async (userId: string, fn: () => Promise<void>) => {\n try {\n await fn();\n } catch (error) {\n result.failures.push({ userId, error });\n }\n };\n // Enumerated under the tenant lock: no provision is in flight.\n const states = await listStates(tenantId);\n // A stale \"entitled\" target (an old snapshot processed after a\n // downgrade) loses to the host's current answer, read under the\n // lock.\n const entitled =\n target.entitled &&\n (!options.entitled || (await options.entitled(tenantId)));\n if (!entitled) {\n for (const { userId, state } of states) {\n if (state.status !== 'active') continue;\n await attempt(userId, async () => {\n result.revoked.push(\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'entitlement-lost',\n true,\n ),\n );\n });\n }\n } else {\n const wanted = new Set(users);\n for (const userId of wanted) {\n await attempt(userId, () =>\n reconcileUserLocked(\n tenantId,\n userId,\n budget as Required<HostedInferenceBudget>,\n gateway as HostedGatewayConfig,\n secrets,\n result,\n ),\n );\n }\n if (budget) {\n for (const { userId, state } of states) {\n if (wanted.has(userId) || state.status !== 'active') continue;\n await attempt(userId, async () => {\n result.synced.push(\n await syncLocked(tenantId, userId, budget, secrets),\n );\n });\n }\n }\n }\n // Owed gateway deletions for every user with state (an active\n // grant's pending entries are old keys, never the current one).\n for (const { userId } of await listStates(tenantId)) {\n await attempt(userId, async () => {\n const state = await retryPendingLocked(userId, secrets);\n const remaining = state?.pendingGatewayRevocations?.length ?? 0;\n if (remaining > 0) result.pending.push({ userId, remaining });\n });\n }\n return result;\n },\n {},\n );\n },\n };\n return provisioner;\n}\n\n/**\n * Provision hosted inference for one user (#4759): create the tenant's\n * gateway project on first use, mint a per-user virtual key with `budget`,\n * store it as the user's `inference.provider-config` (`source: 'hosted'`,\n * `mode: 'hosted'`), keeping the prior config for revoke. Idempotent per\n * `(tenantId, userId)`: an active grant is returned (its budget synced when\n * it differs) and concurrent calls create one key.\n */\nexport function provisionHostedInference(\n options: HostedInferenceProvisionerOptions & {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n },\n): Promise<ProvisionHostedInferenceResult> {\n const { tenantId, userId, budget, ...deps } = options;\n return createHostedInferenceProvisioner(deps).provision({\n tenantId,\n userId,\n budget,\n });\n}\n\n/**\n * Revoke a user's hosted inference (downgrade, deletion): stop using the key\n * (restore the user's prior provider config, or mode `off`), retire the\n * state, and delete the gateway key when the SDK can (otherwise it is kept\n * as an owed deletion and audited). Idempotent.\n */\nexport function revokeHostedInference(\n options: HostedInferenceProvisionerOptions & {\n tenantId: string;\n userId: string;\n reason?: string;\n },\n): Promise<RevokeHostedInferenceResult> {\n const { tenantId, userId, reason, ...deps } = options;\n return createHostedInferenceProvisioner(deps).revoke({\n tenantId,\n userId,\n ...(reason ? { reason } : {}),\n });\n}\n\n/** Read a hosted budget from an `inference.hosted` grant's `metadata.budget`. */\nexport function hostedBudgetFromGrantMetadata(\n metadata: Record<string, unknown> | undefined,\n): HostedInferenceBudget | undefined {\n const budget = metadata?.budget;\n if (!budget || typeof budget !== 'object') return undefined;\n const { limitMicros, window } = budget as Record<string, unknown>;\n if (typeof limitMicros !== 'number') return undefined;\n return {\n limitMicros,\n ...(typeof window === 'string'\n ? { window: window as HostedBudgetWindow }\n : {}),\n };\n}\n\n/** Structural mirror of smrt-subscriptions' `EntitlementSnapshot` (grants). */\nexport interface HostedEntitlementSnapshot {\n grants: ReadonlyArray<{\n featureKey: string;\n enabled?: boolean;\n metadata?: Record<string, unknown>;\n }>;\n}\n\n/** Structural mirror of smrt-subscriptions' `EntitlementChange`. */\nexport interface HostedEntitlementChange {\n tenantId: string;\n gained: readonly string[];\n lost: readonly string[];\n changed: readonly string[];\n after: HostedEntitlementSnapshot;\n}\n\n/** Options for the entitlement listener and reconciler. */\nexport interface HostedInferenceEntitlementListenerOptions {\n provisioner: HostedInferenceProvisioner;\n /**\n * Users who should hold a grant while the tenant is entitled (the\n * tenant's members; membership is the host's). Users joining later are\n * provisioned by the host calling `provision()` directly.\n */\n users(tenantId: string): Promise<readonly string[]>;\n /**\n * The budget for the tenant's plan. Default: the `inference.hosted`\n * grant's `metadata.budget` ({@link hostedBudgetFromGrantMetadata}), else\n * `defaultBudget`.\n */\n budgetFor?(\n tenantId: string,\n snapshot: HostedEntitlementSnapshot,\n ): HostedInferenceBudget | undefined;\n defaultBudget?: HostedInferenceBudget;\n}\n\nfunction hostedGrant(snapshot: HostedEntitlementSnapshot) {\n return snapshot.grants.find(\n (grant) =>\n grant.featureKey === HOSTED_INFERENCE_FEATURE_KEY &&\n grant.enabled !== false,\n );\n}\n\nfunction throwReconcileFailures(result: HostedReconcileResult): void {\n if (result.failures.length === 0) return;\n throw new HostedInferenceError(\n `Hosted inference failed for ${result.failures.length} user(s) of tenant ${result.tenantId}.`,\n 'HOSTED_GATEWAY_FAILED',\n {\n tenantId: result.tenantId,\n users: result.failures.map((failure) => failure.userId),\n codes: result.failures.map((failure) =>\n failure.error instanceof SmrtError\n ? failure.error.code\n : failure.error instanceof Error\n ? failure.error.name\n : 'unknown',\n ),\n },\n );\n}\n\n/**\n * Reconcile a tenant's hosted grants with its current entitlement snapshot\n * (smrt-subscriptions' `snapshotEntitlements()`): entitled provisions\n * `users()` and syncs budgets, not entitled revokes every grant, both retry\n * owed gateway deletions. Idempotent: run it on webhook redelivery (a\n * replayed write diffs to \"no change\") and on a schedule, so a reaction\n * lost to a crash or a failed listener is repaired. Throws one error naming\n * the users that failed, after trying all of them.\n */\nexport function createHostedInferenceReconciler(\n options: HostedInferenceEntitlementListenerOptions,\n): (\n tenantId: string,\n snapshot: HostedEntitlementSnapshot,\n) => Promise<HostedReconcileResult> {\n return async (tenantId, snapshot) => {\n const grant = hostedGrant(snapshot);\n if (!grant) {\n const result = await options.provisioner.reconcileTenant(tenantId, {\n entitled: false,\n });\n throwReconcileFailures(result);\n return result;\n }\n const budget =\n options.budgetFor?.(tenantId, snapshot) ??\n hostedBudgetFromGrantMetadata(grant.metadata) ??\n options.defaultBudget;\n if (!budget) {\n throw new HostedInferenceError(\n `The ${HOSTED_INFERENCE_FEATURE_KEY} grant names no budget (metadata.budget.limitMicros) and no defaultBudget is set.`,\n 'HOSTED_INFERENCE_INVALID',\n { tenantId },\n );\n }\n const result = await options.provisioner.reconcileTenant(tenantId, {\n entitled: true,\n budget,\n users: await options.users(tenantId),\n });\n throwReconcileFailures(result);\n return result;\n };\n}\n\n/**\n * The smrt-subscriptions entitlement-change listener for hosted inference:\n * register it with `registerEntitlementChangeListener()`. A change that\n * gains, loses or changes `inference.hosted` reconciles the tenant to the\n * change's `after` snapshot ({@link createHostedInferenceReconciler}).\n */\nexport function createHostedInferenceEntitlementListener(\n options: HostedInferenceEntitlementListenerOptions,\n): (change: HostedEntitlementChange) => Promise<void> {\n const reconcile = createHostedInferenceReconciler(options);\n return async (change) => {\n const touches = [change.gained, change.lost, change.changed].some((keys) =>\n keys.includes(HOSTED_INFERENCE_FEATURE_KEY),\n );\n if (!touches) return;\n await reconcile(change.tenantId, change.after);\n };\n}\n","/**\n * `smrt doctor --ai` (#4752), Node-only subpath.\n *\n * {@link checkInferenceBindings} walks every tier and alias binding offline:\n * provider recognized, key variable present, model named, alias resolvable\n * and cycle-free. With `probe` it also calls each candidate's cheapest\n * list endpoint with a short timeout. Keys are read to test presence and\n * sent only to their own endpoint; they are never printed or returned.\n *\n * The same functions back the settings panel's test-connection action\n * (#4754): `probeInferenceCandidate()` tests one candidate.\n *\n * With `probe`, each candidate also reports its capabilities (#4758) and\n * their source: the model listing's evidence (context length, supported\n * parameters, input modalities) is the `probe` layer under core's preset\n * table and over its model heuristic. The SDK's `getCapabilities()` is the\n * call-time `provider` layer; the doctor builds no SDK clients, so it reads\n * that layer only through `sdkCapabilities`.\n */\nimport { AI_PROVIDER_TYPES } from '@happyvertical/ai';\nimport {\n getDefaultAIKeyEnvName,\n redactBaseUrl,\n} from '@happyvertical/smrt-config';\nimport {\n assertInferenceConfig,\n getInferenceConfig,\n INFERENCE_PRESET_CAPABILITIES,\n INFERENCE_PRESETS,\n INFERENCE_TIERS,\n type InferenceCandidate,\n type InferenceCapabilities,\n type InferenceCapabilityEvidence,\n type InferenceConfig,\n type InferenceTarget,\n inferenceCapabilitiesFromSdk,\n inferenceCapabilityHeuristic,\n inferencePresetFor,\n inferenceTargetKind,\n isInferencePresetId,\n resolveInferenceCapabilities,\n} from '@happyvertical/smrt-core';\nimport { candidateLabel, findInferenceConfigIssues } from './config.js';\nimport type { InferenceConfigIssue } from './types.js';\n\nexport type InferenceDoctorStatus = 'ok' | 'warn' | 'error';\n\n/** Result of one reachability probe. */\nexport interface InferenceProbeResult {\n /** True when the endpoint answered and accepted the credential. */\n reachable: boolean;\n /** True when no cheap probe is known for the provider. */\n skipped?: boolean;\n /** HTTP status, when one came back. */\n status?: number;\n durationMs: number;\n /** Short, key-free explanation. */\n detail: string;\n /**\n * What the model listing says about the candidate's model (#4758), when\n * the endpoint answered and listed it with capability fields.\n */\n capabilities?: InferenceCapabilityEvidence;\n}\n\n/** One candidate of a binding. */\nexport interface InferenceDoctorCandidate {\n label: string;\n status: InferenceDoctorStatus;\n problems: string[];\n probe?: InferenceProbeResult;\n /** With `probe`: the candidate's capabilities and their source (#4758). */\n capabilities?: InferenceCapabilities;\n}\n\n/** One tier or alias binding. */\nexport interface InferenceDoctorBinding {\n /** `tier:<name>` or `alias:<name>`. */\n binding: string;\n /** Key-free description of the target. */\n target: string;\n status: InferenceDoctorStatus;\n problems: string[];\n candidates: InferenceDoctorCandidate[];\n}\n\n/** The doctor's answer. `ok` is false when any binding cannot resolve. */\nexport interface InferenceDoctorReport {\n ok: boolean;\n /** False when no `inference` block is configured (every tier uses the configured client). */\n configured: boolean;\n bindings: InferenceDoctorBinding[];\n issues: InferenceConfigIssue[];\n}\n\n/** Options for {@link checkInferenceBindings} and the probe. */\nexport interface CheckInferenceBindingsOptions {\n /** Config to check. Default: core's merged `getInferenceConfig()`. */\n config?: InferenceConfig;\n /** Environment for key presence. Default `process.env`. */\n env?: Record<string, string | undefined>;\n /** Call each candidate's list endpoint. Default false (offline). */\n probe?: boolean;\n /** Probe timeout per candidate, ms. Default 3000. */\n timeoutMs?: number;\n /** Fetch implementation for probes. Default global `fetch`. */\n fetch?: typeof fetch;\n /** Recognized provider names. Default the AI SDK's provider list. */\n providers?: readonly string[];\n /**\n * The SDK `getCapabilities()` answer for a candidate (the `provider`\n * layer, #4758), when the host can supply one. Default: none; the doctor\n * builds no SDK clients.\n */\n sdkCapabilities?: (\n candidate: InferenceCandidate,\n ) => unknown | Promise<unknown>;\n}\n\nconst DEFAULT_PROBE_TIMEOUT_MS = 3000;\nconst OPENAI_DEFAULT_BASE = 'https://api.openai.com/v1';\nconst ANTHROPIC_DEFAULT_BASE = 'https://api.anthropic.com/v1';\nconst GEMINI_DEFAULT_BASE = 'https://generativelanguage.googleapis.com/v1beta';\nconst OLLAMA_DEFAULT_BASE = 'http://127.0.0.1:11434';\nconst OPENAI_COMPATIBLE = new Set(['openai', 'litellm', 'bifrost']);\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\nfunction worst(statuses: InferenceDoctorStatus[]): InferenceDoctorStatus {\n if (statuses.includes('error')) return 'error';\n if (statuses.includes('warn')) return 'warn';\n return 'ok';\n}\n\n/** Where a candidate's key comes from, without reading its value out. */\nfunction keySource(candidate: InferenceCandidate): {\n env?: string;\n inline?: boolean;\n required: boolean;\n providerDefault?: boolean;\n} {\n if (text(candidate.apiKey)) return { inline: true, required: true };\n const env = text(candidate.apiKeyEnv);\n if (env) return { env, required: true };\n const preset = text(candidate.preset);\n if (preset && isInferencePresetId(preset)) {\n return { required: INFERENCE_PRESETS[preset].requiresApiKey };\n }\n if (text(candidate.baseUrl)) return { required: false };\n const providerEnv = getDefaultAIKeyEnvName(candidate.provider);\n return providerEnv\n ? { env: providerEnv, required: true, providerDefault: true }\n : { required: false };\n}\n\nfunction resolveKey(\n candidate: InferenceCandidate,\n env: Record<string, string | undefined>,\n): string | undefined {\n const source = keySource(candidate);\n if (source.inline) return text(candidate.apiKey);\n return source.env ? text(env[source.env]) : undefined;\n}\n\nfunction checkCandidateOffline(\n candidate: InferenceCandidate,\n options: Required<Pick<CheckInferenceBindingsOptions, 'env' | 'providers'>>,\n): InferenceDoctorCandidate {\n const label = candidateLabel(candidate);\n const problems: string[] = [];\n const statuses: InferenceDoctorStatus[] = [];\n const fail = (message: string) => {\n problems.push(message);\n statuses.push('error');\n };\n const preset = text(candidate.preset);\n const provider = text(candidate.provider);\n if (preset) {\n if (!isInferencePresetId(preset)) fail(`unknown preset \"${preset}\"`);\n } else if (provider) {\n if (!options.providers.includes(provider.toLowerCase())) {\n fail(`provider \"${provider}\" is not recognized`);\n }\n }\n if (!text(candidate.model)) {\n problems.push('no model named (the provider default answers)');\n statuses.push('warn');\n }\n const source = keySource(candidate);\n if (source.required && !resolveKey(candidate, options.env)) {\n if (source.providerDefault) {\n // Same-provider targets inherit the configured client's key, which\n // this offline check cannot see: a warning, not an error.\n problems.push(\n `key variable ${source.env} is not set (fine if the configured client is this provider)`,\n );\n statuses.push('warn');\n } else {\n fail(\n source.env\n ? `key variable ${source.env} is not set`\n : 'a key is required and none is configured',\n );\n }\n }\n if ((text(candidate.apiKeyEnv) || text(candidate.apiKey)) && !preset) {\n if (!text(candidate.baseUrl)) {\n fail('brings its own key without a baseUrl');\n }\n }\n const sdkWide = ['HAVE_AI_BASE_URL', 'HAVE_AI_API_KEY'].find((name) =>\n text(options.env[name]),\n );\n if (sdkWide && provider && !preset && !text(candidate.baseUrl)) {\n // Core refuses another provider's default endpoint while the SDK-wide\n // fallbacks are set; only the configured provider passes.\n problems.push(\n `${sdkWide} is set: core refuses this candidate unless ${provider} is the configured provider (give it a baseUrl and apiKeyEnv)`,\n );\n statuses.push('warn');\n }\n return { label, status: worst(statuses), problems };\n}\n\nfunction probeRequest(\n candidate: InferenceCandidate,\n key: string | undefined,\n): { url: string; headers: Record<string, string> } | undefined {\n const preset = text(candidate.preset);\n const provider = (text(candidate.provider) ?? '').toLowerCase();\n const baseUrl =\n text(candidate.baseUrl) ??\n (preset && isInferencePresetId(preset)\n ? INFERENCE_PRESETS[preset].baseUrl\n : undefined);\n const trim = (url: string) => url.replace(/\\/+$/, '');\n // A gateway has no public default endpoint: never send its key to OpenAI's.\n if (\n !preset &&\n !baseUrl &&\n provider !== 'openai' &&\n OPENAI_COMPATIBLE.has(provider)\n ) {\n return undefined;\n }\n if (preset || OPENAI_COMPATIBLE.has(provider)) {\n return {\n url: `${trim(baseUrl ?? OPENAI_DEFAULT_BASE)}/models`,\n headers: key ? { authorization: `Bearer ${key}` } : {},\n };\n }\n if (provider === 'anthropic') {\n return {\n url: `${trim(baseUrl ?? ANTHROPIC_DEFAULT_BASE)}/models`,\n headers: {\n 'anthropic-version': '2023-06-01',\n ...(key ? { 'x-api-key': key } : {}),\n },\n };\n }\n if (provider === 'gemini') {\n return {\n url: `${trim(baseUrl ?? GEMINI_DEFAULT_BASE)}/models`,\n headers: key ? { 'x-goog-api-key': key } : {},\n };\n }\n if (provider === 'ollama') {\n return {\n url: `${trim(baseUrl ?? OLLAMA_DEFAULT_BASE)}/api/tags`,\n headers: {},\n };\n }\n return undefined;\n}\n\nfunction positive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0\n ? Math.floor(value)\n : undefined;\n}\n\n/**\n * Capability evidence for `model` from a list endpoint's body (#4758):\n * OpenAI-compatible `data[]` entries (OpenRouter `context_length`,\n * `supported_parameters`, `architecture.input_modalities`; LM Studio\n * `max_context_length`; llama.cpp `meta.n_ctx_train`) and Gemini\n * `models[].inputTokenLimit`. Unknown shapes give nothing.\n */\nexport function inferenceCapabilitiesFromModelListing(\n body: unknown,\n model: string | undefined,\n): InferenceCapabilityEvidence | undefined {\n if (!model || !body || typeof body !== 'object') return undefined;\n const record = body as { data?: unknown; models?: unknown };\n const list = Array.isArray(record.data)\n ? record.data\n : Array.isArray(record.models)\n ? record.models\n : [];\n const entry = list.find((item) => {\n const row = item as { id?: unknown; name?: unknown };\n return (\n row?.id === model ||\n row?.name === model ||\n row?.name === `models/${model}`\n );\n }) as Record<string, unknown> | undefined;\n if (!entry) return undefined;\n const out: InferenceCapabilityEvidence = {};\n const meta = entry.meta as Record<string, unknown> | undefined;\n const topProvider = entry.top_provider as Record<string, unknown> | undefined;\n const maxContext =\n positive(entry.context_length) ??\n positive(entry.max_context_length) ??\n positive(topProvider?.context_length) ??\n positive(meta?.n_ctx_train) ??\n positive(entry.inputTokenLimit);\n if (maxContext !== undefined) out.maxContext = maxContext;\n if (Array.isArray(entry.supported_parameters)) {\n const params = entry.supported_parameters;\n out.tools = params.includes('tools');\n out.json =\n params.includes('response_format') ||\n params.includes('structured_outputs');\n }\n const architecture = entry.architecture as\n | Record<string, unknown>\n | undefined;\n if (Array.isArray(architecture?.input_modalities)) {\n out.vision = architecture.input_modalities.includes('image');\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * A candidate's capabilities from every layer the doctor can see (#4758):\n * the host's SDK answer (when given), the preset table, the probe's\n * evidence, the model heuristic, then assumed.\n */\nexport async function inferenceCandidateCapabilities(\n candidate: InferenceCandidate,\n options: {\n probe?: InferenceCapabilityEvidence;\n sdkCapabilities?: CheckInferenceBindingsOptions['sdkCapabilities'];\n } = {},\n): Promise<InferenceCapabilities> {\n const preset = inferencePresetFor(candidate);\n let sdk: unknown;\n // The SDK answer describes the provider's own endpoint only.\n if (options.sdkCapabilities && !text(candidate.baseUrl) && !preset) {\n try {\n sdk = await options.sdkCapabilities(candidate);\n } catch {\n sdk = undefined;\n }\n }\n return resolveInferenceCapabilities({\n provider: inferenceCapabilitiesFromSdk(sdk),\n preset: preset ? INFERENCE_PRESET_CAPABILITIES[preset] : undefined,\n probe: options.probe,\n heuristic: inferenceCapabilityHeuristic(candidate),\n });\n}\n\n/**\n * The URL {@link probeInferenceCandidate} would call for a candidate, or\n * `undefined` when no cheap probe is known. Key-free; the settings key rule\n * (#4754) binds a key to this URL's origin.\n */\nexport function inferenceProbeUrl(\n candidate: InferenceCandidate,\n): string | undefined {\n return probeRequest(candidate, undefined)?.url;\n}\n\n/**\n * Probe one candidate's cheapest list endpoint (`/models`, Ollama\n * `/api/tags`) with a short timeout. The key goes only to the candidate's\n * own endpoint and never appears in the result.\n */\nexport async function probeInferenceCandidate(\n candidate: InferenceCandidate,\n options: Pick<\n CheckInferenceBindingsOptions,\n 'env' | 'timeoutMs' | 'fetch'\n > = {},\n): Promise<InferenceProbeResult> {\n const env = options.env ?? process.env;\n const preset = text(candidate.preset);\n if (preset && !isInferencePresetId(preset)) {\n return {\n reachable: false,\n durationMs: 0,\n detail: `not probed: unknown preset \"${preset}\"`,\n };\n }\n if (\n (text(candidate.apiKey) || text(candidate.apiKeyEnv)) &&\n !text(candidate.baseUrl) &&\n !preset\n ) {\n // Core's pair rule: a candidate's own key travels only with its own\n // endpoint. Refuse before any request rather than sending it to a\n // provider default.\n return {\n reachable: false,\n durationMs: 0,\n detail:\n 'not probed: a candidate key needs its own baseUrl (it is never sent to a provider default endpoint)',\n };\n }\n const request = probeRequest(candidate, resolveKey(candidate, env));\n if (!request) {\n return {\n reachable: false,\n skipped: true,\n durationMs: 0,\n detail: `no cheap probe for provider \"${candidate.provider ?? 'default'}\"`,\n };\n }\n const doFetch = options.fetch ?? globalThis.fetch;\n const controller = new AbortController();\n const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const started = Date.now();\n const where = redactBaseUrl(request.url) ?? 'endpoint';\n try {\n const response = await doFetch(request.url, {\n method: 'GET',\n headers: request.headers,\n signal: controller.signal,\n // A redirect would carry the key's header to another host.\n redirect: 'manual',\n });\n const durationMs = Date.now() - started;\n if (\n response.type === 'opaqueredirect' ||\n (response.status >= 300 && response.status < 400)\n ) {\n return {\n reachable: false,\n status: response.status || undefined,\n durationMs,\n detail: `${where} redirected (not followed: the key stays with its own endpoint)`,\n };\n }\n if (response.ok) {\n let capabilities: InferenceCapabilityEvidence | undefined;\n try {\n capabilities = inferenceCapabilitiesFromModelListing(\n await response.json(),\n text(candidate.model),\n );\n } catch {\n // A listing that is not JSON still proves reachability.\n }\n return {\n reachable: true,\n status: response.status,\n durationMs,\n detail: `${where} answered ${response.status}`,\n ...(capabilities ? { capabilities } : {}),\n };\n }\n const why =\n response.status === 401 || response.status === 403\n ? 'credential rejected'\n : response.status === 402 || response.status === 429\n ? 'quota or rate limit'\n : 'endpoint error';\n return {\n reachable: false,\n status: response.status,\n durationMs,\n detail: `${where} answered ${response.status} (${why})`,\n };\n } catch (error) {\n const aborted = controller.signal.aborted;\n return {\n reachable: false,\n durationMs: Date.now() - started,\n detail: aborted\n ? `${where} did not answer within ${timeoutMs} ms`\n : `${where} unreachable (${error instanceof Error ? error.name : 'error'})`,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction describeTarget(target: InferenceTarget): string {\n const kind = inferenceTargetKind(target);\n if (kind === 'alias') return `alias ${String(target.alias)}`;\n if (kind === 'candidates') {\n return `${target.candidates?.length ?? 0} inline candidate(s)`;\n }\n if (kind === 'fusion') return `fusion ${String(target.fusion)}`;\n if (kind !== 'single') return `kind ${kind} (not checked here)`;\n const base = text(target.baseUrl);\n return `${candidateLabel(target)}${base ? ` at ${redactBaseUrl(base)}` : ''}`;\n}\n\n/**\n * Walk every tier and alias binding; see the module comment. Never throws on\n * a bad config: problems come back in the report, and `ok` is false when any\n * binding cannot resolve.\n */\nexport async function checkInferenceBindings(\n options: CheckInferenceBindingsOptions = {},\n): Promise<InferenceDoctorReport> {\n const env = options.env ?? process.env;\n const providers = (options.providers ?? AI_PROVIDER_TYPES).map((name) =>\n name.toLowerCase(),\n );\n const bindings: InferenceDoctorBinding[] = [];\n let config: InferenceConfig | undefined;\n try {\n config = 'config' in options ? options.config : getInferenceConfig();\n assertInferenceConfig(config);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const issue: InferenceConfigIssue = {\n path: 'inference',\n code: 'candidate-invalid',\n message,\n };\n return {\n ok: false,\n configured: true,\n bindings: [\n {\n binding: 'inference',\n target: 'config',\n status: 'error',\n problems: [message],\n candidates: [],\n },\n ],\n issues: [issue],\n };\n }\n const issues = findInferenceConfigIssues(config);\n const aliases = config?.aliases ?? {};\n const issuesFor = (prefix: string) =>\n issues\n .filter(\n (issue) =>\n issue.path === prefix ||\n issue.path.startsWith(`${prefix}.`) ||\n issue.path.startsWith(`${prefix}[`),\n )\n .map((issue) => issue.message);\n\n const checkList = async (\n list: readonly InferenceCandidate[],\n ): Promise<InferenceDoctorCandidate[]> => {\n const out: InferenceDoctorCandidate[] = [];\n for (const candidate of list) {\n const nested = text(candidate.alias);\n if (nested) {\n out.push({\n label: `alias:${nested}`,\n status: Object.hasOwn(aliases, nested) ? 'ok' : 'error',\n problems: Object.hasOwn(aliases, nested)\n ? [`expands alias ${nested} (checked under alias:${nested})`]\n : [`unknown alias \"${nested}\"`],\n });\n continue;\n }\n const checked = checkCandidateOffline(candidate, { env, providers });\n if (options.probe && checked.status !== 'error') {\n checked.probe = await probeInferenceCandidate(candidate, {\n env,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n });\n if (!checked.probe.reachable && !checked.probe.skipped) {\n checked.status = 'error';\n checked.problems.push(checked.probe.detail);\n }\n checked.capabilities = await inferenceCandidateCapabilities(candidate, {\n probe: checked.probe.capabilities,\n sdkCapabilities: options.sdkCapabilities,\n });\n }\n out.push(checked);\n }\n return out;\n };\n\n const settle = (\n binding: string,\n target: string,\n configProblems: string[],\n candidates: InferenceDoctorCandidate[],\n ): InferenceDoctorBinding => {\n const usable = candidates.some((candidate) => candidate.status !== 'error');\n const status: InferenceDoctorStatus =\n configProblems.length > 0 || (candidates.length > 0 && !usable)\n ? 'error'\n : candidates.every((candidate) => candidate.status === 'ok')\n ? 'ok'\n : // Some candidate cannot answer but another can: the chain still\n // resolves, so this warns rather than fails.\n 'warn';\n return {\n binding,\n target,\n status,\n problems: [\n ...configProblems,\n ...(configProblems.length === 0 && candidates.length > 0 && !usable\n ? ['no candidate can answer']\n : []),\n ],\n candidates,\n };\n };\n\n for (const tier of INFERENCE_TIERS) {\n const target = config?.tiers?.[tier];\n const name = `tier:${tier}`;\n if (!target || Object.keys(target).length === 0) {\n bindings.push({\n binding: name,\n target: 'the configured client',\n status: 'ok',\n problems: [],\n candidates: [],\n });\n continue;\n }\n const kind = inferenceTargetKind(target);\n const configProblems = issuesFor(`inference.tiers.${tier}`);\n let candidates: InferenceDoctorCandidate[] = [];\n if (kind === 'single') {\n candidates = await checkList([target as InferenceCandidate]);\n } else if (kind === 'candidates') {\n candidates = await checkList(target.candidates ?? []);\n } else if (kind === 'alias') {\n const aliasName = text(target.alias);\n candidates = [\n {\n label: `alias:${aliasName ?? '?'}`,\n status:\n aliasName && Object.hasOwn(aliases, aliasName) ? 'ok' : 'error',\n problems: [],\n },\n ];\n }\n bindings.push(\n settle(name, describeTarget(target), configProblems, candidates),\n );\n }\n\n for (const [name, alias] of Object.entries(aliases)) {\n const list = Array.isArray(alias?.candidates) ? alias.candidates : [];\n bindings.push(\n settle(\n `alias:${name}`,\n `${list.length} candidate(s)${alias?.description ? ` — ${alias.description}` : ''}`,\n issuesFor(`inference.aliases.${name}`),\n await checkList(list),\n ),\n );\n }\n\n // Fusions (#4753): structure only; each seat's target is a tier, alias or\n // model checked above. Prompt keys are the app's and are checked at load.\n for (const [name, fusion] of Object.entries(config?.fusions ?? {})) {\n const seats = Array.isArray(fusion?.seats) ? fusion.seats.length : 0;\n bindings.push(\n settle(\n `fusion:${name}`,\n `${String(fusion?.mode)}, ${seats} seat(s)${fusion?.description ? ` — ${fusion.description}` : ''}`,\n issuesFor(`inference.fusions.${name}`),\n [],\n ),\n );\n }\n\n // A tier bound to an alias inherits that alias's verdict.\n for (const binding of bindings) {\n if (!binding.binding.startsWith('tier:')) continue;\n const ref = binding.candidates[0]?.label;\n if (binding.candidates.length !== 1 || !ref?.startsWith('alias:')) continue;\n const resolved = bindings.find((entry) => entry.binding === ref);\n const reference = binding.candidates[0];\n if (resolved && reference) reference.status = resolved.status;\n if (resolved?.status === 'error') {\n binding.status = 'error';\n binding.problems.push(`${ref} cannot resolve`);\n } else if (resolved?.status === 'warn' && binding.status === 'ok') {\n binding.status = 'warn';\n binding.problems.push(`${ref} has a candidate that cannot answer`);\n }\n }\n\n return {\n ok: !bindings.some((binding) => binding.status === 'error'),\n configured: !!config && (!!config.tiers || !!config.aliases),\n bindings,\n issues,\n };\n}\n\n/** One line describing capabilities, e.g. `json no, tools yes, … (preset)`. */\nexport function formatInferenceCapabilities(\n capabilities: InferenceCapabilities,\n): string {\n const flag = (value: boolean) => (value ? 'yes' : 'no');\n return `json ${flag(capabilities.json)}, tools ${flag(capabilities.tools)}, vision ${flag(capabilities.vision)}, context ${capabilities.maxContext ?? 'unknown'} (source: ${capabilities.source})`;\n}\n\nconst ICON: Record<InferenceDoctorStatus, string> = {\n ok: '✅',\n warn: '⚠️ ',\n error: '❌',\n};\n\n/** The lines `smrt doctor --ai` prints for a report. */\nexport function formatInferenceDoctorReport(\n report: InferenceDoctorReport,\n): string[] {\n const lines: string[] = [];\n if (!report.configured) {\n lines.push(\n ' No inference config: every tier uses the configured AI client.',\n );\n }\n for (const binding of report.bindings) {\n lines.push(\n ` ${ICON[binding.status]} ${binding.binding} → ${binding.target}`,\n );\n for (const problem of binding.problems) lines.push(` ${problem}`);\n for (const candidate of binding.candidates) {\n const probe = candidate.probe\n ? candidate.probe.skipped\n ? ' (probe skipped)'\n : ` (${candidate.probe.detail}, ${candidate.probe.durationMs} ms)`\n : '';\n lines.push(` ${ICON[candidate.status]} ${candidate.label}${probe}`);\n if (candidate.capabilities) {\n lines.push(\n ` capabilities: ${formatInferenceCapabilities(candidate.capabilities)}`,\n );\n }\n for (const problem of candidate.problems) {\n if (candidate.probe && problem === candidate.probe.detail) continue;\n lines.push(` ${problem}`);\n }\n }\n }\n return lines;\n}\n\nexport * from './hosted.js';\nexport type { InferenceConfigIssue } from './types.js';\n"],"mappings":"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"}
1
+ {"version":3,"file":"node.js","names":["model","next","audit","result"],"sources":["../src/hosted.ts","../src/node.ts"],"sourcesContent":["/**\n * Hosted inference provisioning (#4759), Node-only (`./node`).\n *\n * When a member gains `inference.hosted`, SMRT mints them a per-user virtual\n * key on the operator's AI gateway (`bifrost` or `litellm`, through the\n * `ai.admin` surface of `@happyvertical/ai`; SMRT never talks to a gateway's\n * admin API itself) and stores it where the BYO path already looks: the\n * user's `inference.provider-config` secret (#4756) with `source: 'hosted'`\n * and `mode: 'hosted'` (#4757). Switching to hosted therefore changes only\n * the stored provider config.\n *\n * Storage (all smrt-secrets rows under `context = tenantId`, every write and\n * delete audited through `SecretAuditLog` with an `operation` detail):\n * - `inference.provider-config:user:<userId>`: the hosted binding (holds the\n * virtual key; the only place it lives).\n * - `inference.hosted:user:<userId>`: provisioning state: status, lifecycle\n * generation, the key's non-secret id and alias, the budget, and the\n * user's prior provider config (restored on revoke).\n * - `inference.hosted:project`: the tenant's gateway project id.\n *\n * Idempotency: provisioning runs under `runOnce()` per `(tenantId, userId,\n * generation)`, so concurrent deliveries create one key; the generation\n * advances on every revoke, so a later re-provision runs again. The tenant\n * project is looked up on the gateway (`listProjects`) before one is\n * created, so a crash between create and persist never makes a second. Lifecycle\n * operations run one at a time per process and, on PostgreSQL, under a\n * per-tenant advisory lock across processes. A revoke ends local\n * use of the key whatever the gateway answers; deletions the gateway could\n * not perform are kept as `pendingGatewayRevocations` and retried by\n * `revoke()` / `reconcileTenant()`.\n *\n * The key never appears in returned objects, errors, logs, usage tags,\n * audit details or `runOnce()` results: usage rows carry `source: hosted`\n * and the key's alias (`gatewayKeyAlias`) only.\n */\nimport type {\n AIAdminBudget,\n AIAdminInterface,\n AIAdminProject,\n AIVirtualKey,\n CreateAIVirtualKeyOptions,\n} from '@happyvertical/ai';\nimport {\n ensureSystemTables,\n INFERENCE_TIERS,\n type InferenceTier,\n isPostgresDatabase,\n runOnce,\n} from '@happyvertical/smrt-core';\nimport { SmrtError } from '@happyvertical/smrt-core/smrt-error';\nimport { SecretService } from '@happyvertical/smrt-secrets';\nimport { withTenant } from '@happyvertical/smrt-tenancy';\nimport type { DatabaseInterface } from '@happyvertical/sql';\nimport { classifyInferenceFailure } from './classify.js';\nimport {\n INFERENCE_PROVIDER_CONFIG_SECRET_TYPE,\n type InferenceProviderBinding,\n type InferenceProviderConfig,\n inferenceProviderBindingFor,\n inferenceProviderConfigSecretName,\n parseInferenceProviderConfig,\n} from './provider-config.js';\nimport { invalidateUserInferenceResolvers } from './resolver.js';\n\n/** The feature key that entitles hosted inference (`smrt-features`' `FEATURE_INFERENCE_HOSTED`). */\nexport const HOSTED_INFERENCE_FEATURE_KEY = 'inference.hosted';\n\n/** smrt-secrets category of the hosted state and project rows. */\nexport const HOSTED_INFERENCE_SECRET_CATEGORY = 'inference.hosted';\n\n/** Gateway providers of `@happyvertical/ai` that expose `ai.admin`. */\nexport const HOSTED_GATEWAY_PROVIDERS = ['bifrost', 'litellm'] as const;\n\nexport type HostedGatewayProvider = (typeof HOSTED_GATEWAY_PROVIDERS)[number];\n\n/** Budget reset windows; mapped to the gateway's reset duration. */\nexport const HOSTED_BUDGET_WINDOWS = ['day', 'week', 'month', 'year'] as const;\n\nexport type HostedBudgetWindow = (typeof HOSTED_BUDGET_WINDOWS)[number];\n\nconst WINDOW_DURATION: Record<HostedBudgetWindow, string> = {\n day: '1d',\n week: '7d',\n month: '30d',\n year: '365d',\n};\n\n/**\n * A hosted user's spend cap. Money is an integer: micro-dollars (USD ×\n * 1,000,000), the unit #4760's spend gate meters. Converted to the gateway's\n * USD `maxLimit` only at the admin call.\n */\nexport interface HostedInferenceBudget {\n limitMicros: number;\n /** Reset window. Default `month`. */\n window?: HostedBudgetWindow;\n}\n\n/** Error codes of {@link HostedInferenceError}. */\nexport type HostedInferenceErrorCode =\n | 'HOSTED_INFERENCE_INVALID'\n | 'HOSTED_GATEWAY_NOT_CONFIGURED'\n | 'HOSTED_GATEWAY_UNSUPPORTED'\n | 'HOSTED_GATEWAY_FAILED'\n | 'HOSTED_STATE_INVALID'\n | 'HOSTED_NOT_ENTITLED';\n\n/**\n * A provisioning failure. Messages name fields and operations, never a key,\n * an admin credential or a gateway response body.\n */\nexport class HostedInferenceError extends SmrtError {\n constructor(\n message: string,\n code: HostedInferenceErrorCode,\n details: Record<string, unknown> = {},\n ) {\n super(message, code, 'configuration', details);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Gateway configuration and admin client\n// ---------------------------------------------------------------------------\n\n/**\n * The operator's gateway. Admin credentials stay in this object (built once\n * from the host environment) and are only handed to `@happyvertical/ai`.\n */\nexport interface HostedGatewayConfig {\n provider: HostedGatewayProvider;\n /** OpenAI-compatible inference endpoint the virtual keys are used against. */\n baseUrl: string;\n /** Admin API root, when it differs from `baseUrl`. */\n adminUrl?: string;\n /** LiteLLM master key, or a Bifrost admin virtual key. */\n adminApiKey?: string;\n /** Bifrost governance Basic-auth user. */\n adminUser?: string;\n /** Bifrost governance Basic-auth password. */\n adminPassword?: string;\n /** Default model of the stored binding. */\n model?: string;\n /** Per-tier models of the stored binding. */\n tiers?: Partial<Record<InferenceTier, string>>;\n /** Models the virtual key may use. Default: `model` plus the tier models. */\n models?: string[];\n /** Key lifetime (`30d`, `permanent`, ...), where the gateway supports it. */\n keyDuration?: string;\n /**\n * Deadline for each admin call in ms (default\n * {@link HOSTED_GATEWAY_ADMIN_TIMEOUT_MS}). A call past it counts as\n * failed, so a stalled gateway never holds the lifecycle lock.\n */\n adminTimeoutMs?: number;\n}\n\n/** Default deadline of one gateway admin call. */\nexport const HOSTED_GATEWAY_ADMIN_TIMEOUT_MS = 30_000;\n\n/** The `AIAdminInterface` calls provisioning uses. */\nexport const HOSTED_GATEWAY_ADMIN_METHODS = [\n 'createProject',\n 'createVirtualKey',\n 'updateVirtualKey',\n 'deleteVirtualKey',\n 'listProjects',\n] as const;\n\n/**\n * The admin surface provisioning uses (`@happyvertical/ai` >= 0.111.7,\n * happyvertical/sdk#1494). Plan changes send only `budget` to\n * `updateVirtualKey` (Bifrost refuses `models` / `metadata`, LiteLLM\n * `providerConfigs`, with `ADMIN_UNSUPPORTED_FIELD`); `deleteVirtualKey`\n * resolving `false` means the key is already gone.\n */\nexport type HostedGatewayAdmin = Pick<\n AIAdminInterface,\n (typeof HOSTED_GATEWAY_ADMIN_METHODS)[number]\n>;\n\n/** Environment variables {@link hostedGatewayConfigFromEnv} reads. */\nexport const HOSTED_GATEWAY_ENV = {\n provider: 'SMRT_INFERENCE_HOSTED_PROVIDER',\n baseUrl: 'SMRT_INFERENCE_HOSTED_BASE_URL',\n adminUrl: 'SMRT_INFERENCE_HOSTED_ADMIN_URL',\n adminApiKey: 'SMRT_INFERENCE_HOSTED_ADMIN_KEY',\n adminUser: 'SMRT_INFERENCE_HOSTED_ADMIN_USER',\n adminPassword: 'SMRT_INFERENCE_HOSTED_ADMIN_PASSWORD',\n model: 'SMRT_INFERENCE_HOSTED_MODEL',\n models: 'SMRT_INFERENCE_HOSTED_MODELS',\n keyDuration: 'SMRT_INFERENCE_HOSTED_KEY_DURATION',\n adminTimeoutMs: 'SMRT_INFERENCE_HOSTED_ADMIN_TIMEOUT_MS',\n} as const;\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\nfunction isPlainHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n (url.protocol === 'http:' || url.protocol === 'https:') &&\n !url.username &&\n !url.password &&\n !url.search &&\n !url.hash\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Validate a gateway config. Throws `HOSTED_GATEWAY_NOT_CONFIGURED` naming\n * the missing or invalid fields (never their values).\n */\nexport function parseHostedGatewayConfig(\n value: Partial<HostedGatewayConfig>,\n): HostedGatewayConfig {\n const issues: string[] = [];\n const provider = text(value.provider)?.toLowerCase();\n if (\n !provider ||\n !(HOSTED_GATEWAY_PROVIDERS as readonly string[]).includes(provider)\n ) {\n issues.push(\n `provider must be one of ${HOSTED_GATEWAY_PROVIDERS.join(', ')}`,\n );\n }\n const baseUrl = text(value.baseUrl);\n if (!baseUrl || !isPlainHttpUrl(baseUrl)) {\n issues.push(\n 'baseUrl must be an http(s) URL without credentials, query or fragment',\n );\n }\n const adminUrl = text(value.adminUrl);\n if (adminUrl && !isPlainHttpUrl(adminUrl)) {\n issues.push(\n 'adminUrl must be an http(s) URL without credentials, query or fragment',\n );\n }\n const adminApiKey = text(value.adminApiKey);\n const adminUser = text(value.adminUser);\n const adminPassword = text(value.adminPassword);\n if (provider === 'litellm' && !adminApiKey) {\n issues.push('adminApiKey (the LiteLLM master key) is required for litellm');\n }\n if (provider === 'bifrost' && !adminApiKey && !(adminUser && adminPassword)) {\n issues.push(\n 'bifrost needs adminApiKey or both adminUser and adminPassword',\n );\n }\n const tiers: Partial<Record<InferenceTier, string>> = {};\n for (const tier of INFERENCE_TIERS) {\n const model = text(value.tiers?.[tier]);\n if (model) tiers[tier] = model;\n }\n const adminTimeoutMs = value.adminTimeoutMs;\n if (\n adminTimeoutMs !== undefined &&\n (!Number.isSafeInteger(adminTimeoutMs) || adminTimeoutMs <= 0)\n ) {\n issues.push('adminTimeoutMs must be a positive integer (ms)');\n }\n const model = text(value.model);\n const models = (value.models ?? [])\n .map((entry) => text(entry))\n .filter((entry): entry is string => !!entry);\n if (issues.length > 0) {\n throw new HostedInferenceError(\n `Hosted inference gateway is not configured: ${issues.join('; ')}`,\n 'HOSTED_GATEWAY_NOT_CONFIGURED',\n { issues },\n );\n }\n return {\n provider: provider as HostedGatewayProvider,\n baseUrl: baseUrl as string,\n ...(adminUrl ? { adminUrl } : {}),\n ...(adminApiKey ? { adminApiKey } : {}),\n ...(adminUser ? { adminUser } : {}),\n ...(adminPassword ? { adminPassword } : {}),\n ...(model ? { model } : {}),\n ...(Object.keys(tiers).length > 0 ? { tiers } : {}),\n ...(models.length > 0 ? { models } : {}),\n ...(text(value.keyDuration)\n ? { keyDuration: text(value.keyDuration) }\n : {}),\n ...(adminTimeoutMs !== undefined ? { adminTimeoutMs } : {}),\n };\n}\n\n/**\n * Build the gateway config from environment variables\n * ({@link HOSTED_GATEWAY_ENV}, plus `SMRT_INFERENCE_HOSTED_<TIER>_MODEL`).\n * `undefined` when `SMRT_INFERENCE_HOSTED_PROVIDER` is unset (hosted\n * inference off); throws when it is set but incomplete.\n */\nexport function hostedGatewayConfigFromEnv(\n env: Record<string, string | undefined> = process.env,\n): HostedGatewayConfig | undefined {\n if (!text(env[HOSTED_GATEWAY_ENV.provider])) return undefined;\n const tiers: Partial<Record<InferenceTier, string>> = {};\n for (const tier of INFERENCE_TIERS) {\n const model = text(\n env[`SMRT_INFERENCE_HOSTED_${tier.toUpperCase()}_MODEL`],\n );\n if (model) tiers[tier] = model;\n }\n return parseHostedGatewayConfig({\n provider: env[HOSTED_GATEWAY_ENV.provider] as HostedGatewayProvider,\n baseUrl: env[HOSTED_GATEWAY_ENV.baseUrl],\n adminUrl: env[HOSTED_GATEWAY_ENV.adminUrl],\n adminApiKey: env[HOSTED_GATEWAY_ENV.adminApiKey],\n adminUser: env[HOSTED_GATEWAY_ENV.adminUser],\n adminPassword: env[HOSTED_GATEWAY_ENV.adminPassword],\n model: env[HOSTED_GATEWAY_ENV.model],\n tiers,\n models: (env[HOSTED_GATEWAY_ENV.models] ?? '').split(','),\n keyDuration: env[HOSTED_GATEWAY_ENV.keyDuration],\n ...(text(env[HOSTED_GATEWAY_ENV.adminTimeoutMs])\n ? {\n adminTimeoutMs: Number(text(env[HOSTED_GATEWAY_ENV.adminTimeoutMs])),\n }\n : {}),\n });\n}\n\nlet envConfig: { value: HostedGatewayConfig | undefined } | undefined;\n\n/**\n * The process gateway config, read from the environment once and cached.\n * Throws `HOSTED_GATEWAY_NOT_CONFIGURED` when hosted inference is not set up.\n */\nexport function getHostedGatewayConfig(): HostedGatewayConfig {\n envConfig ??= { value: hostedGatewayConfigFromEnv() };\n if (!envConfig.value) {\n throw new HostedInferenceError(\n `Hosted inference gateway is not configured: set ${HOSTED_GATEWAY_ENV.provider} (bifrost or litellm), ${HOSTED_GATEWAY_ENV.baseUrl} and the admin credentials.`,\n 'HOSTED_GATEWAY_NOT_CONFIGURED',\n );\n }\n return envConfig.value;\n}\n\n/** Forget the cached environment config (tests). */\nexport function resetHostedGatewayConfig(): void {\n envConfig = undefined;\n}\n\n/**\n * The `ai.admin` surface of the configured gateway provider, built through\n * `getAI()` of `@happyvertical/ai`. Throws `HOSTED_GATEWAY_UNSUPPORTED` when\n * the installed SDK's provider lacks any of {@link HOSTED_GATEWAY_ADMIN_METHODS}.\n */\nexport async function createHostedGatewayAdmin(\n config: HostedGatewayConfig,\n): Promise<HostedGatewayAdmin> {\n const { getAI } = await import('@happyvertical/ai');\n const common = {\n baseUrl: config.baseUrl,\n // The client is only used for admin calls; the inference key is\n // per-user and stored separately.\n apiKey: config.adminApiKey ?? 'smrt-admin',\n timeout: config.adminTimeoutMs ?? HOSTED_GATEWAY_ADMIN_TIMEOUT_MS,\n ...(config.adminUrl ? { adminUrl: config.adminUrl } : {}),\n ...(config.adminApiKey ? { adminApiKey: config.adminApiKey } : {}),\n };\n const ai =\n config.provider === 'bifrost'\n ? await getAI({\n type: 'bifrost',\n ...common,\n ...(config.adminUser ? { adminUser: config.adminUser } : {}),\n ...(config.adminPassword\n ? { adminPassword: config.adminPassword }\n : {}),\n })\n : await getAI({ type: 'litellm', ...common });\n const admin = ai.admin as Partial<HostedGatewayAdmin> | undefined;\n const missing = HOSTED_GATEWAY_ADMIN_METHODS.filter(\n (method) => typeof admin?.[method] !== 'function',\n );\n if (!admin || missing.length > 0) {\n throw new HostedInferenceError(\n `The ${config.provider} provider of @happyvertical/ai lacks admin calls: ${missing.join(', ')} (needs @happyvertical/ai >= 0.111.7).`,\n 'HOSTED_GATEWAY_UNSUPPORTED',\n { provider: config.provider, missing },\n );\n }\n return admin as HostedGatewayAdmin;\n}\n\n// ---------------------------------------------------------------------------\n// Stored state\n// ---------------------------------------------------------------------------\n\n/** A gateway key SMRT no longer uses but could not delete yet. */\nexport interface HostedPendingGatewayRevocation {\n /** The key's non-secret handle; `null` when the gateway returned none. */\n keyId: string | null;\n keyAlias: string;\n /**\n * Why it is pending: `failed` the delete call failed; `orphaned` a failed\n * provision may have left it; `unsupported` no admin client was usable or\n * the key has no id (entries recorded before `@happyvertical/ai` 0.111.7\n * also carry it). Entries with a `keyId` are retried; without one only an\n * operator can revoke the key (by alias) and acknowledge it.\n */\n reason: 'unsupported' | 'failed' | 'orphaned';\n since: string;\n}\n\n/** A user's provisioning state (`inference.hosted:user:<userId>`). */\nexport interface HostedInferenceState {\n version: 1;\n status: 'active' | 'revoked';\n /** Lifecycle generation; advances on every revoke. */\n generation: number;\n provider?: HostedGatewayProvider;\n projectId?: string;\n /** The gateway's non-secret key id (update/delete handle), when returned. */\n keyId: string | null;\n /** Empty when no key was ever stored (only pending cleanups). */\n keyAlias: string;\n budget?: Required<HostedInferenceBudget>;\n provisionedAt?: string;\n revokedAt?: string;\n /** The user's provider config before provisioning; dropped on revoke. */\n prior: InferenceProviderConfig | null;\n /**\n * Keys SMRT stopped using whose gateway deletion is still owed (a failed\n * delete, no usable admin or key id, or a key left by a failed provision).\n * Retried by `revoke()` and `reconcileTenant()`; never holds a key value.\n */\n pendingGatewayRevocations?: HostedPendingGatewayRevocation[];\n /**\n * The member replaced the hosted binding with their own config: reconcile\n * leaves them alone; only an explicit `provision()` re-provisions.\n */\n optedOut?: boolean;\n /**\n * Set when a budget update failed or timed out (it may still land on the\n * gateway); budget syncs re-apply the stored budget while it is set.\n */\n budgetSyncUncertainAt?: string;\n}\n\n/**\n * How long after an uncertain budget update a confirmed one must come\n * before the uncertainty is cleared (a late landing is overwritten by the\n * re-applies in between).\n */\nexport const HOSTED_BUDGET_UNCERTAIN_WINDOW_MS = 15 * 60_000;\n\n/** What provisioning and revocation return: never the key. */\nexport interface HostedInferenceDescriptor {\n tenantId: string;\n userId: string;\n status: 'active' | 'revoked' | 'none';\n generation: number;\n provider?: HostedGatewayProvider;\n projectId?: string;\n keyId?: string | null;\n keyAlias?: string;\n budget?: Required<HostedInferenceBudget>;\n /** Non-secret handles of keys whose gateway deletion is still owed. */\n pendingGatewayRevocations?: HostedPendingGatewayRevocation[];\n}\n\n/** Result of {@link HostedInferenceProvisioner.provision}. */\nexport interface ProvisionHostedInferenceResult\n extends HostedInferenceDescriptor {\n /** True when this call created the key; false for an existing grant. */\n created: boolean;\n /** Budget sync outcome when an existing grant's budget differed. */\n budgetSync?: HostedBudgetSyncStatus;\n}\n\n/** How a gateway key revocation went. */\nexport type HostedGatewayRevokeStatus =\n | 'revoked'\n | 'missing'\n | 'unsupported'\n | 'failed'\n | 'none';\n\n/** Result of {@link HostedInferenceProvisioner.revoke}. */\nexport interface RevokeHostedInferenceResult extends HostedInferenceDescriptor {\n revoked: boolean;\n /**\n * `revoked`: the gateway deleted the key; `missing`: it no longer existed\n * (already revoked); `unsupported`: no admin client is configured or the\n * gateway returned no key id; `failed`: the delete call failed. For\n * `unsupported` and `failed` SMRT has already stopped using the key and\n * records it in `pendingGatewayRevocations` (and the audit log) for a\n * later retry or a manual revocation on the gateway; `none`: nothing to\n * revoke.\n */\n gateway: HostedGatewayRevokeStatus;\n /** What the user's provider config became. */\n restored?: 'prior' | 'off' | 'kept';\n}\n\n/**\n * Budget sync outcome. `unsupported`: the grant has no gateway key id to\n * update (the stored budget stays, so a later sync retries).\n */\nexport type HostedBudgetSyncStatus = 'updated' | 'unchanged' | 'unsupported';\n\n/** Result of {@link HostedInferenceProvisioner.syncBudget}. */\nexport interface SyncHostedBudgetResult extends HostedInferenceDescriptor {\n sync: HostedBudgetSyncStatus | 'none';\n}\n\n/** What {@link HostedInferenceProvisioner.reconcileTenant} should converge to. */\nexport interface HostedReconcileTarget {\n /** Whether the tenant's plan grants `inference.hosted` now. */\n entitled: boolean;\n /** The plan's budget (required to provision; syncs active grants). */\n budget?: HostedInferenceBudget;\n /** Users to hold an active grant while entitled (idempotent). */\n users?: readonly string[];\n}\n\n/** Per-user outcome of a reconcile. */\nexport interface HostedReconcileResult {\n tenantId: string;\n provisioned: ProvisionHostedInferenceResult[];\n revoked: RevokeHostedInferenceResult[];\n synced: SyncHostedBudgetResult[];\n /** Pending gateway deletions retried, and what is still pending. */\n pending: Array<{ userId: string; remaining: number }>;\n failures: Array<{ userId: string; error: unknown }>;\n}\n\n/** The secret store calls provisioning uses (`SecretService` satisfies it). */\nexport interface HostedSecretStore {\n retrieve(name: string): Promise<{ value: string }>;\n store(\n name: string,\n value: string,\n options?: {\n category?: string;\n description?: string;\n auditDetails?: Record<string, unknown>;\n },\n ): Promise<unknown>;\n delete(\n name: string,\n options?: { auditDetails?: Record<string, unknown> },\n ): Promise<boolean>;\n list(options?: { category?: string }): Promise<Array<{ name: string }>>;\n}\n\n/** Options for {@link createHostedInferenceProvisioner}. */\nexport interface HostedInferenceProvisionerOptions {\n /** Database for `runOnce()` claims, the lifecycle lock and, without `secrets`, the secrets. */\n db: DatabaseInterface;\n /** Secret store; default `SecretService.create({ db })`. */\n secrets?: HostedSecretStore;\n /** Gateway config; default {@link getHostedGatewayConfig} (environment). */\n gateway?: HostedGatewayConfig;\n /** Admin client; default {@link createHostedGatewayAdmin}. Tests inject a fake. */\n admin?: HostedGatewayAdmin;\n /** Who acts, recorded as the audit user (default `system`). */\n actor?: string;\n /** Clock seam. */\n now?: () => Date;\n /** Alias/project nonce seam (tests). Default: random. */\n nonce?: () => string;\n /**\n * Current entitlement check, run inside the tenant lock before a new key\n * is minted (e.g. the host's `checkFeature('inference.hosted')`). With it,\n * a provision that arrives after a downgrade (a stale event, a late\n * retry) refuses with `HOSTED_NOT_ENTITLED` instead of activating.\n */\n entitled?: (tenantId: string) => Promise<boolean>;\n}\n\n/** Hosted provisioning bound to one database, store and gateway. */\nexport interface HostedInferenceProvisioner {\n provision(options: {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n }): Promise<ProvisionHostedInferenceResult>;\n /**\n * Revoke the user's active grant; with none active, retry any pending\n * gateway deletions. Idempotent.\n */\n revoke(options: {\n tenantId: string;\n userId: string;\n reason?: string;\n }): Promise<RevokeHostedInferenceResult>;\n syncBudget(options: {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n }): Promise<SyncHostedBudgetResult>;\n status(options: {\n tenantId: string;\n userId: string;\n }): Promise<HostedInferenceDescriptor>;\n /** Users of the tenant with an active hosted grant. */\n listActive(tenantId: string): Promise<string[]>;\n /** Users of the tenant with any hosted state (active, revoked, pending). */\n listKnown(tenantId: string): Promise<string[]>;\n revokeTenant(\n tenantId: string,\n options?: { reason?: string },\n ): Promise<RevokeHostedInferenceResult[]>;\n syncTenantBudget(\n tenantId: string,\n budget: HostedInferenceBudget,\n ): Promise<SyncHostedBudgetResult[]>;\n /**\n * Converge the tenant to `target` (idempotent): not entitled revokes every\n * active grant; entitled provisions `users` and syncs active budgets; both\n * retry pending gateway deletions. Run it on webhook redelivery or on a\n * schedule so a lost entitlement reaction is repaired.\n */\n reconcileTenant(\n tenantId: string,\n target: HostedReconcileTarget,\n ): Promise<HostedReconcileResult>;\n /**\n * Drop an owed gateway deletion after an operator revoked that key on the\n * gateway by hand (audited). Returns whether an entry was removed.\n */\n acknowledgeGatewayRevocation(options: {\n tenantId: string;\n userId: string;\n keyAlias: string;\n }): Promise<boolean>;\n}\n\nconst STATE_PREFIX = `${HOSTED_INFERENCE_SECRET_CATEGORY}:user:`;\nconst PROJECT_NAME = `${HOSTED_INFERENCE_SECRET_CATEGORY}:project`;\nconst RUN_ONCE_ACTOR_PROJECT = 'smrt-inference-hosted';\n\n/** Gateway project name of a tenant (also how a lost project is found). */\nfunction hostedProjectName(tenantId: string): string {\n return `smrt tenant ${tenantId}`;\n}\n\n/** Secret name of a user's hosted state. */\nexport function hostedInferenceStateSecretName(userId: string): string {\n return `${STATE_PREFIX}${userId}`;\n}\n\nconst ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:@-]{0,199}$/;\n\nfunction requireId(value: unknown, field: string): string {\n const id = text(value);\n if (!id || !ID_PATTERN.test(id)) {\n throw new HostedInferenceError(\n `${field} must be 1-200 characters of letters, digits and . _ : @ -`,\n 'HOSTED_INFERENCE_INVALID',\n { field },\n );\n }\n return id;\n}\n\n/** Validate a budget; fills the default window. */\nexport function normalizeHostedBudget(\n budget: HostedInferenceBudget | undefined,\n): Required<HostedInferenceBudget> {\n const limit = budget?.limitMicros;\n if (typeof limit !== 'number' || !Number.isSafeInteger(limit) || limit <= 0) {\n throw new HostedInferenceError(\n 'budget.limitMicros must be a positive integer (micro-dollars)',\n 'HOSTED_INFERENCE_INVALID',\n { field: 'budget.limitMicros' },\n );\n }\n const window = budget?.window ?? 'month';\n if (!(HOSTED_BUDGET_WINDOWS as readonly string[]).includes(window)) {\n throw new HostedInferenceError(\n `budget.window must be one of ${HOSTED_BUDGET_WINDOWS.join(', ')}`,\n 'HOSTED_INFERENCE_INVALID',\n { field: 'budget.window' },\n );\n }\n return { limitMicros: limit, window };\n}\n\n/** The gateway's budget shape for a hosted budget (USD at the boundary). */\nexport function toGatewayBudget(\n budget: Required<HostedInferenceBudget>,\n): AIAdminBudget {\n return {\n maxLimit: budget.limitMicros / 1_000_000,\n resetDuration: WINDOW_DURATION[budget.window],\n };\n}\n\nfunction sameBudget(\n a: Required<HostedInferenceBudget> | undefined,\n b: Required<HostedInferenceBudget>,\n): boolean {\n return !!a && a.limitMicros === b.limitMicros && a.window === b.window;\n}\n\n/** A gateway key value (`sk-...`), as the SDK's own redaction matches it. */\nconst KEY_VALUE_PATTERN = /\\bsk-[A-Za-z0-9_-]{8,}/;\n\n/**\n * A key handle safe to persist, audit and return: `null` when it is empty,\n * equals the key, or looks like a key value (a state written through an SDK\n * before 0.111.7 may hold LiteLLM's key as its id). Such a key then counts\n * as having no id: never updated or deleted by SMRT, owed by alias.\n */\nfunction safeKeyId(id: unknown, apiKey?: string): string | null {\n const handle = text(id);\n if (!handle || handle === apiKey || KEY_VALUE_PATTERN.test(handle)) {\n return null;\n }\n return handle;\n}\n\nfunction parseState(value: string): HostedInferenceState {\n let raw: unknown;\n try {\n raw = JSON.parse(value);\n } catch {\n throw new HostedInferenceError(\n 'Stored hosted inference state is not JSON',\n 'HOSTED_STATE_INVALID',\n );\n }\n const state = raw as Partial<HostedInferenceState>;\n if (\n !state ||\n typeof state !== 'object' ||\n state.version !== 1 ||\n (state.status !== 'active' && state.status !== 'revoked') ||\n typeof state.generation !== 'number' ||\n !Number.isSafeInteger(state.generation) ||\n typeof state.keyAlias !== 'string' ||\n (state.status === 'active' &&\n (typeof state.projectId !== 'string' || !state.budget))\n ) {\n throw new HostedInferenceError(\n 'Stored hosted inference state is invalid',\n 'HOSTED_STATE_INVALID',\n );\n }\n return {\n ...(state as HostedInferenceState),\n keyId: safeKeyId(state.keyId),\n ...(Array.isArray(state.pendingGatewayRevocations)\n ? {\n pendingGatewayRevocations: state.pendingGatewayRevocations.map(\n (entry) => ({ ...entry, keyId: safeKeyId(entry.keyId) }),\n ),\n }\n : {}),\n prior: state.prior ? parseInferenceProviderConfig(state.prior) : null,\n };\n}\n\nfunction isAbsentSecretError(error: unknown, name: string): boolean {\n if (!(error instanceof Error)) return false;\n return (\n error.message === `Secret '${name}' not found` ||\n error.message === 'Secret not found' ||\n error.message === 'Secret disabled' ||\n error.message === 'Secret expired'\n );\n}\n\nfunction describeState(\n tenantId: string,\n userId: string,\n state: HostedInferenceState | undefined,\n): HostedInferenceDescriptor {\n if (!state) return { tenantId, userId, status: 'none', generation: 0 };\n return {\n tenantId,\n userId,\n status: state.status,\n generation: state.generation,\n ...(state.provider ? { provider: state.provider } : {}),\n ...(state.projectId ? { projectId: state.projectId } : {}),\n keyId: state.keyId,\n ...(state.keyAlias ? { keyAlias: state.keyAlias } : {}),\n ...(state.budget ? { budget: state.budget } : {}),\n ...(state.pendingGatewayRevocations?.length\n ? { pendingGatewayRevocations: state.pendingGatewayRevocations }\n : {}),\n };\n}\n\n/** Error code of an admin call that passed its deadline. */\nconst GATEWAY_TIMEOUT_CODE = 'HOSTED_GATEWAY_TIMEOUT';\n\n/**\n * `call()` bounded by `ms`: past it the result rejects with a timeout error\n * (the call itself may still finish on the gateway; callers treat a timed\n * out create as a possibly created key).\n */\nasync function withDeadline<T>(\n call: () => Promise<T>,\n ms: number,\n operation: string,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n Object.assign(new Error(`Gateway ${operation} timed out`), {\n code: GATEWAY_TIMEOUT_CODE,\n }),\n ),\n ms,\n );\n });\n try {\n return await Promise.race([call(), deadline]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\nfunction isGatewayTimeout(error: unknown): boolean {\n return (\n !!error &&\n typeof error === 'object' &&\n (error as { code?: unknown }).code === GATEWAY_TIMEOUT_CODE\n );\n}\n\n/**\n * Whether a failed create may still have minted the key: anything but an\n * explicit refusal (credential, quota, missing model, refusal). Timeouts,\n * transport resets, 5xx and unclassified errors are ambiguous.\n */\nfunction mayHaveCreated(error: unknown): boolean {\n if (isGatewayTimeout(error)) return true;\n const failure = classifyInferenceFailure(error);\n return (\n failure !== 'credential' &&\n failure !== 'quota' &&\n failure !== 'missing-model' &&\n failure !== 'refusal'\n );\n}\n\n/** Name an admin-call failure without echoing the SDK's error text. */\nfunction gatewayFailure(\n operation: string,\n error: unknown,\n): HostedInferenceError {\n const code =\n error && typeof error === 'object' && 'code' in error\n ? String((error as { code: unknown }).code)\n : undefined;\n const name = error instanceof Error ? error.name : typeof error;\n return new HostedInferenceError(\n `Gateway ${operation} failed (${code ?? name}).`,\n 'HOSTED_GATEWAY_FAILED',\n { operation, ...(code ? { gatewayCode: code } : {}), errorName: name },\n );\n}\n\n/** The binding a hosted key stores: default plus per-tier models. */\nfunction hostedProviderConfig(\n gateway: HostedGatewayConfig,\n apiKey: string,\n keyAlias: string,\n): InferenceProviderConfig {\n const binding = (model: string | undefined): InferenceProviderBinding => ({\n provider: gateway.provider,\n baseUrl: gateway.baseUrl,\n apiKey,\n ...(model ? { model } : {}),\n });\n const tiers: Partial<Record<InferenceTier, InferenceProviderBinding>> = {};\n for (const [tier, model] of Object.entries(gateway.tiers ?? {})) {\n if (model) tiers[tier as InferenceTier] = binding(model);\n }\n return parseInferenceProviderConfig({\n version: 1,\n source: 'hosted',\n mode: 'hosted',\n gatewayKeyAlias: keyAlias,\n ...binding(gateway.model),\n ...(Object.keys(tiers).length > 0 ? { tiers } : {}),\n });\n}\n\n/** Models the key may use: explicit list, else the binding's models. */\nfunction keyModels(gateway: HostedGatewayConfig): string[] | undefined {\n if (gateway.models?.length) return gateway.models;\n const models = new Set<string>();\n if (gateway.model) models.add(gateway.model);\n for (const model of Object.values(gateway.tiers ?? {})) {\n if (model) models.add(model);\n }\n return models.size > 0 ? [...models] : undefined;\n}\n\n/** The config to restore on revoke: the prior one, or mode `off`. */\nfunction restoredConfig(prior: InferenceProviderConfig | null): {\n config: InferenceProviderConfig;\n restored: 'prior' | 'off';\n} {\n if (prior && prior.mode !== 'hosted' && prior.source !== 'hosted') {\n const hasBinding = INFERENCE_TIERS.some(\n (tier) => !!inferenceProviderBindingFor(prior, tier),\n );\n if (hasBinding || prior.mode) {\n return { config: prior, restored: 'prior' };\n }\n }\n return { config: { mode: 'off' }, restored: 'off' };\n}\n\nfunction defaultNonce(): string {\n return globalThis.crypto.randomUUID().replace(/-/g, '').slice(0, 10);\n}\n\n/**\n * Process-wide chain of hosted lifecycle operations. One at a time per\n * process: they are rare (webhooks, operators) and this bounds the pooled\n * connections an operation holds (the PostgreSQL lock transaction plus its\n * own writes) whatever the number of tenants changing at once.\n */\nlet lifecycleChain: Promise<unknown> = Promise.resolve();\n\n/**\n * Create a provisioner. One per process (or per request) is fine: lifecycle\n * operations run one at a time per process and, on PostgreSQL, hold a\n * per-tenant transaction-scoped advisory lock (cross-process), so a\n * tenant-wide revoke never misses an in-flight provision.\n */\nexport function createHostedInferenceProvisioner(\n options: HostedInferenceProvisionerOptions,\n): HostedInferenceProvisioner {\n const now = options.now ?? (() => new Date());\n const nonce = options.nonce ?? defaultNonce;\n const deadlineMs = (): number => {\n try {\n return gatewayConfig().adminTimeoutMs ?? HOSTED_GATEWAY_ADMIN_TIMEOUT_MS;\n } catch {\n return HOSTED_GATEWAY_ADMIN_TIMEOUT_MS;\n }\n };\n let secretsPromise: Promise<HostedSecretStore> | undefined;\n let adminPromise: Promise<HostedGatewayAdmin> | undefined;\n let tablesPromise: Promise<void> | undefined;\n\n const gatewayConfig = (): HostedGatewayConfig =>\n options.gateway\n ? parseHostedGatewayConfig(options.gateway)\n : getHostedGatewayConfig();\n\n const getSecrets = (): Promise<HostedSecretStore> => {\n if (options.secrets) return Promise.resolve(options.secrets);\n secretsPromise ??= SecretService.create({ db: options.db }).catch(\n (error: unknown) => {\n secretsPromise = undefined;\n throw error;\n },\n );\n return secretsPromise;\n };\n\n const getAdmin = (gateway: HostedGatewayConfig) => {\n if (options.admin) return Promise.resolve(options.admin);\n adminPromise ??= createHostedGatewayAdmin(gateway).catch(\n (error: unknown) => {\n adminPromise = undefined;\n throw error;\n },\n );\n return adminPromise;\n };\n\n /** The admin surface, or `undefined` when none is usable (revoke path). */\n const optionalAdmin = async (): Promise<HostedGatewayAdmin | undefined> => {\n try {\n return await getAdmin(gatewayConfig());\n } catch (error) {\n if (\n error instanceof HostedInferenceError &&\n (error.code === 'HOSTED_GATEWAY_NOT_CONFIGURED' ||\n error.code === 'HOSTED_GATEWAY_UNSUPPORTED')\n ) {\n return undefined;\n }\n throw error;\n }\n };\n\n const ensureTables = () => {\n tablesPromise ??= ensureSystemTables(options.db)\n .then(() => undefined)\n .catch((error: unknown) => {\n tablesPromise = undefined;\n throw error;\n });\n return tablesPromise;\n };\n\n const inTenant = <T>(tenantId: string, fn: () => Promise<T>) =>\n withTenant(\n { tenantId, ...(options.actor ? { userId: options.actor } : {}) },\n fn,\n );\n\n /**\n * Run one lifecycle operation for a tenant: after every earlier operation\n * of this process (the chain), and on PostgreSQL under a per-tenant\n * transaction-scoped advisory lock held for the whole operation, so\n * operations of other processes on the same tenant wait (the operation's\n * own writes use other pooled connections: the pool needs at least three).\n * Single-connection adapters (SQLite) serve one process, which the chain\n * covers. Callers inside a locked operation use the `*Locked` helpers,\n * never these public methods (the chain is not re-entrant).\n */\n const withTenantLock = <T>(\n tenantId: string,\n fn: () => Promise<T>,\n ): Promise<T> => {\n const key = JSON.stringify(['smrt.inference.hosted', tenantId]);\n const run = async () => {\n if (isPostgresDatabase(options.db) && options.db.transaction) {\n return options.db.transaction(async (tx) => {\n await tx.query(\n 'SELECT pg_advisory_xact_lock(hashtextextended($1::text, 0))',\n key,\n );\n return fn();\n });\n }\n return fn();\n };\n const result = lifecycleChain.then(run, run);\n lifecycleChain = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n };\n\n const read = async (\n secrets: HostedSecretStore,\n name: string,\n ): Promise<string | undefined> => {\n try {\n return (await secrets.retrieve(name)).value;\n } catch (error) {\n if (isAbsentSecretError(error, name)) return undefined;\n throw error;\n }\n };\n\n const readState = async (secrets: HostedSecretStore, userId: string) => {\n const value = await read(secrets, hostedInferenceStateSecretName(userId));\n return value === undefined ? undefined : parseState(value);\n };\n\n const readUserConfig = async (\n secrets: HostedSecretStore,\n userId: string,\n ): Promise<InferenceProviderConfig | undefined> => {\n const value = await read(\n secrets,\n inferenceProviderConfigSecretName({ userId }),\n );\n return value === undefined\n ? undefined\n : parseInferenceProviderConfig(value);\n };\n\n const writeState = (\n secrets: HostedSecretStore,\n userId: string,\n state: HostedInferenceState,\n auditDetails: Record<string, unknown>,\n ) =>\n secrets.store(\n hostedInferenceStateSecretName(userId),\n JSON.stringify(state),\n {\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n description: 'Hosted inference provisioning state',\n auditDetails,\n },\n );\n\n const writeUserConfig = (\n secrets: HostedSecretStore,\n userId: string,\n config: InferenceProviderConfig,\n auditDetails: Record<string, unknown>,\n ) =>\n secrets.store(\n inferenceProviderConfigSecretName({ userId }),\n JSON.stringify(parseInferenceProviderConfig(config)),\n {\n category: INFERENCE_PROVIDER_CONFIG_SECRET_TYPE,\n description: 'Inference provider config (user)',\n auditDetails,\n },\n );\n\n /** Whether the user's current config is this grant's hosted binding. */\n const isOurs = (\n config: InferenceProviderConfig | undefined,\n keyAlias: string,\n ) =>\n !!keyAlias &&\n config?.source === 'hosted' &&\n config.gatewayKeyAlias === keyAlias;\n\n /**\n * Try to delete a key on the gateway. Never throws: the outcome says\n * whether SMRT still owes the deletion (`missing`, the SDK's `false`, is\n * an already revoked key: nothing is owed).\n */\n const deleteGatewayKey = async (\n admin: HostedGatewayAdmin | undefined,\n keyId: string | null,\n ): Promise<'revoked' | 'missing' | 'unsupported' | 'failed'> => {\n if (!keyId || !admin) return 'unsupported';\n try {\n return (await withDeadline(\n () => admin.deleteVirtualKey(keyId),\n deadlineMs(),\n 'deleteVirtualKey',\n ))\n ? 'revoked'\n : 'missing';\n } catch {\n return 'failed';\n }\n };\n\n /**\n * A project this provisioner created for the tenant earlier but never\n * recorded (a crash or failed write between create and persist): the\n * gateway's projects for the tenant carrying SMRT's name (and, on LiteLLM,\n * id prefix). The lowest id wins so every process adopts the same one.\n * A failed lookup throws: creating blind could make a second project.\n */\n const findTenantProject = async (\n tenantId: string,\n gateway: HostedGatewayConfig,\n admin: HostedGatewayAdmin,\n ): Promise<string | undefined> => {\n let projects: AIAdminProject[];\n try {\n projects = await withDeadline(\n () => admin.listProjects({ tenantId }),\n deadlineMs(),\n 'listProjects',\n );\n } catch (error) {\n throw gatewayFailure('listProjects', error);\n }\n const ids = (Array.isArray(projects) ? projects : [])\n .filter(\n (project) =>\n project?.tenantId === tenantId &&\n project.name === hostedProjectName(tenantId) &&\n (gateway.provider !== 'litellm' ||\n project.id?.startsWith(`smrt-${tenantId}-`)),\n )\n .map((project) => text(project.id))\n .filter((id): id is string => !!id)\n .sort();\n return ids[0];\n };\n\n /**\n * The tenant's gateway project: the stored record, else one found on the\n * gateway ({@link findTenantProject}), else created once (runOnce).\n */\n const ensureProject = async (\n tenantId: string,\n gateway: HostedGatewayConfig,\n admin: HostedGatewayAdmin,\n secrets: HostedSecretStore,\n ): Promise<string> => {\n const stored = await read(secrets, PROJECT_NAME);\n if (stored !== undefined) {\n try {\n const parsed = JSON.parse(stored) as {\n projectId?: unknown;\n provider?: unknown;\n };\n if (\n typeof parsed.projectId === 'string' &&\n parsed.provider === gateway.provider\n ) {\n return parsed.projectId;\n }\n } catch {\n // Unreadable project record: create (or replay) below.\n }\n }\n const result = await runOnce(\n {\n db: options.db,\n tenantId,\n actor: RUN_ONCE_ACTOR_PROJECT,\n token: 'smrt-inference-hosted:project',\n content: { provider: gateway.provider, baseUrl: gateway.baseUrl },\n },\n async () => {\n const found = await findTenantProject(tenantId, gateway, admin);\n let projectId = found;\n if (!projectId) {\n let project: AIAdminProject;\n try {\n project = await withDeadline(\n () =>\n admin.createProject({\n // LiteLLM requires an id, unique per attempt so a retry after\n // a failed attempt never collides with the team it left.\n id: `smrt-${tenantId}-${nonce()}`,\n name: hostedProjectName(tenantId),\n tenantId,\n metadata: { smrt: HOSTED_INFERENCE_FEATURE_KEY, tenantId },\n }),\n deadlineMs(),\n 'createProject',\n );\n } catch (error) {\n throw gatewayFailure('createProject', error);\n }\n projectId = text(project?.id);\n }\n if (!projectId) {\n throw new HostedInferenceError(\n 'Gateway createProject returned no project id.',\n 'HOSTED_GATEWAY_FAILED',\n { operation: 'createProject' },\n );\n }\n await secrets.store(\n PROJECT_NAME,\n JSON.stringify({\n projectId,\n provider: gateway.provider,\n createdAt: now().toISOString(),\n }),\n {\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n description: 'Hosted inference gateway project',\n auditDetails: {\n operation: 'inference.hosted.project',\n projectId,\n provider: gateway.provider,\n ...(found ? { adopted: true } : {}),\n },\n },\n );\n return { projectId };\n },\n );\n return result.projectId;\n };\n\n /**\n * Retry owed gateway deletions. Returns the state to store (the same\n * object when nothing settled) and the entries that settled, with the\n * gateway's answer (`revoked`, or `missing`: already gone).\n */\n const retryPending = async (\n state: HostedInferenceState,\n ): Promise<{\n state: HostedInferenceState;\n settled: Array<{\n keyId: string;\n keyAlias: string;\n gateway: 'revoked' | 'missing';\n }>;\n }> => {\n const pending = state.pendingGatewayRevocations ?? [];\n // Entries without a key id cannot be deleted by SMRT: skip the client.\n if (!pending.some((entry) => entry.keyId)) return { state, settled: [] };\n const admin = await optionalAdmin();\n const remaining: HostedPendingGatewayRevocation[] = [];\n const settled: Array<{\n keyId: string;\n keyAlias: string;\n gateway: 'revoked' | 'missing';\n }> = [];\n for (const entry of pending) {\n const outcome = await deleteGatewayKey(admin, entry.keyId);\n if (outcome === 'revoked' || outcome === 'missing') {\n settled.push({\n keyId: entry.keyId as string,\n keyAlias: entry.keyAlias,\n gateway: outcome,\n });\n } else {\n remaining.push(entry);\n }\n }\n if (settled.length === 0) return { state, settled };\n return {\n state: { ...state, pendingGatewayRevocations: remaining },\n settled,\n };\n };\n\n /**\n * Revoke an active grant (caller holds the lifecycle lock). Local access\n * ends whatever the gateway answers: the config is restored and the state\n * retired first-class; a deletion the gateway could not perform is kept in\n * `pendingGatewayRevocations` and audited.\n */\n const revokeLocked = async (\n tenantId: string,\n userId: string,\n secrets: HostedSecretStore,\n state: HostedInferenceState,\n reason: string,\n restore: boolean,\n optOut = false,\n ): Promise<RevokeHostedInferenceResult> => {\n const since = now().toISOString();\n const audit = {\n operation: 'inference.hosted.revoke',\n reason,\n keyAlias: state.keyAlias,\n ...(state.keyId ? { keyId: state.keyId } : {}),\n };\n // 1. End local use first, whatever the gateway does next.\n let restored: RevokeHostedInferenceResult['restored'] = 'kept';\n if (restore) {\n const current = await readUserConfig(secrets, userId);\n if (isOurs(current, state.keyAlias)) {\n const next = restoredConfig(state.prior);\n restored = next.restored;\n await writeUserConfig(secrets, userId, next.config, {\n ...audit,\n restored,\n });\n }\n }\n const owedEntry: HostedPendingGatewayRevocation = {\n keyId: state.keyId,\n keyAlias: state.keyAlias,\n reason: 'failed',\n since,\n };\n const retired: HostedInferenceState = {\n ...state,\n status: 'revoked',\n generation: state.generation + 1,\n revokedAt: since,\n prior: null,\n ...(optOut ? { optedOut: true } : {}),\n // Owed until the gateway confirms the deletion below.\n pendingGatewayRevocations: [\n ...(state.pendingGatewayRevocations ?? []),\n owedEntry,\n ],\n };\n await writeState(secrets, userId, retired, {\n ...audit,\n restored,\n gateway: 'pending',\n });\n // 2. Then the gateway (bounded by the admin deadline).\n const gateway = await deleteGatewayKey(await optionalAdmin(), state.keyId);\n const owed = gateway === 'unsupported' || gateway === 'failed';\n const next: HostedInferenceState = {\n ...retired,\n pendingGatewayRevocations: [\n ...(state.pendingGatewayRevocations ?? []),\n ...(owed ? [{ ...owedEntry, reason: gateway }] : []),\n ],\n };\n await writeState(secrets, userId, next, {\n ...audit,\n restored,\n gateway,\n ...(owed ? { manualGatewayRevocation: true } : {}),\n });\n return {\n ...describeState(tenantId, userId, next),\n revoked: true,\n gateway,\n restored,\n };\n };\n\n const provisionLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n gateway: HostedGatewayConfig,\n secrets: HostedSecretStore,\n ): Promise<ProvisionHostedInferenceResult> => {\n let state = await readState(secrets, userId);\n if (state?.status === 'active') {\n const current = await readUserConfig(secrets, userId);\n if (isOurs(current, state.keyAlias)) {\n const sync = sameBudget(state.budget, budget)\n ? undefined\n : await syncLocked(tenantId, userId, budget, secrets);\n const fresh = (await readState(secrets, userId)) ?? state;\n return {\n ...describeState(tenantId, userId, fresh),\n created: false,\n ...(sync && sync.sync !== 'none' ? { budgetSync: sync.sync } : {}),\n };\n }\n // The user replaced the hosted binding since: retire that key\n // (keeping their new config) and provision afresh.\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'user-replaced',\n false,\n true,\n );\n state = await readState(secrets, userId);\n }\n if (options.entitled && !(await options.entitled(tenantId))) {\n throw new HostedInferenceError(\n `Tenant ${tenantId} is not entitled to ${HOSTED_INFERENCE_FEATURE_KEY}; nothing was provisioned.`,\n 'HOSTED_NOT_ENTITLED',\n { tenantId, userId },\n );\n }\n const generation = state?.generation ?? 0;\n const admin = await getAdmin(gateway);\n const projectId = await ensureProject(tenantId, gateway, admin, secrets);\n let rollback: (() => Promise<unknown>) | undefined;\n const provisioned = await runOnce(\n {\n db: options.db,\n tenantId,\n actor: userId,\n token: 'smrt-inference-hosted:provision',\n content: { userId, generation },\n },\n async () => {\n const current = await readUserConfig(secrets, userId);\n // Keep the user's own config to restore on revoke; a leftover\n // hosted binding is never a prior.\n const prior = current && current.source !== 'hosted' ? current : null;\n // Unique per attempt: a failed attempt's key (whose delete may be\n // still owed) never blocks the retry's alias.\n const keyAlias = `smrt-${tenantId}-${userId}-g${generation}-${nonce()}`;\n const models = keyModels(gateway);\n const request: CreateAIVirtualKeyOptions = {\n name: keyAlias,\n description: `smrt hosted inference for user ${userId}`,\n projectId,\n tenantId,\n userId,\n budget: toGatewayBudget(budget),\n ...(models ? { models } : {}),\n ...(gateway.keyDuration ? { duration: gateway.keyDuration } : {}),\n ...(gateway.provider === 'bifrost' ? { keyIds: ['*'] } : {}),\n metadata: {\n smrt: HOSTED_INFERENCE_FEATURE_KEY,\n tenantId,\n userId,\n generation,\n },\n };\n let key: AIVirtualKey | undefined;\n let createError: unknown;\n try {\n key = await withDeadline(\n () => admin.createVirtualKey(request),\n deadlineMs(),\n 'createVirtualKey',\n );\n } catch (error) {\n createError = error;\n }\n const apiKey = text(key?.key);\n // The persisted handle must never be the key itself (an SDK before\n // 0.111.7 could echo LiteLLM's key as its id).\n const keyId = safeKeyId(key?.id, apiKey);\n /**\n * After the claim rolled back (a delete inside its transaction would\n * wait on it on single-connection adapters): delete the new key or\n * record it as owed, and put the previous state back so the user's\n * prior config survives for the retry.\n */\n const scheduleRollback = () => {\n const previous = state;\n rollback = async () => {\n const outcome = await deleteGatewayKey(admin, keyId);\n const owed = outcome === 'unsupported' || outcome === 'failed';\n const audit = {\n operation: 'inference.hosted.provision-rollback',\n keyAlias,\n gateway: outcome,\n ...(keyId ? { keyId } : {}),\n ...(owed ? { manualGatewayRevocation: true } : {}),\n };\n const pending = [\n ...(previous?.pendingGatewayRevocations ?? []),\n ...(owed\n ? [\n {\n keyId,\n keyAlias,\n reason: 'orphaned',\n since: now().toISOString(),\n } satisfies HostedPendingGatewayRevocation,\n ]\n : []),\n ];\n if (previous) {\n await writeState(\n secrets,\n userId,\n { ...previous, pendingGatewayRevocations: pending },\n audit,\n );\n } else if (owed) {\n await writeState(\n secrets,\n userId,\n {\n version: 1,\n status: 'revoked',\n generation,\n keyId: null,\n keyAlias: '',\n prior: null,\n pendingGatewayRevocations: pending,\n },\n audit,\n );\n } else {\n await secrets.delete(hostedInferenceStateSecretName(userId), {\n auditDetails: audit,\n });\n }\n };\n };\n if (createError !== undefined) {\n // An ambiguous failure may still mint the key on the gateway:\n // record its alias as owed. An explicit refusal created nothing.\n if (mayHaveCreated(createError)) scheduleRollback();\n throw gatewayFailure('createVirtualKey', createError);\n }\n if (!apiKey) {\n scheduleRollback();\n throw new HostedInferenceError(\n 'Gateway createVirtualKey returned no key value.',\n 'HOSTED_GATEWAY_FAILED',\n { operation: 'createVirtualKey' },\n );\n }\n const next: HostedInferenceState = {\n version: 1,\n status: 'active',\n generation,\n provider: gateway.provider,\n projectId,\n keyId,\n keyAlias,\n budget,\n provisionedAt: now().toISOString(),\n prior,\n ...(state?.pendingGatewayRevocations?.length\n ? { pendingGatewayRevocations: state.pendingGatewayRevocations }\n : {}),\n };\n const audit = {\n operation: 'inference.hosted.provision',\n keyAlias,\n projectId,\n provider: gateway.provider,\n ...(keyId ? { keyId } : {}),\n };\n try {\n await writeState(secrets, userId, next, audit);\n await writeUserConfig(\n secrets,\n userId,\n hostedProviderConfig(gateway, apiKey, keyAlias),\n audit,\n );\n } catch (error) {\n scheduleRollback();\n throw error;\n }\n const created: ProvisionHostedInferenceResult = {\n ...describeState(tenantId, userId, next),\n created: true,\n };\n return created;\n },\n ).catch(async (error: unknown) => {\n if (rollback) await rollback().catch(() => undefined);\n throw error;\n });\n // A replayed claim must describe what is stored. It does not when the\n // state was removed out of band (claims outlive it for the retention\n // window): refuse rather than report a grant that does not exist.\n const stored = await readUserConfig(secrets, userId);\n if (!isOurs(stored, provisioned.keyAlias ?? '')) {\n throw new HostedInferenceError(\n 'Hosted inference state for this user was removed outside provisioning; revoke the gateway key named in the audit log and provision again after the run-once claim expires.',\n 'HOSTED_STATE_INVALID',\n { tenantId, userId, keyAlias: provisioned.keyAlias },\n );\n }\n return provisioned;\n };\n\n /**\n * Sync one grant's gateway budget (lock held). Unchanged budgets are not\n * re-sent (a gateway update may restart the spend window), except while\n * an earlier update's outcome is uncertain (`budgetSyncUncertainAt`: it\n * failed or timed out and may still land): then the stored budget is\n * re-applied, until a confirmed update at least\n * {@link HOSTED_BUDGET_UNCERTAIN_WINDOW_MS} after the uncertain one.\n */\n const syncLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n secrets: HostedSecretStore,\n ): Promise<SyncHostedBudgetResult> => {\n const state = await readState(secrets, userId);\n if (state?.status !== 'active') {\n return { ...describeState(tenantId, userId, state), sync: 'none' };\n }\n const uncertainAt = state.budgetSyncUncertainAt;\n if (!uncertainAt && sameBudget(state.budget, budget)) {\n return { ...describeState(tenantId, userId, state), sync: 'unchanged' };\n }\n if (!state.keyId) {\n // No handle to update: the recorded state keeps the old budget.\n return { ...describeState(tenantId, userId, state), sync: 'unsupported' };\n }\n const admin = await getAdmin(gatewayConfig());\n try {\n const keyId = state.keyId;\n // Budget only: models and metadata are not updatable on every\n // gateway (ADMIN_UNSUPPORTED_FIELD), and a plan change is a budget.\n await withDeadline(\n () =>\n admin.updateVirtualKey(keyId, { budget: toGatewayBudget(budget) }),\n deadlineMs(),\n 'updateVirtualKey',\n );\n } catch (error) {\n // The update may still land later: re-apply on the next sync.\n await writeState(\n secrets,\n userId,\n { ...state, budgetSyncUncertainAt: now().toISOString() },\n {\n operation: 'inference.hosted.budget-uncertain',\n keyAlias: state.keyAlias,\n keyId: state.keyId,\n },\n );\n throw gatewayFailure('updateVirtualKey', error);\n }\n const settled =\n !uncertainAt ||\n now().getTime() - Date.parse(uncertainAt) >=\n HOSTED_BUDGET_UNCERTAIN_WINDOW_MS;\n const { budgetSyncUncertainAt: _settled, ...rest } = state;\n const next: HostedInferenceState = {\n ...(settled ? rest : state),\n budget,\n };\n if (sameBudget(state.budget, budget) && !(settled && uncertainAt)) {\n return { ...describeState(tenantId, userId, next), sync: 'updated' };\n }\n await writeState(secrets, userId, next, {\n operation: 'inference.hosted.budget',\n keyAlias: state.keyAlias,\n keyId: state.keyId,\n limitMicros: budget.limitMicros,\n window: budget.window,\n });\n return { ...describeState(tenantId, userId, next), sync: 'updated' };\n };\n\n const listStates = async (tenantId: string) => {\n const secrets = await getSecrets();\n return inTenant(tenantId, async () => {\n const rows = await secrets.list({\n category: HOSTED_INFERENCE_SECRET_CATEGORY,\n });\n const states: Array<{ userId: string; state: HostedInferenceState }> = [];\n for (const row of rows) {\n if (!row.name.startsWith(STATE_PREFIX)) continue;\n const userId = row.name.slice(STATE_PREFIX.length);\n const state = await readState(secrets, userId);\n if (state) states.push({ userId, state });\n }\n return states.sort((a, b) => a.userId.localeCompare(b.userId));\n });\n };\n\n /** Retry a user's owed gateway deletions and persist what changed. */\n const retryPendingLocked = async (\n userId: string,\n secrets: HostedSecretStore,\n ): Promise<HostedInferenceState | undefined> => {\n const state = await readState(secrets, userId);\n if (!state?.pendingGatewayRevocations?.length) return state;\n const { state: retried, settled } = await retryPending(state);\n if (settled.length === 0) return state;\n // Each settled entry closes an earlier `manualGatewayRevocation` audit\n // record for the same alias and key id.\n await writeState(secrets, userId, retried, {\n operation: 'inference.hosted.revoke-retry',\n settled,\n remaining: retried.pendingGatewayRevocations?.length ?? 0,\n });\n return retried;\n };\n\n /** Revoke one user (lock held): active grant, else owed deletions only. */\n const revokeUserLocked = async (\n tenantId: string,\n userId: string,\n secrets: HostedSecretStore,\n reason: string,\n ): Promise<RevokeHostedInferenceResult> => {\n const state = await readState(secrets, userId);\n if (state?.status !== 'active') {\n const retried = await retryPendingLocked(userId, secrets);\n return {\n ...describeState(tenantId, userId, retried),\n revoked: false,\n gateway: 'none',\n };\n }\n return revokeLocked(tenantId, userId, secrets, state, reason, true);\n };\n\n /**\n * Converge one entitled user (lock held). A user who replaced the hosted\n * binding with their own config keeps it: the stale key is retired and\n * the user marked opted out, so later reconciles leave them alone.\n */\n const reconcileUserLocked = async (\n tenantId: string,\n userId: string,\n budget: Required<HostedInferenceBudget>,\n gateway: HostedGatewayConfig,\n secrets: HostedSecretStore,\n result: HostedReconcileResult,\n ): Promise<void> => {\n const state = await readState(secrets, userId);\n if (state?.status === 'revoked' && state.optedOut) return;\n if (state?.status === 'active') {\n const current = await readUserConfig(secrets, userId);\n if (!isOurs(current, state.keyAlias)) {\n result.revoked.push(\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'user-replaced',\n false,\n true,\n ),\n );\n return;\n }\n result.synced.push(await syncLocked(tenantId, userId, budget, secrets));\n return;\n }\n result.provisioned.push(\n await provisionLocked(tenantId, userId, budget, gateway, secrets),\n );\n };\n\n /** Common entry: prepare, lock the tenant, set its context. */\n const locked = async <T>(\n tenantId: string,\n fn: (secrets: HostedSecretStore) => Promise<T>,\n invalidate?: { userId?: string },\n ): Promise<T> => {\n await ensureTables();\n const secrets = await getSecrets();\n try {\n return await withTenantLock(tenantId, () =>\n inTenant(tenantId, () => fn(secrets)),\n );\n } finally {\n if (invalidate) {\n invalidateUserInferenceResolvers({\n tenantId,\n ...(invalidate.userId ? { userId: invalidate.userId } : {}),\n });\n }\n }\n };\n\n const provisioner: HostedInferenceProvisioner = {\n async provision(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const budget = normalizeHostedBudget(input.budget);\n const gateway = gatewayConfig();\n return locked(\n tenantId,\n (secrets) =>\n provisionLocked(tenantId, userId, budget, gateway, secrets),\n { userId },\n );\n },\n\n async revoke(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n return locked(\n tenantId,\n (secrets) =>\n revokeUserLocked(\n tenantId,\n userId,\n secrets,\n text(input.reason) ?? 'revoke',\n ),\n { userId },\n );\n },\n\n async syncBudget(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const budget = normalizeHostedBudget(input.budget);\n return locked(tenantId, (secrets) =>\n syncLocked(tenantId, userId, budget, secrets),\n );\n },\n\n async status(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const secrets = await getSecrets();\n return inTenant(tenantId, async () =>\n describeState(tenantId, userId, await readState(secrets, userId)),\n );\n },\n\n async listActive(tenantIdInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return (await listStates(tenantId))\n .filter(({ state }) => state.status === 'active')\n .map(({ userId }) => userId);\n },\n\n async listKnown(tenantIdInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return (await listStates(tenantId)).map(({ userId }) => userId);\n },\n\n async revokeTenant(tenantIdInput, revokeOptions = {}) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n return locked(\n tenantId,\n async (secrets) => {\n const results: RevokeHostedInferenceResult[] = [];\n for (const { userId, state } of await listStates(tenantId)) {\n if (state.status !== 'active') continue;\n results.push(\n await revokeUserLocked(\n tenantId,\n userId,\n secrets,\n text(revokeOptions.reason) ?? 'revoke',\n ),\n );\n }\n return results;\n },\n {},\n );\n },\n\n async syncTenantBudget(tenantIdInput, budgetInput) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n const budget = normalizeHostedBudget(budgetInput);\n return locked(tenantId, async (secrets) => {\n const results: SyncHostedBudgetResult[] = [];\n for (const { userId, state } of await listStates(tenantId)) {\n if (state.status !== 'active') continue;\n results.push(await syncLocked(tenantId, userId, budget, secrets));\n }\n return results;\n });\n },\n\n async acknowledgeGatewayRevocation(input) {\n const tenantId = requireId(input.tenantId, 'tenantId');\n const userId = requireId(input.userId, 'userId');\n const keyAlias = text(input.keyAlias);\n if (!keyAlias) {\n throw new HostedInferenceError(\n 'keyAlias is required',\n 'HOSTED_INFERENCE_INVALID',\n { field: 'keyAlias' },\n );\n }\n return locked(tenantId, async (secrets) => {\n const state = await readState(secrets, userId);\n const pending = state?.pendingGatewayRevocations ?? [];\n const remaining = pending.filter(\n (entry) => entry.keyAlias !== keyAlias,\n );\n if (!state || remaining.length === pending.length) return false;\n await writeState(\n secrets,\n userId,\n { ...state, pendingGatewayRevocations: remaining },\n {\n operation: 'inference.hosted.revoke-acknowledged',\n keyAlias,\n },\n );\n return true;\n });\n },\n\n async reconcileTenant(tenantIdInput, target) {\n const tenantId = requireId(tenantIdInput, 'tenantId');\n const budget = target.budget\n ? normalizeHostedBudget(target.budget)\n : undefined;\n const users = (target.users ?? []).map((userId) =>\n requireId(userId, 'userId'),\n );\n if (target.entitled && users.length > 0 && !budget) {\n throw new HostedInferenceError(\n 'reconcileTenant needs a budget to provision users.',\n 'HOSTED_INFERENCE_INVALID',\n { tenantId },\n );\n }\n const gateway =\n target.entitled && users.length > 0 ? gatewayConfig() : undefined;\n return locked(\n tenantId,\n async (secrets) => {\n const result: HostedReconcileResult = {\n tenantId,\n provisioned: [],\n revoked: [],\n synced: [],\n pending: [],\n failures: [],\n };\n const attempt = async (userId: string, fn: () => Promise<void>) => {\n try {\n await fn();\n } catch (error) {\n result.failures.push({ userId, error });\n }\n };\n // Enumerated under the tenant lock: no provision is in flight.\n const states = await listStates(tenantId);\n // A stale \"entitled\" target (an old snapshot processed after a\n // downgrade) loses to the host's current answer, read under the\n // lock.\n const entitled =\n target.entitled &&\n (!options.entitled || (await options.entitled(tenantId)));\n if (!entitled) {\n for (const { userId, state } of states) {\n if (state.status !== 'active') continue;\n await attempt(userId, async () => {\n result.revoked.push(\n await revokeLocked(\n tenantId,\n userId,\n secrets,\n state,\n 'entitlement-lost',\n true,\n ),\n );\n });\n }\n } else {\n const wanted = new Set(users);\n for (const userId of wanted) {\n await attempt(userId, () =>\n reconcileUserLocked(\n tenantId,\n userId,\n budget as Required<HostedInferenceBudget>,\n gateway as HostedGatewayConfig,\n secrets,\n result,\n ),\n );\n }\n if (budget) {\n for (const { userId, state } of states) {\n if (wanted.has(userId) || state.status !== 'active') continue;\n await attempt(userId, async () => {\n result.synced.push(\n await syncLocked(tenantId, userId, budget, secrets),\n );\n });\n }\n }\n }\n // Owed gateway deletions for every user with state (an active\n // grant's pending entries are old keys, never the current one).\n for (const { userId } of await listStates(tenantId)) {\n await attempt(userId, async () => {\n const state = await retryPendingLocked(userId, secrets);\n const remaining = state?.pendingGatewayRevocations?.length ?? 0;\n if (remaining > 0) result.pending.push({ userId, remaining });\n });\n }\n return result;\n },\n {},\n );\n },\n };\n return provisioner;\n}\n\n/**\n * Provision hosted inference for one user (#4759): create the tenant's\n * gateway project on first use, mint a per-user virtual key with `budget`,\n * store it as the user's `inference.provider-config` (`source: 'hosted'`,\n * `mode: 'hosted'`), keeping the prior config for revoke. Idempotent per\n * `(tenantId, userId)`: an active grant is returned (its budget synced when\n * it differs) and concurrent calls create one key.\n */\nexport function provisionHostedInference(\n options: HostedInferenceProvisionerOptions & {\n tenantId: string;\n userId: string;\n budget: HostedInferenceBudget;\n },\n): Promise<ProvisionHostedInferenceResult> {\n const { tenantId, userId, budget, ...deps } = options;\n return createHostedInferenceProvisioner(deps).provision({\n tenantId,\n userId,\n budget,\n });\n}\n\n/**\n * Revoke a user's hosted inference (downgrade, deletion): stop using the key\n * (restore the user's prior provider config, or mode `off`), retire the\n * state, and delete the gateway key (a failed delete is kept as an owed\n * deletion, audited, and retried by revoke or reconcile). Idempotent.\n */\nexport function revokeHostedInference(\n options: HostedInferenceProvisionerOptions & {\n tenantId: string;\n userId: string;\n reason?: string;\n },\n): Promise<RevokeHostedInferenceResult> {\n const { tenantId, userId, reason, ...deps } = options;\n return createHostedInferenceProvisioner(deps).revoke({\n tenantId,\n userId,\n ...(reason ? { reason } : {}),\n });\n}\n\n/** Read a hosted budget from an `inference.hosted` grant's `metadata.budget`. */\nexport function hostedBudgetFromGrantMetadata(\n metadata: Record<string, unknown> | undefined,\n): HostedInferenceBudget | undefined {\n const budget = metadata?.budget;\n if (!budget || typeof budget !== 'object') return undefined;\n const { limitMicros, window } = budget as Record<string, unknown>;\n if (typeof limitMicros !== 'number') return undefined;\n return {\n limitMicros,\n ...(typeof window === 'string'\n ? { window: window as HostedBudgetWindow }\n : {}),\n };\n}\n\n/** Structural mirror of smrt-subscriptions' `EntitlementSnapshot` (grants). */\nexport interface HostedEntitlementSnapshot {\n grants: ReadonlyArray<{\n featureKey: string;\n enabled?: boolean;\n metadata?: Record<string, unknown>;\n }>;\n}\n\n/** Structural mirror of smrt-subscriptions' `EntitlementChange`. */\nexport interface HostedEntitlementChange {\n tenantId: string;\n gained: readonly string[];\n lost: readonly string[];\n changed: readonly string[];\n after: HostedEntitlementSnapshot;\n}\n\n/** Options for the entitlement listener and reconciler. */\nexport interface HostedInferenceEntitlementListenerOptions {\n provisioner: HostedInferenceProvisioner;\n /**\n * Users who should hold a grant while the tenant is entitled (the\n * tenant's members; membership is the host's). Users joining later are\n * provisioned by the host calling `provision()` directly.\n */\n users(tenantId: string): Promise<readonly string[]>;\n /**\n * The budget for the tenant's plan. Default: the `inference.hosted`\n * grant's `metadata.budget` ({@link hostedBudgetFromGrantMetadata}), else\n * `defaultBudget`.\n */\n budgetFor?(\n tenantId: string,\n snapshot: HostedEntitlementSnapshot,\n ): HostedInferenceBudget | undefined;\n defaultBudget?: HostedInferenceBudget;\n}\n\nfunction hostedGrant(snapshot: HostedEntitlementSnapshot) {\n return snapshot.grants.find(\n (grant) =>\n grant.featureKey === HOSTED_INFERENCE_FEATURE_KEY &&\n grant.enabled !== false,\n );\n}\n\nfunction throwReconcileFailures(result: HostedReconcileResult): void {\n if (result.failures.length === 0) return;\n throw new HostedInferenceError(\n `Hosted inference failed for ${result.failures.length} user(s) of tenant ${result.tenantId}.`,\n 'HOSTED_GATEWAY_FAILED',\n {\n tenantId: result.tenantId,\n users: result.failures.map((failure) => failure.userId),\n codes: result.failures.map((failure) =>\n failure.error instanceof SmrtError\n ? failure.error.code\n : failure.error instanceof Error\n ? failure.error.name\n : 'unknown',\n ),\n },\n );\n}\n\n/**\n * Reconcile a tenant's hosted grants with its current entitlement snapshot\n * (smrt-subscriptions' `snapshotEntitlements()`): entitled provisions\n * `users()` and syncs budgets, not entitled revokes every grant, both retry\n * owed gateway deletions. Idempotent: run it on webhook redelivery (a\n * replayed write diffs to \"no change\") and on a schedule, so a reaction\n * lost to a crash or a failed listener is repaired. Throws one error naming\n * the users that failed, after trying all of them.\n */\nexport function createHostedInferenceReconciler(\n options: HostedInferenceEntitlementListenerOptions,\n): (\n tenantId: string,\n snapshot: HostedEntitlementSnapshot,\n) => Promise<HostedReconcileResult> {\n return async (tenantId, snapshot) => {\n const grant = hostedGrant(snapshot);\n if (!grant) {\n const result = await options.provisioner.reconcileTenant(tenantId, {\n entitled: false,\n });\n throwReconcileFailures(result);\n return result;\n }\n const budget =\n options.budgetFor?.(tenantId, snapshot) ??\n hostedBudgetFromGrantMetadata(grant.metadata) ??\n options.defaultBudget;\n if (!budget) {\n throw new HostedInferenceError(\n `The ${HOSTED_INFERENCE_FEATURE_KEY} grant names no budget (metadata.budget.limitMicros) and no defaultBudget is set.`,\n 'HOSTED_INFERENCE_INVALID',\n { tenantId },\n );\n }\n const result = await options.provisioner.reconcileTenant(tenantId, {\n entitled: true,\n budget,\n users: await options.users(tenantId),\n });\n throwReconcileFailures(result);\n return result;\n };\n}\n\n/**\n * The smrt-subscriptions entitlement-change listener for hosted inference:\n * register it with `registerEntitlementChangeListener()`. A change that\n * gains, loses or changes `inference.hosted` reconciles the tenant to the\n * change's `after` snapshot ({@link createHostedInferenceReconciler}).\n */\nexport function createHostedInferenceEntitlementListener(\n options: HostedInferenceEntitlementListenerOptions,\n): (change: HostedEntitlementChange) => Promise<void> {\n const reconcile = createHostedInferenceReconciler(options);\n return async (change) => {\n const touches = [change.gained, change.lost, change.changed].some((keys) =>\n keys.includes(HOSTED_INFERENCE_FEATURE_KEY),\n );\n if (!touches) return;\n await reconcile(change.tenantId, change.after);\n };\n}\n","/**\n * `smrt doctor --ai` (#4752), Node-only subpath.\n *\n * {@link checkInferenceBindings} walks every tier and alias binding offline:\n * provider recognized, key variable present, model named, alias resolvable\n * and cycle-free. With `probe` it also calls each candidate's cheapest\n * list endpoint with a short timeout. Keys are read to test presence and\n * sent only to their own endpoint; they are never printed or returned.\n *\n * The same functions back the settings panel's test-connection action\n * (#4754): `probeInferenceCandidate()` tests one candidate.\n *\n * With `probe`, each candidate also reports its capabilities (#4758) and\n * their source: the model listing's evidence (context length, supported\n * parameters, input modalities) is the `probe` layer under core's preset\n * table and over its model heuristic. The SDK's `getCapabilities()` is the\n * call-time `provider` layer; the doctor builds no SDK clients, so it reads\n * that layer only through `sdkCapabilities`.\n */\nimport { AI_PROVIDER_TYPES } from '@happyvertical/ai';\nimport {\n getDefaultAIKeyEnvName,\n redactBaseUrl,\n} from '@happyvertical/smrt-config';\nimport {\n assertInferenceConfig,\n getInferenceConfig,\n INFERENCE_PRESET_CAPABILITIES,\n INFERENCE_PRESETS,\n INFERENCE_TIERS,\n type InferenceCandidate,\n type InferenceCapabilities,\n type InferenceCapabilityEvidence,\n type InferenceConfig,\n type InferenceTarget,\n inferenceCapabilitiesFromSdk,\n inferenceCapabilityHeuristic,\n inferencePresetFor,\n inferenceTargetKind,\n isInferencePresetId,\n resolveInferenceCapabilities,\n} from '@happyvertical/smrt-core';\nimport { candidateLabel, findInferenceConfigIssues } from './config.js';\nimport type { InferenceConfigIssue } from './types.js';\n\nexport type InferenceDoctorStatus = 'ok' | 'warn' | 'error';\n\n/** Result of one reachability probe. */\nexport interface InferenceProbeResult {\n /** True when the endpoint answered and accepted the credential. */\n reachable: boolean;\n /** True when no cheap probe is known for the provider. */\n skipped?: boolean;\n /** HTTP status, when one came back. */\n status?: number;\n durationMs: number;\n /** Short, key-free explanation. */\n detail: string;\n /**\n * What the model listing says about the candidate's model (#4758), when\n * the endpoint answered and listed it with capability fields.\n */\n capabilities?: InferenceCapabilityEvidence;\n}\n\n/** One candidate of a binding. */\nexport interface InferenceDoctorCandidate {\n label: string;\n status: InferenceDoctorStatus;\n problems: string[];\n probe?: InferenceProbeResult;\n /** With `probe`: the candidate's capabilities and their source (#4758). */\n capabilities?: InferenceCapabilities;\n}\n\n/** One tier or alias binding. */\nexport interface InferenceDoctorBinding {\n /** `tier:<name>` or `alias:<name>`. */\n binding: string;\n /** Key-free description of the target. */\n target: string;\n status: InferenceDoctorStatus;\n problems: string[];\n candidates: InferenceDoctorCandidate[];\n}\n\n/** The doctor's answer. `ok` is false when any binding cannot resolve. */\nexport interface InferenceDoctorReport {\n ok: boolean;\n /** False when no `inference` block is configured (every tier uses the configured client). */\n configured: boolean;\n bindings: InferenceDoctorBinding[];\n issues: InferenceConfigIssue[];\n}\n\n/** Options for {@link checkInferenceBindings} and the probe. */\nexport interface CheckInferenceBindingsOptions {\n /** Config to check. Default: core's merged `getInferenceConfig()`. */\n config?: InferenceConfig;\n /** Environment for key presence. Default `process.env`. */\n env?: Record<string, string | undefined>;\n /** Call each candidate's list endpoint. Default false (offline). */\n probe?: boolean;\n /** Probe timeout per candidate, ms. Default 3000. */\n timeoutMs?: number;\n /** Fetch implementation for probes. Default global `fetch`. */\n fetch?: typeof fetch;\n /** Recognized provider names. Default the AI SDK's provider list. */\n providers?: readonly string[];\n /**\n * The SDK `getCapabilities()` answer for a candidate (the `provider`\n * layer, #4758), when the host can supply one. Default: none; the doctor\n * builds no SDK clients.\n */\n sdkCapabilities?: (\n candidate: InferenceCandidate,\n ) => unknown | Promise<unknown>;\n}\n\nconst DEFAULT_PROBE_TIMEOUT_MS = 3000;\nconst OPENAI_DEFAULT_BASE = 'https://api.openai.com/v1';\nconst ANTHROPIC_DEFAULT_BASE = 'https://api.anthropic.com/v1';\nconst GEMINI_DEFAULT_BASE = 'https://generativelanguage.googleapis.com/v1beta';\nconst OLLAMA_DEFAULT_BASE = 'http://127.0.0.1:11434';\nconst OPENAI_COMPATIBLE = new Set(['openai', 'litellm', 'bifrost']);\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\nfunction worst(statuses: InferenceDoctorStatus[]): InferenceDoctorStatus {\n if (statuses.includes('error')) return 'error';\n if (statuses.includes('warn')) return 'warn';\n return 'ok';\n}\n\n/** Where a candidate's key comes from, without reading its value out. */\nfunction keySource(candidate: InferenceCandidate): {\n env?: string;\n inline?: boolean;\n required: boolean;\n providerDefault?: boolean;\n} {\n if (text(candidate.apiKey)) return { inline: true, required: true };\n const env = text(candidate.apiKeyEnv);\n if (env) return { env, required: true };\n const preset = text(candidate.preset);\n if (preset && isInferencePresetId(preset)) {\n return { required: INFERENCE_PRESETS[preset].requiresApiKey };\n }\n if (text(candidate.baseUrl)) return { required: false };\n const providerEnv = getDefaultAIKeyEnvName(candidate.provider);\n return providerEnv\n ? { env: providerEnv, required: true, providerDefault: true }\n : { required: false };\n}\n\nfunction resolveKey(\n candidate: InferenceCandidate,\n env: Record<string, string | undefined>,\n): string | undefined {\n const source = keySource(candidate);\n if (source.inline) return text(candidate.apiKey);\n return source.env ? text(env[source.env]) : undefined;\n}\n\nfunction checkCandidateOffline(\n candidate: InferenceCandidate,\n options: Required<Pick<CheckInferenceBindingsOptions, 'env' | 'providers'>>,\n): InferenceDoctorCandidate {\n const label = candidateLabel(candidate);\n const problems: string[] = [];\n const statuses: InferenceDoctorStatus[] = [];\n const fail = (message: string) => {\n problems.push(message);\n statuses.push('error');\n };\n const preset = text(candidate.preset);\n const provider = text(candidate.provider);\n if (preset) {\n if (!isInferencePresetId(preset)) fail(`unknown preset \"${preset}\"`);\n } else if (provider) {\n if (!options.providers.includes(provider.toLowerCase())) {\n fail(`provider \"${provider}\" is not recognized`);\n }\n }\n if (!text(candidate.model)) {\n problems.push('no model named (the provider default answers)');\n statuses.push('warn');\n }\n const source = keySource(candidate);\n if (source.required && !resolveKey(candidate, options.env)) {\n if (source.providerDefault) {\n // Same-provider targets inherit the configured client's key, which\n // this offline check cannot see: a warning, not an error.\n problems.push(\n `key variable ${source.env} is not set (fine if the configured client is this provider)`,\n );\n statuses.push('warn');\n } else {\n fail(\n source.env\n ? `key variable ${source.env} is not set`\n : 'a key is required and none is configured',\n );\n }\n }\n if ((text(candidate.apiKeyEnv) || text(candidate.apiKey)) && !preset) {\n if (!text(candidate.baseUrl)) {\n fail('brings its own key without a baseUrl');\n }\n }\n const sdkWide = ['HAVE_AI_BASE_URL', 'HAVE_AI_API_KEY'].find((name) =>\n text(options.env[name]),\n );\n if (sdkWide && provider && !preset && !text(candidate.baseUrl)) {\n // Core refuses another provider's default endpoint while the SDK-wide\n // fallbacks are set; only the configured provider passes.\n problems.push(\n `${sdkWide} is set: core refuses this candidate unless ${provider} is the configured provider (give it a baseUrl and apiKeyEnv)`,\n );\n statuses.push('warn');\n }\n return { label, status: worst(statuses), problems };\n}\n\nfunction probeRequest(\n candidate: InferenceCandidate,\n key: string | undefined,\n): { url: string; headers: Record<string, string> } | undefined {\n const preset = text(candidate.preset);\n const provider = (text(candidate.provider) ?? '').toLowerCase();\n const baseUrl =\n text(candidate.baseUrl) ??\n (preset && isInferencePresetId(preset)\n ? INFERENCE_PRESETS[preset].baseUrl\n : undefined);\n const trim = (url: string) => url.replace(/\\/+$/, '');\n // A gateway has no public default endpoint: never send its key to OpenAI's.\n if (\n !preset &&\n !baseUrl &&\n provider !== 'openai' &&\n OPENAI_COMPATIBLE.has(provider)\n ) {\n return undefined;\n }\n if (preset || OPENAI_COMPATIBLE.has(provider)) {\n return {\n url: `${trim(baseUrl ?? OPENAI_DEFAULT_BASE)}/models`,\n headers: key ? { authorization: `Bearer ${key}` } : {},\n };\n }\n if (provider === 'anthropic') {\n return {\n url: `${trim(baseUrl ?? ANTHROPIC_DEFAULT_BASE)}/models`,\n headers: {\n 'anthropic-version': '2023-06-01',\n ...(key ? { 'x-api-key': key } : {}),\n },\n };\n }\n if (provider === 'gemini') {\n return {\n url: `${trim(baseUrl ?? GEMINI_DEFAULT_BASE)}/models`,\n headers: key ? { 'x-goog-api-key': key } : {},\n };\n }\n if (provider === 'ollama') {\n return {\n url: `${trim(baseUrl ?? OLLAMA_DEFAULT_BASE)}/api/tags`,\n headers: {},\n };\n }\n return undefined;\n}\n\nfunction positive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0\n ? Math.floor(value)\n : undefined;\n}\n\n/**\n * Capability evidence for `model` from a list endpoint's body (#4758):\n * OpenAI-compatible `data[]` entries (OpenRouter `context_length`,\n * `supported_parameters`, `architecture.input_modalities`; LM Studio\n * `max_context_length`; llama.cpp `meta.n_ctx_train`) and Gemini\n * `models[].inputTokenLimit`. Unknown shapes give nothing.\n */\nexport function inferenceCapabilitiesFromModelListing(\n body: unknown,\n model: string | undefined,\n): InferenceCapabilityEvidence | undefined {\n if (!model || !body || typeof body !== 'object') return undefined;\n const record = body as { data?: unknown; models?: unknown };\n const list = Array.isArray(record.data)\n ? record.data\n : Array.isArray(record.models)\n ? record.models\n : [];\n const entry = list.find((item) => {\n const row = item as { id?: unknown; name?: unknown };\n return (\n row?.id === model ||\n row?.name === model ||\n row?.name === `models/${model}`\n );\n }) as Record<string, unknown> | undefined;\n if (!entry) return undefined;\n const out: InferenceCapabilityEvidence = {};\n const meta = entry.meta as Record<string, unknown> | undefined;\n const topProvider = entry.top_provider as Record<string, unknown> | undefined;\n const maxContext =\n positive(entry.context_length) ??\n positive(entry.max_context_length) ??\n positive(topProvider?.context_length) ??\n positive(meta?.n_ctx_train) ??\n positive(entry.inputTokenLimit);\n if (maxContext !== undefined) out.maxContext = maxContext;\n if (Array.isArray(entry.supported_parameters)) {\n const params = entry.supported_parameters;\n out.tools = params.includes('tools');\n out.json =\n params.includes('response_format') ||\n params.includes('structured_outputs');\n }\n const architecture = entry.architecture as\n | Record<string, unknown>\n | undefined;\n if (Array.isArray(architecture?.input_modalities)) {\n out.vision = architecture.input_modalities.includes('image');\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * A candidate's capabilities from every layer the doctor can see (#4758):\n * the host's SDK answer (when given), the preset table, the probe's\n * evidence, the model heuristic, then assumed.\n */\nexport async function inferenceCandidateCapabilities(\n candidate: InferenceCandidate,\n options: {\n probe?: InferenceCapabilityEvidence;\n sdkCapabilities?: CheckInferenceBindingsOptions['sdkCapabilities'];\n } = {},\n): Promise<InferenceCapabilities> {\n const preset = inferencePresetFor(candidate);\n let sdk: unknown;\n // The SDK answer describes the provider's own endpoint only.\n if (options.sdkCapabilities && !text(candidate.baseUrl) && !preset) {\n try {\n sdk = await options.sdkCapabilities(candidate);\n } catch {\n sdk = undefined;\n }\n }\n return resolveInferenceCapabilities({\n provider: inferenceCapabilitiesFromSdk(sdk),\n preset: preset ? INFERENCE_PRESET_CAPABILITIES[preset] : undefined,\n probe: options.probe,\n heuristic: inferenceCapabilityHeuristic(candidate),\n });\n}\n\n/**\n * The URL {@link probeInferenceCandidate} would call for a candidate, or\n * `undefined` when no cheap probe is known. Key-free; the settings key rule\n * (#4754) binds a key to this URL's origin.\n */\nexport function inferenceProbeUrl(\n candidate: InferenceCandidate,\n): string | undefined {\n return probeRequest(candidate, undefined)?.url;\n}\n\n/**\n * Probe one candidate's cheapest list endpoint (`/models`, Ollama\n * `/api/tags`) with a short timeout. The key goes only to the candidate's\n * own endpoint and never appears in the result.\n */\nexport async function probeInferenceCandidate(\n candidate: InferenceCandidate,\n options: Pick<\n CheckInferenceBindingsOptions,\n 'env' | 'timeoutMs' | 'fetch'\n > = {},\n): Promise<InferenceProbeResult> {\n const env = options.env ?? process.env;\n const preset = text(candidate.preset);\n if (preset && !isInferencePresetId(preset)) {\n return {\n reachable: false,\n durationMs: 0,\n detail: `not probed: unknown preset \"${preset}\"`,\n };\n }\n if (\n (text(candidate.apiKey) || text(candidate.apiKeyEnv)) &&\n !text(candidate.baseUrl) &&\n !preset\n ) {\n // Core's pair rule: a candidate's own key travels only with its own\n // endpoint. Refuse before any request rather than sending it to a\n // provider default.\n return {\n reachable: false,\n durationMs: 0,\n detail:\n 'not probed: a candidate key needs its own baseUrl (it is never sent to a provider default endpoint)',\n };\n }\n const request = probeRequest(candidate, resolveKey(candidate, env));\n if (!request) {\n return {\n reachable: false,\n skipped: true,\n durationMs: 0,\n detail: `no cheap probe for provider \"${candidate.provider ?? 'default'}\"`,\n };\n }\n const doFetch = options.fetch ?? globalThis.fetch;\n const controller = new AbortController();\n const timeoutMs = options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS;\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const started = Date.now();\n const where = redactBaseUrl(request.url) ?? 'endpoint';\n try {\n const response = await doFetch(request.url, {\n method: 'GET',\n headers: request.headers,\n signal: controller.signal,\n // A redirect would carry the key's header to another host.\n redirect: 'manual',\n });\n const durationMs = Date.now() - started;\n if (\n response.type === 'opaqueredirect' ||\n (response.status >= 300 && response.status < 400)\n ) {\n return {\n reachable: false,\n status: response.status || undefined,\n durationMs,\n detail: `${where} redirected (not followed: the key stays with its own endpoint)`,\n };\n }\n if (response.ok) {\n let capabilities: InferenceCapabilityEvidence | undefined;\n try {\n capabilities = inferenceCapabilitiesFromModelListing(\n await response.json(),\n text(candidate.model),\n );\n } catch {\n // A listing that is not JSON still proves reachability.\n }\n return {\n reachable: true,\n status: response.status,\n durationMs,\n detail: `${where} answered ${response.status}`,\n ...(capabilities ? { capabilities } : {}),\n };\n }\n const why =\n response.status === 401 || response.status === 403\n ? 'credential rejected'\n : response.status === 402 || response.status === 429\n ? 'quota or rate limit'\n : 'endpoint error';\n return {\n reachable: false,\n status: response.status,\n durationMs,\n detail: `${where} answered ${response.status} (${why})`,\n };\n } catch (error) {\n const aborted = controller.signal.aborted;\n return {\n reachable: false,\n durationMs: Date.now() - started,\n detail: aborted\n ? `${where} did not answer within ${timeoutMs} ms`\n : `${where} unreachable (${error instanceof Error ? error.name : 'error'})`,\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\nfunction describeTarget(target: InferenceTarget): string {\n const kind = inferenceTargetKind(target);\n if (kind === 'alias') return `alias ${String(target.alias)}`;\n if (kind === 'candidates') {\n return `${target.candidates?.length ?? 0} inline candidate(s)`;\n }\n if (kind === 'fusion') return `fusion ${String(target.fusion)}`;\n if (kind !== 'single') return `kind ${kind} (not checked here)`;\n const base = text(target.baseUrl);\n return `${candidateLabel(target)}${base ? ` at ${redactBaseUrl(base)}` : ''}`;\n}\n\n/**\n * Walk every tier and alias binding; see the module comment. Never throws on\n * a bad config: problems come back in the report, and `ok` is false when any\n * binding cannot resolve.\n */\nexport async function checkInferenceBindings(\n options: CheckInferenceBindingsOptions = {},\n): Promise<InferenceDoctorReport> {\n const env = options.env ?? process.env;\n const providers = (options.providers ?? AI_PROVIDER_TYPES).map((name) =>\n name.toLowerCase(),\n );\n const bindings: InferenceDoctorBinding[] = [];\n let config: InferenceConfig | undefined;\n try {\n config = 'config' in options ? options.config : getInferenceConfig();\n assertInferenceConfig(config);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const issue: InferenceConfigIssue = {\n path: 'inference',\n code: 'candidate-invalid',\n message,\n };\n return {\n ok: false,\n configured: true,\n bindings: [\n {\n binding: 'inference',\n target: 'config',\n status: 'error',\n problems: [message],\n candidates: [],\n },\n ],\n issues: [issue],\n };\n }\n const issues = findInferenceConfigIssues(config);\n const aliases = config?.aliases ?? {};\n const issuesFor = (prefix: string) =>\n issues\n .filter(\n (issue) =>\n issue.path === prefix ||\n issue.path.startsWith(`${prefix}.`) ||\n issue.path.startsWith(`${prefix}[`),\n )\n .map((issue) => issue.message);\n\n const checkList = async (\n list: readonly InferenceCandidate[],\n ): Promise<InferenceDoctorCandidate[]> => {\n const out: InferenceDoctorCandidate[] = [];\n for (const candidate of list) {\n const nested = text(candidate.alias);\n if (nested) {\n out.push({\n label: `alias:${nested}`,\n status: Object.hasOwn(aliases, nested) ? 'ok' : 'error',\n problems: Object.hasOwn(aliases, nested)\n ? [`expands alias ${nested} (checked under alias:${nested})`]\n : [`unknown alias \"${nested}\"`],\n });\n continue;\n }\n const checked = checkCandidateOffline(candidate, { env, providers });\n if (options.probe && checked.status !== 'error') {\n checked.probe = await probeInferenceCandidate(candidate, {\n env,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n });\n if (!checked.probe.reachable && !checked.probe.skipped) {\n checked.status = 'error';\n checked.problems.push(checked.probe.detail);\n }\n checked.capabilities = await inferenceCandidateCapabilities(candidate, {\n probe: checked.probe.capabilities,\n sdkCapabilities: options.sdkCapabilities,\n });\n }\n out.push(checked);\n }\n return out;\n };\n\n const settle = (\n binding: string,\n target: string,\n configProblems: string[],\n candidates: InferenceDoctorCandidate[],\n ): InferenceDoctorBinding => {\n const usable = candidates.some((candidate) => candidate.status !== 'error');\n const status: InferenceDoctorStatus =\n configProblems.length > 0 || (candidates.length > 0 && !usable)\n ? 'error'\n : candidates.every((candidate) => candidate.status === 'ok')\n ? 'ok'\n : // Some candidate cannot answer but another can: the chain still\n // resolves, so this warns rather than fails.\n 'warn';\n return {\n binding,\n target,\n status,\n problems: [\n ...configProblems,\n ...(configProblems.length === 0 && candidates.length > 0 && !usable\n ? ['no candidate can answer']\n : []),\n ],\n candidates,\n };\n };\n\n for (const tier of INFERENCE_TIERS) {\n const target = config?.tiers?.[tier];\n const name = `tier:${tier}`;\n if (!target || Object.keys(target).length === 0) {\n bindings.push({\n binding: name,\n target: 'the configured client',\n status: 'ok',\n problems: [],\n candidates: [],\n });\n continue;\n }\n const kind = inferenceTargetKind(target);\n const configProblems = issuesFor(`inference.tiers.${tier}`);\n let candidates: InferenceDoctorCandidate[] = [];\n if (kind === 'single') {\n candidates = await checkList([target as InferenceCandidate]);\n } else if (kind === 'candidates') {\n candidates = await checkList(target.candidates ?? []);\n } else if (kind === 'alias') {\n const aliasName = text(target.alias);\n candidates = [\n {\n label: `alias:${aliasName ?? '?'}`,\n status:\n aliasName && Object.hasOwn(aliases, aliasName) ? 'ok' : 'error',\n problems: [],\n },\n ];\n }\n bindings.push(\n settle(name, describeTarget(target), configProblems, candidates),\n );\n }\n\n for (const [name, alias] of Object.entries(aliases)) {\n const list = Array.isArray(alias?.candidates) ? alias.candidates : [];\n bindings.push(\n settle(\n `alias:${name}`,\n `${list.length} candidate(s)${alias?.description ? ` — ${alias.description}` : ''}`,\n issuesFor(`inference.aliases.${name}`),\n await checkList(list),\n ),\n );\n }\n\n // Fusions (#4753): structure only; each seat's target is a tier, alias or\n // model checked above. Prompt keys are the app's and are checked at load.\n for (const [name, fusion] of Object.entries(config?.fusions ?? {})) {\n const seats = Array.isArray(fusion?.seats) ? fusion.seats.length : 0;\n bindings.push(\n settle(\n `fusion:${name}`,\n `${String(fusion?.mode)}, ${seats} seat(s)${fusion?.description ? ` — ${fusion.description}` : ''}`,\n issuesFor(`inference.fusions.${name}`),\n [],\n ),\n );\n }\n\n // A tier bound to an alias inherits that alias's verdict.\n for (const binding of bindings) {\n if (!binding.binding.startsWith('tier:')) continue;\n const ref = binding.candidates[0]?.label;\n if (binding.candidates.length !== 1 || !ref?.startsWith('alias:')) continue;\n const resolved = bindings.find((entry) => entry.binding === ref);\n const reference = binding.candidates[0];\n if (resolved && reference) reference.status = resolved.status;\n if (resolved?.status === 'error') {\n binding.status = 'error';\n binding.problems.push(`${ref} cannot resolve`);\n } else if (resolved?.status === 'warn' && binding.status === 'ok') {\n binding.status = 'warn';\n binding.problems.push(`${ref} has a candidate that cannot answer`);\n }\n }\n\n return {\n ok: !bindings.some((binding) => binding.status === 'error'),\n configured: !!config && (!!config.tiers || !!config.aliases),\n bindings,\n issues,\n };\n}\n\n/** One line describing capabilities, e.g. `json no, tools yes, … (preset)`. */\nexport function formatInferenceCapabilities(\n capabilities: InferenceCapabilities,\n): string {\n const flag = (value: boolean) => (value ? 'yes' : 'no');\n return `json ${flag(capabilities.json)}, tools ${flag(capabilities.tools)}, vision ${flag(capabilities.vision)}, context ${capabilities.maxContext ?? 'unknown'} (source: ${capabilities.source})`;\n}\n\nconst ICON: Record<InferenceDoctorStatus, string> = {\n ok: '✅',\n warn: '⚠️ ',\n error: '❌',\n};\n\n/** The lines `smrt doctor --ai` prints for a report. */\nexport function formatInferenceDoctorReport(\n report: InferenceDoctorReport,\n): string[] {\n const lines: string[] = [];\n if (!report.configured) {\n lines.push(\n ' No inference config: every tier uses the configured AI client.',\n );\n }\n for (const binding of report.bindings) {\n lines.push(\n ` ${ICON[binding.status]} ${binding.binding} → ${binding.target}`,\n );\n for (const problem of binding.problems) lines.push(` ${problem}`);\n for (const candidate of binding.candidates) {\n const probe = candidate.probe\n ? candidate.probe.skipped\n ? ' (probe skipped)'\n : ` (${candidate.probe.detail}, ${candidate.probe.durationMs} ms)`\n : '';\n lines.push(` ${ICON[candidate.status]} ${candidate.label}${probe}`);\n if (candidate.capabilities) {\n lines.push(\n ` capabilities: ${formatInferenceCapabilities(candidate.capabilities)}`,\n );\n }\n for (const problem of candidate.problems) {\n if (candidate.probe && problem === candidate.probe.detail) continue;\n lines.push(` ${problem}`);\n }\n }\n }\n return lines;\n}\n\nexport * from './hosted.js';\nexport type { InferenceConfigIssue } from './types.js';\n"],"mappings":"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"}