@happyvertical/smrt-inference 0.0.0-stage → 0.60.15

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 (107) hide show
  1. package/AGENTS.md +201 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +73 -2
  5. package/agents/fusions.md +92 -0
  6. package/agents/hosted.md +145 -0
  7. package/agents/resolver.md +65 -0
  8. package/agents/settings.md +84 -0
  9. package/dist/__smrt-register__.d.ts +2 -0
  10. package/dist/__smrt-register__.d.ts.map +1 -0
  11. package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js +398 -0
  12. package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js.map +1 -0
  13. package/dist/chunks/provider-config-4Bx8N52X.js +1847 -0
  14. package/dist/chunks/provider-config-4Bx8N52X.js.map +1 -0
  15. package/dist/classify.d.ts +32 -0
  16. package/dist/classify.d.ts.map +1 -0
  17. package/dist/config.d.ts +89 -0
  18. package/dist/config.d.ts.map +1 -0
  19. package/dist/errors.d.ts +51 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/executor.d.ts +45 -0
  22. package/dist/executor.d.ts.map +1 -0
  23. package/dist/fusion-store.d.ts +16 -0
  24. package/dist/fusion-store.d.ts.map +1 -0
  25. package/dist/fusion.d.ts +155 -0
  26. package/dist/fusion.d.ts.map +1 -0
  27. package/dist/gate.d.ts +143 -0
  28. package/dist/gate.d.ts.map +1 -0
  29. package/dist/gate.js +94 -0
  30. package/dist/gate.js.map +1 -0
  31. package/dist/hosted.d.ts +454 -0
  32. package/dist/hosted.d.ts.map +1 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +8 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/manifest.json +453 -0
  38. package/dist/models/InferenceOverride.d.ts +38 -0
  39. package/dist/models/InferenceOverride.d.ts.map +1 -0
  40. package/dist/models/InferenceOverrideCollection.d.ts +35 -0
  41. package/dist/models/InferenceOverrideCollection.d.ts.map +1 -0
  42. package/dist/models.d.ts +11 -0
  43. package/dist/models.d.ts.map +1 -0
  44. package/dist/models.js +2 -0
  45. package/dist/node.d.ts +110 -0
  46. package/dist/node.d.ts.map +1 -0
  47. package/dist/node.js +1361 -0
  48. package/dist/node.js.map +1 -0
  49. package/dist/provider-config.d.ts +113 -0
  50. package/dist/provider-config.d.ts.map +1 -0
  51. package/dist/register.d.ts +30 -0
  52. package/dist/register.d.ts.map +1 -0
  53. package/dist/resolver.d.ts +166 -0
  54. package/dist/resolver.d.ts.map +1 -0
  55. package/dist/resolver.js +288 -0
  56. package/dist/resolver.js.map +1 -0
  57. package/dist/server.d.ts +24 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +985 -0
  60. package/dist/server.js.map +1 -0
  61. package/dist/settings/cache.d.ts +5 -0
  62. package/dist/settings/cache.d.ts.map +1 -0
  63. package/dist/settings/keys.d.ts +70 -0
  64. package/dist/settings/keys.d.ts.map +1 -0
  65. package/dist/settings/layers.d.ts +54 -0
  66. package/dist/settings/layers.d.ts.map +1 -0
  67. package/dist/settings/override-service.d.ts +119 -0
  68. package/dist/settings/override-service.d.ts.map +1 -0
  69. package/dist/settings/preferences.d.ts +137 -0
  70. package/dist/settings/preferences.d.ts.map +1 -0
  71. package/dist/settings/resolver.d.ts +34 -0
  72. package/dist/settings/resolver.d.ts.map +1 -0
  73. package/dist/settings/settings-service.d.ts +282 -0
  74. package/dist/settings/settings-service.d.ts.map +1 -0
  75. package/dist/settings/targets.d.ts +93 -0
  76. package/dist/settings/targets.d.ts.map +1 -0
  77. package/dist/smrt-knowledge.json +335 -0
  78. package/dist/smrt-knowledge.okf.md +80 -0
  79. package/dist/svelte/components/InferenceAliasEditor.svelte +210 -0
  80. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts +13 -0
  81. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts.map +1 -0
  82. package/dist/svelte/components/InferenceDefaultView.svelte +330 -0
  83. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts +15 -0
  84. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts.map +1 -0
  85. package/dist/svelte/components/InferenceFusionList.svelte +135 -0
  86. package/dist/svelte/components/InferenceFusionList.svelte.d.ts +16 -0
  87. package/dist/svelte/components/InferenceFusionList.svelte.d.ts.map +1 -0
  88. package/dist/svelte/components/InferenceSettingsPanel.svelte +345 -0
  89. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts +65 -0
  90. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts.map +1 -0
  91. package/dist/svelte/components/InferenceTierRow.svelte +199 -0
  92. package/dist/svelte/components/InferenceTierRow.svelte.d.ts +14 -0
  93. package/dist/svelte/components/InferenceTierRow.svelte.d.ts.map +1 -0
  94. package/dist/svelte/components/InferenceUsageTable.svelte +127 -0
  95. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts +15 -0
  96. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts.map +1 -0
  97. package/dist/svelte/index.d.ts +17 -0
  98. package/dist/svelte/index.d.ts.map +1 -0
  99. package/dist/svelte/index.js +14 -0
  100. package/dist/svelte/types.d.ts +197 -0
  101. package/dist/svelte/types.d.ts.map +1 -0
  102. package/dist/svelte/types.js +96 -0
  103. package/dist/types.d.ts +47 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +13 -0
  106. package/dist/types.js.map +1 -0
  107. package/package.json +105 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","names":["text"],"sources":["../src/settings/keys.ts","../src/settings/override-service.ts","../src/settings/layers.ts","../src/settings/preferences.ts","../src/settings/resolver.ts","../src/settings/settings-service.ts"],"sourcesContent":["/**\n * The origin-bound key rule (#4754, ported from the planner's BYO screen).\n *\n * A key is bound, when it is saved, to the origin of the endpoint it was\n * entered for (the explicit base URL, else the preset's, else the provider's\n * default API endpoint). It is only ever sent to that origin: a request to\n * any other origin gets no key, even when the user later edits the base URL.\n * The settings panel never receives a key back, only \"set / not set\".\n *\n * Storage stays with the host: `bindInferenceKey()` returns the bound\n * record, and `inferenceProviderConfigFromKey()` turns it into the #4756\n * `inference.provider-config` value the host writes through the user\n * resolver's `write()` (one secret type for every key).\n */\nimport { isInferencePresetId } from '@happyvertical/smrt-core';\nimport { inferenceProbeUrl } from '../node.js';\n\n/** What the host stores in smrt-secrets for one provider key. */\nexport interface InferenceKeyRecord {\n provider: string;\n preset?: string;\n /** The base URL the key was entered for, when one was given. */\n baseUrl?: string;\n /** The only origin this key is ever sent to. */\n origin: string;\n apiKey: string;\n}\n\n/** Thrown when a key cannot be bound to an origin. Nothing should be stored. */\nexport class InferenceKeyOriginError extends Error {\n readonly status = 400;\n constructor(reason: string) {\n super(`Cannot bind the key: ${reason}`);\n this.name = 'InferenceKeyOriginError';\n }\n}\n\nconst LOOPBACK = new Set(['localhost', '127.0.0.1', '[::1]', '::1']);\n\n/**\n * SDK base URLs for providers whose client appends its own version path\n * (the Anthropic SDK calls `{baseURL}/v1/messages`; the probe base carries\n * `/v1`). A stored binding names these, not the probe base.\n */\nconst SDK_DEFAULT_BASE: Readonly<Record<string, string>> = {\n anthropic: 'https://api.anthropic.com',\n gemini: 'https://generativelanguage.googleapis.com',\n};\n\n/** Gateways whose default endpoint is not the provider's own: they need a base URL. */\nconst NEEDS_BASE_URL = new Set(['litellm', 'bifrost']);\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\nfunction originOf(url: string): string {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new InferenceKeyOriginError('the base URL is not a URL');\n }\n if (parsed.username || parsed.password) {\n throw new InferenceKeyOriginError('the base URL carries credentials');\n }\n if (parsed.protocol === 'https:') return parsed.origin;\n if (parsed.protocol === 'http:' && LOOPBACK.has(parsed.hostname)) {\n return parsed.origin;\n }\n throw new InferenceKeyOriginError(\n 'a key travels only over https (plain http is allowed for loopback servers)',\n );\n}\n\n/**\n * The origin a key for this endpoint is bound to, or `null` when none can be\n * known (an unknown provider without a base URL, or a gateway without one).\n */\nexport function inferenceKeyOrigin(input: {\n provider?: string;\n preset?: string;\n baseUrl?: string;\n}): string | null {\n const baseUrl = text(input.baseUrl);\n if (baseUrl) return originOf(baseUrl);\n const provider = text(input.provider)?.toLowerCase();\n // A provider id that names a #4674 preset (the panel's preset choices)\n // binds to that preset's endpoint.\n const preset =\n text(input.preset) ??\n (provider && isInferencePresetId(provider) ? provider : undefined);\n if (!preset && (!provider || NEEDS_BASE_URL.has(provider))) return null;\n if (preset && !isInferencePresetId(preset)) return null;\n const url = inferenceProbeUrl(preset ? { preset } : { provider });\n return url ? originOf(url) : null;\n}\n\n/**\n * Bind a key to its endpoint's origin. The host stores the returned record\n * through `SecretService` and hands the panel back only `keySet: true`.\n *\n * @throws {InferenceKeyOriginError} when the key is blank or no origin can be known.\n */\nexport function bindInferenceKey(input: {\n provider: string;\n key: string;\n preset?: string;\n baseUrl?: string;\n}): InferenceKeyRecord {\n const provider = text(input.provider);\n const apiKey = text(input.key);\n if (!provider) throw new InferenceKeyOriginError('no provider');\n if (!apiKey) throw new InferenceKeyOriginError('the key is empty');\n const origin = inferenceKeyOrigin(input);\n if (!origin) {\n throw new InferenceKeyOriginError(\n `no endpoint is known for \"${provider}\"; enter its base URL first`,\n );\n }\n const record: InferenceKeyRecord = { provider, origin, apiKey };\n const preset = text(input.preset);\n const baseUrl = text(input.baseUrl);\n if (preset) record.preset = preset;\n if (baseUrl) record.baseUrl = baseUrl;\n return record;\n}\n\n/**\n * The #4756 stored provider config (`inference.provider-config`, written\n * through the user resolver's `write(level, config)`) for a bound key: the\n * one place a panel-saved key is stored, so the resolver uses it. The\n * binding is self-contained: a preset key keeps its preset (the preset\n * supplies the endpoint), a provider key names its endpoint explicitly\n * (its base URL, else the provider default it was bound to), and the\n * endpoint is checked against the bound origin again.\n *\n * @throws {InferenceKeyOriginError} when no endpoint on the bound origin is\n * known, or a preset key has no model.\n */\nexport function inferenceProviderConfigFromKey(\n record: InferenceKeyRecord,\n choice: { model?: string } = {},\n): {\n provider?: string;\n preset?: string;\n baseUrl?: string;\n model?: string;\n apiKey: string;\n} {\n const model = text(choice.model);\n // A provider id that names a preset binds to that preset (as\n // `inferenceKeyOrigin()` does).\n const preset =\n text(record.preset) ??\n (isInferencePresetId(record.provider) ? record.provider : undefined);\n if (preset) {\n if (!model) throw new InferenceKeyOriginError('a preset key needs a model');\n const custom = text(record.baseUrl);\n const presetUrl = isInferencePresetId(preset)\n ? inferenceProbeUrl({ preset })\n : undefined;\n const endpoint = custom ?? presetUrl;\n if (!endpoint || originOf(endpoint) !== record.origin) {\n throw new InferenceKeyOriginError(\n `no endpoint on ${record.origin} is known for preset \"${preset}\"`,\n );\n }\n // A preset always calls its own endpoint, so a key entered for a custom\n // base URL on another origin is stored as the OpenAI-compatible endpoint\n // it was entered for (every preset is one), never under the preset.\n if (custom && (!presetUrl || originOf(presetUrl) !== record.origin)) {\n return {\n provider: 'openai',\n baseUrl: custom,\n model,\n apiKey: record.apiKey,\n };\n }\n return { preset, model, apiKey: record.apiKey };\n }\n let baseUrl = text(record.baseUrl);\n if (!baseUrl) {\n const provider = record.provider.toLowerCase();\n const url = inferenceProbeUrl({ provider });\n baseUrl = Object.hasOwn(SDK_DEFAULT_BASE, provider)\n ? SDK_DEFAULT_BASE[provider]\n : url?.replace(/\\/(?:models|api\\/tags)$/, '');\n }\n if (!baseUrl || originOf(baseUrl) !== record.origin) {\n throw new InferenceKeyOriginError(\n `no endpoint on ${record.origin} is known for \"${record.provider}\"; enter its base URL`,\n );\n }\n return {\n provider: record.provider,\n baseUrl,\n ...(model ? { model } : {}),\n apiKey: record.apiKey,\n };\n}\n\n/**\n * The key to send to `url`, or `undefined` when `url` is not on the key's\n * bound origin (or is not a URL). The only way a stored key reaches a request.\n */\nexport function inferenceKeyFor(\n record: InferenceKeyRecord | null | undefined,\n url: string,\n): string | undefined {\n if (!record?.apiKey || !record.origin) return undefined;\n try {\n return new URL(url).origin === record.origin ? record.apiKey : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** A short placeholder for a stored key; never derived from the key itself. */\nexport const REDACTED_KEY = '[redacted]';\n\n/**\n * Remove a key (and common key shapes) from text bound for a screen or a\n * log. Used on every test-connection detail before it leaves the server.\n */\nexport function redactInferenceSecret(text: string, secret?: string): string {\n let out = text;\n if (secret && secret.length >= 4) out = out.split(secret).join(REDACTED_KEY);\n return out\n .replace(/\\b(sk|rk|pk)-[A-Za-z0-9_-]{8,}/g, REDACTED_KEY)\n .replace(/\\b(Bearer)\\s+[A-Za-z0-9._~+/=-]{8,}/gi, `$1 ${REDACTED_KEY}`);\n}\n","/**\n * Authorized write path for `_smrt_inference_overrides` (#4754), mirroring\n * smrt-prompts' `PromptOverrideService` (#3013): every write names its exact\n * scope, asks the host authorizer, and fails closed. Tenant writes of a tier\n * or the default tier must also pass the tenant allow-list (the app row's\n * stored list, then the host's `tenantTargetPolicy` hook).\n */\nimport type { InferenceTier } from '@happyvertical/smrt-core';\nimport { APP_INFERENCE_SCOPE_ID } from '../models/InferenceOverride.js';\nimport type {\n InferenceOverrideCollection,\n InferenceOverrideScopeType,\n} from '../models/InferenceOverrideCollection.js';\nimport {\n InferenceBindingValueError,\n inferenceTargetRef,\n isAllowedByList,\n normalizeAliasDefinition,\n normalizeAllowList,\n normalizeDefaultTier,\n normalizeFusionDefinition,\n normalizeTierTarget,\n parseInferenceBinding,\n} from './targets.js';\n\n/** One override write the host must authorize before it runs. */\nexport interface InferenceOverrideWriteRequest {\n operation: 'set' | 'remove' | 'allow-list';\n binding: string;\n scopeType: InferenceOverrideScopeType;\n scopeId: string;\n /** `set`: the normalized value (`null` reverts the scope to what it inherits). */\n value?: unknown;\n /** `allow-list`: the normalized list (`null` removes it). */\n tenantAllowList?: string[] | null;\n}\n\n/**\n * The host's authorization decision for one write. Return `true` only when\n * the current caller may write that exact scope; anything else (including a\n * throw) denies.\n */\nexport type InferenceOverrideAuthorizer = (\n request: InferenceOverrideWriteRequest,\n) => boolean | Promise<boolean>;\n\n/** What the tenant-target policy hook is asked. */\nexport interface InferenceTenantTargetRequest {\n binding: string;\n /** The tier being bound (`tier:*` bindings). */\n tier?: InferenceTier;\n /** The allow-list reference of the requested target (`model:…`, `alias:…`, `fusion:…`, `tier:…`). */\n ref: string;\n /** The normalized target (a tier target, or a tier name for `default-tier`). */\n value: unknown;\n tenantId: string;\n}\n\n/**\n * The code half of the tenant allow-list, after the stored list on the app\n * row: return `true` only when the tenant may bind `ref`. This is the hook\n * #4667 uses to cap the maximum hosted tier per plan. It runs on every\n * tenant write and on every read that layers a tenant row, so narrowing it\n * stops an existing tenant binding from taking effect.\n */\nexport type InferenceTenantTargetPolicy = (\n request: InferenceTenantTargetRequest,\n) => boolean | Promise<boolean>;\n\n/** Thrown when the authorizer does not return `true`. Nothing is written. */\nexport class InferenceOverrideAuthorizationError extends Error {\n readonly status = 403;\n constructor(readonly request: InferenceOverrideWriteRequest) {\n super(\n `Inference override ${request.operation} denied for ${request.scopeType} scope`,\n );\n this.name = 'InferenceOverrideAuthorizationError';\n }\n}\n\n/** Thrown when a tenant write names a target the allow-list does not open. Nothing is written. */\nexport class InferenceTenantTargetNotAllowedError extends Error {\n readonly status = 403;\n constructor(\n readonly binding: string,\n readonly ref: string,\n ) {\n super(\n `Tenants may not bind ${binding} to ${ref}: not on the tenant allow-list`,\n );\n this.name = 'InferenceTenantTargetNotAllowedError';\n }\n}\n\n/** Thrown for a scope the layered read never consults. Checked before the authorizer. */\nexport class InvalidInferenceScopeError extends Error {\n readonly status = 400;\n constructor(\n readonly scopeType: string,\n readonly scopeId: string,\n reason: string,\n ) {\n super(`Invalid inference override scope (${scopeType}): ${reason}`);\n this.name = 'InvalidInferenceScopeError';\n }\n}\n\n/** Reject any scope the layered read does not consult. */\nexport function assertValidInferenceScope(\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n): void {\n if (scopeType !== 'app' && scopeType !== 'tenant') {\n throw new InvalidInferenceScopeError(\n String(scopeType),\n String(scopeId),\n 'scope type must be \"app\" or \"tenant\"',\n );\n }\n if (typeof scopeId !== 'string') {\n throw new InvalidInferenceScopeError(\n scopeType,\n String(scopeId),\n 'scope id must be a string',\n );\n }\n if (scopeType === 'app' && scopeId !== APP_INFERENCE_SCOPE_ID) {\n throw new InvalidInferenceScopeError(\n scopeType,\n scopeId,\n `the app scope is only ever \"${APP_INFERENCE_SCOPE_ID}\"`,\n );\n }\n if (\n scopeType === 'tenant' &&\n (!scopeId.trim() ||\n scopeId !== scopeId.trim() ||\n scopeId === APP_INFERENCE_SCOPE_ID)\n ) {\n throw new InvalidInferenceScopeError(\n scopeType,\n scopeId,\n 'a tenant scope id must be non-blank, already trimmed, and not the app scope id',\n );\n }\n}\n\n/** Normalize the value of one binding (throws {@link InferenceBindingValueError}). */\nexport function normalizeBindingValue(\n binding: string,\n value: unknown,\n): unknown {\n const parsed = parseInferenceBinding(binding);\n if (!parsed) {\n throw new InferenceBindingValueError(String(binding), 'unknown binding id');\n }\n switch (parsed.kind) {\n case 'tier':\n return normalizeTierTarget(binding, value);\n case 'default-tier':\n return normalizeDefaultTier(binding, value);\n case 'alias':\n return normalizeAliasDefinition(binding, value);\n case 'fusion':\n return normalizeFusionDefinition(binding, value);\n }\n}\n\n/** The allow-list reference of a normalized tier or default-tier value. */\nexport function bindingValueRef(binding: string, value: unknown): string {\n if (binding === 'default-tier') return `tier:${String(value)}`;\n return inferenceTargetRef(value as Parameters<typeof inferenceTargetRef>[0]);\n}\n\n/**\n * Whether a tenant may hold `value` for `binding`: the app row's stored\n * list, then the policy hook. A throwing policy denies.\n */\nexport async function isTenantValueAllowed(\n binding: string,\n value: unknown,\n tenantId: string,\n allowList: readonly string[] | null,\n policy: InferenceTenantTargetPolicy | undefined,\n): Promise<boolean> {\n const ref = bindingValueRef(binding, value);\n if (!isAllowedByList(ref, allowList)) return false;\n if (!policy) return true;\n const parsed = parseInferenceBinding(binding);\n try {\n return (\n (await policy({\n binding,\n tier:\n parsed?.kind === 'tier' ? (parsed.name as InferenceTier) : undefined,\n ref,\n value,\n tenantId,\n })) === true\n );\n } catch {\n return false;\n }\n}\n\n/** Options for {@link InferenceOverrideService}. */\nexport interface InferenceOverrideServiceOptions {\n /** See {@link InferenceTenantTargetPolicy}. */\n tenantTargetPolicy?: InferenceTenantTargetPolicy;\n}\n\n/**\n * The only write path a host should use for `_smrt_inference_overrides`.\n * Construct it with an authorizer bound to the current caller; every write\n * asks it first and fails closed.\n */\nexport class InferenceOverrideService {\n constructor(\n private readonly overrides: InferenceOverrideCollection,\n private readonly authorize: InferenceOverrideAuthorizer,\n private readonly options: InferenceOverrideServiceOptions = {},\n ) {}\n\n /**\n * Set (or, with `null`, revert) one binding at one scope. Tenants may write\n * only `tier:*` and `default-tier`, and only an allow-listed target;\n * reverting is always allowed.\n *\n * @throws {InvalidInferenceScopeError} for a scope nothing reads.\n * @throws {InferenceBindingValueError} for an invalid binding or value.\n * @throws {InferenceOverrideAuthorizationError} when the authorizer declines.\n * @throws {InferenceTenantTargetNotAllowedError} when the allow-list refuses.\n */\n async setValue(\n binding: string,\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n value: unknown,\n options: {\n /**\n * Runs after the authorizer allowed the write and before it lands; a\n * check that reads other rows belongs here, never before\n * authorization.\n */\n beforeWrite?: (normalized: unknown) => Promise<void>;\n } = {},\n ): Promise<void> {\n assertValidInferenceScope(scopeType, scopeId);\n const normalized = normalizeBindingValue(binding, value);\n const parsed = parseInferenceBinding(binding);\n if (\n scopeType === 'tenant' &&\n (parsed?.kind === 'alias' || parsed?.kind === 'fusion')\n ) {\n throw new InferenceBindingValueError(\n binding,\n 'aliases and fusions are defined at the app scope only',\n );\n }\n await this.require({\n operation: normalized === null ? 'remove' : 'set',\n binding,\n scopeType,\n scopeId,\n value: normalized,\n });\n if (scopeType === 'tenant' && normalized !== null) {\n const app = await this.overrides.findByScope(\n binding,\n 'app',\n APP_INFERENCE_SCOPE_ID,\n );\n const allowed = await isTenantValueAllowed(\n binding,\n normalized,\n scopeId,\n app?.getTenantAllowList() ?? null,\n this.options.tenantTargetPolicy,\n );\n if (!allowed) {\n throw new InferenceTenantTargetNotAllowedError(\n binding,\n bindingValueRef(binding, normalized),\n );\n }\n }\n await options.beforeWrite?.(normalized);\n await this.overrides.setOverride(binding, scopeType, scopeId, {\n target: normalized,\n });\n }\n\n /**\n * Set (or, with `null`, remove) the tenant allow-list of a `tier:*` or\n * `default-tier` binding. The list lives on the app row.\n */\n async setTenantAllowList(\n binding: string,\n allowList: string[] | null,\n ): Promise<void> {\n const parsed = parseInferenceBinding(binding);\n if (!parsed || (parsed.kind !== 'tier' && parsed.kind !== 'default-tier')) {\n throw new InferenceBindingValueError(\n String(binding),\n 'only tier and default-tier bindings carry a tenant allow-list',\n );\n }\n const normalized = normalizeAllowList(binding, allowList);\n await this.require({\n operation: 'allow-list',\n binding,\n scopeType: 'app',\n scopeId: APP_INFERENCE_SCOPE_ID,\n tenantAllowList: normalized,\n });\n await this.overrides.setOverride(binding, 'app', APP_INFERENCE_SCOPE_ID, {\n tenantAllowList: normalized,\n });\n }\n\n /** Delete the whole row (value and, on the app row, the allow-list). */\n async removeOverride(\n binding: string,\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n ): Promise<boolean> {\n assertValidInferenceScope(scopeType, scopeId);\n if (!parseInferenceBinding(binding)) {\n throw new InferenceBindingValueError(\n String(binding),\n 'unknown binding id',\n );\n }\n await this.require({ operation: 'remove', binding, scopeType, scopeId });\n return this.overrides.removeOverride(binding, scopeType, scopeId);\n }\n\n private async require(request: InferenceOverrideWriteRequest): Promise<void> {\n let allowed = false;\n try {\n allowed = (await this.authorize(request)) === true;\n } catch {\n allowed = false;\n }\n if (!allowed) throw new InferenceOverrideAuthorizationError(request);\n }\n}\n","/**\n * Layered read of `_smrt_inference_overrides` (#4754): the code default\n * (core's built-in resolution), the `inference` config, the app row, then\n * the tenant row, as in smrt-prompts. A tenant row takes effect only while\n * the app row's allow-list and the policy hook still allow it.\n */\nimport {\n type InferenceAlias,\n type InferenceConfig,\n type InferenceFusionDefinition,\n type InferenceTarget,\n type InferenceTier,\n mergeInferenceConfigs,\n} from '@happyvertical/smrt-core';\nimport {\n APP_INFERENCE_SCOPE_ID,\n type InferenceOverride,\n} from '../models/InferenceOverride.js';\nimport type { InferenceOverrideCollection } from '../models/InferenceOverrideCollection.js';\nimport {\n type InferenceTenantTargetPolicy,\n isTenantValueAllowed,\n} from './override-service.js';\nimport { parseInferenceBinding } from './targets.js';\n\n/**\n * The settings layers' config shape: core's `InferenceConfig`, whose\n * `fusions` member is #4753's typed `InferenceFusionDefinition` map.\n */\nexport type InferenceSettingsConfig = InferenceConfig;\n\n/** The stored layers, each as an `InferenceConfig` fragment. */\nexport interface InferenceOverrideLayers {\n app: InferenceSettingsConfig;\n tenant: InferenceSettingsConfig;\n /** Tenant bindings present but refused by the allow-list or policy at read time. */\n blockedTenantBindings: string[];\n /** Raw rows, for screens that show each scope. */\n appRows: Map<string, InferenceOverride>;\n tenantRows: Map<string, InferenceOverride>;\n}\n\n/**\n * Merge config layers, lowest first. Core's merge (#4753) already replaces\n * `fusions` whole per name, so this is `mergeInferenceConfigs()`.\n */\nexport function mergeSettingsConfigs(\n ...layers: Array<InferenceSettingsConfig | undefined | null>\n): InferenceSettingsConfig | undefined {\n return mergeInferenceConfigs(...layers);\n}\n\nfunction layerFromRows(\n rows: Map<string, InferenceOverride>,\n include: (binding: string) => boolean,\n): InferenceSettingsConfig {\n const layer: InferenceSettingsConfig = {};\n for (const [binding, row] of rows) {\n if (!include(binding)) continue;\n const parsed = parseInferenceBinding(binding);\n if (!parsed) continue;\n let value: unknown;\n try {\n value = row.getValue();\n } catch {\n // A row written by an older or foreign writer that no longer validates\n // never takes effect; the settings screen shows it as raw state.\n continue;\n }\n if (value === null) continue;\n switch (parsed.kind) {\n case 'tier':\n layer.tiers ??= {};\n layer.tiers[parsed.name as InferenceTier] = value as InferenceTarget;\n break;\n case 'default-tier':\n layer.defaultTier = value as InferenceTier;\n break;\n case 'alias':\n layer.aliases ??= {};\n layer.aliases[parsed.name as string] = value as InferenceAlias;\n break;\n case 'fusion':\n layer.fusions ??= {};\n layer.fusions[parsed.name as string] =\n value as InferenceFusionDefinition;\n break;\n }\n }\n return layer;\n}\n\n/** The raw rows of the app scope and (with a tenant) one tenant scope. */\nexport interface InferenceOverrideRows {\n tenantId: string | null;\n appRows: Map<string, InferenceOverride>;\n tenantRows: Map<string, InferenceOverride>;\n}\n\n/** Two bounded reads: the app scope and, with `tenantId`, that tenant's. */\nexport async function readInferenceOverrideRows(\n overrides: InferenceOverrideCollection,\n tenantId: string | null | undefined,\n): Promise<InferenceOverrideRows> {\n const tenant = tenantId ?? null;\n const [appRows, tenantRows] = await Promise.all([\n overrides.getScopeMap('app', APP_INFERENCE_SCOPE_ID),\n tenant\n ? overrides.getScopeMap('tenant', tenant)\n : Promise.resolve(new Map<string, InferenceOverride>()),\n ]);\n return { tenantId: tenant, appRows, tenantRows };\n}\n\n/**\n * Read the app layer and (with `tenantId`) the tenant layer; the tenant\n * layer keeps only tier and default-tier rows the allow-list and policy\n * still allow.\n */\nexport async function loadInferenceOverrideLayers(\n overrides: InferenceOverrideCollection,\n options: {\n tenantId?: string | null;\n tenantTargetPolicy?: InferenceTenantTargetPolicy;\n } = {},\n): Promise<InferenceOverrideLayers> {\n return buildInferenceOverrideLayers(\n await readInferenceOverrideRows(overrides, options.tenantId),\n options.tenantTargetPolicy,\n );\n}\n\n/**\n * Layers from rows already read. Applies the allow-list and the policy hook\n * now, so a caller that caches rows (the override resolver) still re-checks\n * the current policy on every use.\n */\nexport async function buildInferenceOverrideLayers(\n rows: InferenceOverrideRows,\n tenantTargetPolicy?: InferenceTenantTargetPolicy,\n): Promise<InferenceOverrideLayers> {\n const { tenantId, appRows, tenantRows } = rows;\n const options = { tenantTargetPolicy };\n const app = layerFromRows(appRows, () => true);\n const blocked: string[] = [];\n const allowedTenant = new Set<string>();\n if (tenantId) {\n for (const [binding, row] of tenantRows) {\n const parsed = parseInferenceBinding(binding);\n if (parsed?.kind !== 'tier' && parsed?.kind !== 'default-tier') continue;\n let value: unknown;\n try {\n value = row.getValue();\n } catch {\n continue;\n }\n if (value === null) continue;\n let allowList: string[] | null = null;\n try {\n allowList = appRows.get(binding)?.getTenantAllowList() ?? null;\n } catch {\n allowList = null;\n }\n if (\n await isTenantValueAllowed(\n binding,\n value,\n tenantId,\n allowList,\n options.tenantTargetPolicy,\n )\n ) {\n allowedTenant.add(binding);\n } else {\n blocked.push(binding);\n }\n }\n }\n const tenant = layerFromRows(tenantRows, (binding) =>\n allowedTenant.has(binding),\n );\n return {\n app,\n tenant,\n blockedTenantBindings: blocked.sort(),\n appRows,\n tenantRows,\n };\n}\n","/**\n * User inference choices as the smrt-preferences kind `inference` (#4754):\n * mode, provider, base URL, model and default tier, at the tenant default\n * and per user. Never a key: the validator refuses credential-shaped members\n * (keys go to smrt-secrets through the host).\n *\n * smrt-preferences is not a dependency: the kind is a plain definition the\n * host registers with `registerPreferenceKind(inferencePreferenceKind)` (and\n * the two permissions with smrt-users' `registerPermissionDefinitions`), and\n * {@link createInferencePreferences} takes the store structurally.\n */\nimport {\n INFERENCE_TIERS,\n type InferenceTier,\n isInferenceTier,\n} from '@happyvertical/smrt-core';\nimport {\n findKeyMember,\n INFERENCE_MODES,\n type InferenceMode,\n} from './targets.js';\n\nexport const INFERENCE_PREFERENCE_KIND = 'inference';\n\n/** Default surface id: one inference choice per tenant and user. */\nexport const INFERENCE_PREFERENCE_SURFACE = 'default';\n\n/** Authorizes changing the tenant's default inference choices. */\nexport const CUSTOMIZE_INFERENCE_PERMISSION = 'inference.customize';\n\n/** Authorizes a user to keep their own inference choices. */\nexport const PERSONALIZE_INFERENCE_PERMISSION = 'inference.personalize';\n\n/** For smrt-users' `registerPermissionDefinitions()`. */\nexport const INFERENCE_PERMISSION_DEFINITIONS = [\n {\n slug: CUSTOMIZE_INFERENCE_PERMISSION,\n category: 'inference',\n name: 'Customize Inference',\n description:\n 'Change the default inference mode, provider and model everyone in the tenant uses',\n },\n {\n slug: PERSONALIZE_INFERENCE_PERMISSION,\n category: 'inference',\n name: 'Personalize Inference',\n description:\n 'Keep a personal inference mode, provider and model on top of the tenant default',\n },\n] as const;\n\n/** The `inference` preference payload. Every member is optional. */\nexport interface InferencePreferences {\n mode?: InferenceMode;\n provider?: string;\n baseUrl?: string;\n model?: string;\n defaultTier?: InferenceTier;\n}\n\n/** Structural copy of smrt-preferences' issue shape. */\nexport interface InferencePreferenceIssue {\n path: string | null;\n code: string;\n message: string;\n}\n\nconst PROVIDER_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;\n// Visible characters only: no whitespace or control characters.\nconst MODEL_PATTERN = /^[!-~\\u00a1-\\uffff]{1,200}$/;\n\nfunction issue(path: string, message: string): InferencePreferenceIssue {\n return { path, code: 'invalid_setting', message };\n}\n\nfunction validBaseUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return (\n (url.protocol === 'https:' || url.protocol === 'http:') &&\n !url.username &&\n !url.password &&\n !url.search &&\n !url.hash\n );\n } catch {\n return false;\n }\n}\n\n/**\n * Validate an `inference` payload: unknown or invalid members are removed\n * and reported, credential-shaped members refuse the whole payload.\n */\nexport function validateInferencePreferences(payload: unknown): {\n ok: boolean;\n canonical: InferencePreferences | null;\n issues: InferencePreferenceIssue[];\n} {\n if (payload === null || payload === undefined) {\n return { ok: true, canonical: null, issues: [] };\n }\n if (typeof payload !== 'object' || Array.isArray(payload)) {\n return {\n ok: false,\n canonical: null,\n issues: [issue('', 'inference preferences are an object')],\n };\n }\n const keyMember = findKeyMember(payload);\n if (keyMember) {\n return {\n ok: false,\n canonical: null,\n issues: [\n issue(\n keyMember,\n 'keys are never stored as preferences; they go to smrt-secrets',\n ),\n ],\n };\n }\n const input = payload as Record<string, unknown>;\n const issues: InferencePreferenceIssue[] = [];\n const canonical: InferencePreferences = {};\n for (const [member, value] of Object.entries(input)) {\n if (value === undefined || value === null || value === '') continue;\n switch (member) {\n case 'mode':\n if ((INFERENCE_MODES as readonly unknown[]).includes(value)) {\n canonical.mode = value as InferenceMode;\n } else {\n issues.push(\n issue(member, `mode is one of ${INFERENCE_MODES.join(', ')}`),\n );\n }\n break;\n case 'provider':\n if (typeof value === 'string' && PROVIDER_PATTERN.test(value)) {\n canonical.provider = value;\n } else issues.push(issue(member, 'invalid provider id'));\n break;\n case 'baseUrl':\n if (typeof value === 'string' && validBaseUrl(value)) {\n canonical.baseUrl = value;\n } else {\n issues.push(\n issue(\n member,\n 'the base URL is an http(s) URL without credentials, query or fragment',\n ),\n );\n }\n break;\n case 'model':\n if (typeof value === 'string' && MODEL_PATTERN.test(value)) {\n canonical.model = value;\n } else issues.push(issue(member, 'invalid model id'));\n break;\n case 'defaultTier':\n if (isInferenceTier(value)) canonical.defaultTier = value;\n else {\n issues.push(\n issue(\n member,\n `the default tier is one of ${INFERENCE_TIERS.join(', ')}`,\n ),\n );\n }\n break;\n default:\n issues.push(issue(member, 'unknown inference preference'));\n }\n }\n return {\n ok: issues.length === 0,\n canonical: Object.keys(canonical).length > 0 ? canonical : null,\n issues,\n };\n}\n\n/** The kind definition for smrt-preferences' `registerPreferenceKind()`. */\nexport const inferencePreferenceKind = {\n kind: INFERENCE_PREFERENCE_KIND,\n formatVersion: 1,\n permissions: {\n tenant: CUSTOMIZE_INFERENCE_PERMISSION,\n user: PERSONALIZE_INFERENCE_PERMISSION,\n },\n validate(payload: unknown) {\n return validateInferencePreferences(payload);\n },\n};\n\ntype WriteResult =\n | { ok: true; revision?: string | null; [extra: string]: unknown }\n | { ok: false; reason: string; [extra: string]: unknown };\n\n/** The slice of smrt-preferences' `PreferenceStore` this layer uses. */\nexport interface InferencePreferenceStoreLike {\n load(\n kind: string,\n surfaceId: string,\n ): Promise<{\n tenant: { payload: unknown; revision: string | null };\n user?: { payload: unknown; revision: string | null } | null;\n issues: InferencePreferenceIssue[];\n canCustomize: { tenant: boolean; user: boolean };\n }>;\n save(\n kind: string,\n surfaceId: string,\n input: {\n scope: 'tenant' | 'user';\n payload: unknown;\n revision: string | null;\n },\n ): Promise<WriteResult>;\n reset(\n kind: string,\n surfaceId: string,\n input: { scope: 'tenant' | 'user'; revision?: string | null },\n ): Promise<WriteResult>;\n}\n\n/** What a settings screen reads. */\nexport interface InferencePreferenceRead {\n /** Tenant default with the user's choices on top (member by member). */\n choices: InferencePreferences;\n tenant: InferencePreferences | null;\n user: InferencePreferences | null;\n revisions: { tenant: string | null; user: string | null };\n issues: InferencePreferenceIssue[];\n canCustomize: { tenant: boolean; user: boolean };\n}\n\n/** Read and write the `inference` preference over a preference store. */\nexport function createInferencePreferences(\n store: InferencePreferenceStoreLike,\n surfaceId: string = INFERENCE_PREFERENCE_SURFACE,\n) {\n return {\n async read(): Promise<InferencePreferenceRead> {\n const state = await store.load(INFERENCE_PREFERENCE_KIND, surfaceId);\n const tenant =\n (state.tenant.payload as InferencePreferences | null) ?? null;\n const user = (state.user?.payload as InferencePreferences | null) ?? null;\n return {\n choices: { ...(tenant ?? {}), ...(user ?? {}) },\n tenant,\n user,\n revisions: {\n tenant: state.tenant.revision,\n user: state.user?.revision ?? null,\n },\n issues: state.issues,\n canCustomize: state.canCustomize,\n };\n },\n\n /** Save choices at a scope (default `user`); `revision` is the one the client loaded. */\n write(\n choices: InferencePreferences,\n options: { scope?: 'tenant' | 'user'; revision: string | null },\n ): Promise<WriteResult> {\n return store.save(INFERENCE_PREFERENCE_KIND, surfaceId, {\n scope: options.scope ?? 'user',\n payload: choices,\n revision: options.revision,\n });\n },\n\n reset(\n options: { scope?: 'tenant' | 'user'; revision?: string | null } = {},\n ): Promise<WriteResult> {\n return store.reset(INFERENCE_PREFERENCE_KIND, surfaceId, {\n scope: options.scope ?? 'user',\n revision: options.revision,\n });\n },\n };\n}\n\nexport type InferencePreferencesApi = ReturnType<\n typeof createInferencePreferences\n>;\n","/**\n * An `InferenceResolver` that applies the stored app and tenant bindings\n * (#4754) at call time: config < app row < allowed tenant row. A tier with\n * no stored binding defers to `base` (another resolver, e.g. #4756's\n * per-user one) or to core's built-in config resolution, so an empty table\n * changes nothing.\n *\n * Aliases defined only in app rows are not visible to the executor (it reads\n * the process config), so a stored binding to such an alias is returned as\n * the alias's inline candidate chain, expanded recursively; aliases that the\n * config defines stay references.\n */\nimport {\n type InferenceAlias,\n type InferenceCandidate,\n type InferenceResolver,\n type InferenceTarget,\n type InferenceTier,\n inferenceTargetKind,\n resolveAutoTier,\n type SmrtClassOptions,\n type SmrtCollectionOptions,\n} from '@happyvertical/smrt-core';\nimport { validateInferenceTarget } from '../config.js';\nimport { InferenceOverrideCollection } from '../models/InferenceOverrideCollection.js';\nimport { getInferenceOverrideGeneration } from './cache.js';\nimport {\n buildInferenceOverrideLayers,\n type InferenceOverrideRows,\n mergeSettingsConfigs,\n readInferenceOverrideRows,\n} from './layers.js';\nimport type { InferenceTenantTargetPolicy } from './override-service.js';\n\n/** Options for {@link createInferenceOverrideResolver}. */\nexport interface InferenceOverrideResolverOptions {\n /** Database options for the override collection (list bounds are stripped). */\n db?: SmrtClassOptions;\n /** An existing collection instead of `db`. */\n overrides?: InferenceOverrideCollection;\n /** Resolver to defer to for tiers with no stored binding. */\n base?: InferenceResolver;\n /** The code half of the tenant allow-list (same hook as the settings service). */\n tenantTargetPolicy?: InferenceTenantTargetPolicy;\n /**\n * How long a tenant's layers are reused, ms. Default 30 000. Any write in\n * this process invalidates immediately; other processes see it after TTL.\n */\n ttlMs?: number;\n}\n\nconst MAX_CACHED_TENANTS = 500;\n\nfunction expandStoredAliases(\n target: InferenceTarget,\n appAliases: Record<string, InferenceAlias>,\n configAliases: Record<string, InferenceAlias>,\n): InferenceTarget {\n if (inferenceTargetKind(target) !== 'alias') return target;\n const name = target.alias as string;\n // A config alias the app row does not replace: the executor resolves it.\n if (!Object.hasOwn(appAliases, name)) return target;\n const expand = (alias: string, seen: Set<string>): InferenceCandidate[] => {\n if (seen.has(alias)) return []; // validated cycle-free before this runs\n const next = new Set(seen).add(alias);\n const definition = appAliases[alias] ?? configAliases[alias];\n return (definition?.candidates ?? []).flatMap((candidate) =>\n candidate.alias && Object.hasOwn(appAliases, candidate.alias)\n ? expand(candidate.alias, next)\n : [candidate],\n );\n };\n return { kind: 'candidates', candidates: expand(name, new Set()) };\n}\n\n/** See the module doc. */\nexport function createInferenceOverrideResolver(\n options: InferenceOverrideResolverOptions = {},\n): InferenceResolver {\n const ttlMs = options.ttlMs ?? 30_000;\n const cache = new Map<\n string,\n { at: number; generation: number; rows: InferenceOverrideRows }\n >();\n let collection: Promise<InferenceOverrideCollection> | undefined;\n\n const getCollection = () => {\n if (options.overrides) return Promise.resolve(options.overrides);\n collection ??= InferenceOverrideCollection.create({\n ...(options.db ?? {}),\n defaultListLimit: undefined,\n maxListLimit: undefined,\n } as SmrtCollectionOptions) as Promise<InferenceOverrideCollection>;\n return collection;\n };\n\n // Rows are cached, never layers: the allow-list and the policy hook are\n // applied again on every resolution, so narrowing the policy takes effect\n // on the next call without an override write.\n const rowsFor = async (tenantId: string | null) => {\n const key = tenantId ?? '';\n const generation = getInferenceOverrideGeneration();\n const hit = cache.get(key);\n if (hit && hit.generation === generation && Date.now() - hit.at < ttlMs) {\n return hit.rows;\n }\n const rows = await readInferenceOverrideRows(\n await getCollection(),\n tenantId,\n );\n cache.delete(key);\n cache.set(key, { at: Date.now(), generation, rows });\n if (cache.size > MAX_CACHED_TENANTS) {\n const oldest = cache.keys().next().value;\n if (oldest !== undefined) cache.delete(oldest);\n }\n return rows;\n };\n\n return {\n async resolve(request, context) {\n const tenantId =\n typeof request.tenantId === 'string' && request.tenantId\n ? request.tenantId\n : null;\n const layers = await buildInferenceOverrideLayers(\n await rowsFor(tenantId),\n options.tenantTargetPolicy,\n );\n const merged = mergeSettingsConfigs(\n context.config,\n layers.app,\n layers.tenant,\n );\n const tier: InferenceTier =\n request.tier === 'auto' ? resolveAutoTier(merged) : request.tier;\n const stored = layers.tenant.tiers?.[tier] ?? layers.app.tiers?.[tier];\n if (!stored) {\n const next = { ...request, tier };\n const deferred = options.base\n ? await options.base.resolve(next, context)\n : undefined;\n if (deferred !== undefined) return deferred;\n // `auto` may move to a stored default tier; resolve that tier from\n // config so the caller still sees the concrete tier.\n return request.tier === 'auto'\n ? context.resolveFromConfig(next)\n : undefined;\n }\n // Only the requested tier's chain is checked: a dangling reference\n // elsewhere fails that binding, not every stored tier.\n validateInferenceTarget(stored, `inference.tiers.${tier}`, merged);\n return {\n tierResolved: tier,\n target: expandStoredAliases(\n { ...stored },\n (layers.app.aliases ?? {}) as Record<string, InferenceAlias>,\n (context.config?.aliases ?? {}) as Record<string, InferenceAlias>,\n ),\n via: 'resolver',\n };\n },\n };\n}\n","/**\n * Server half of the inference settings screen (#4754), mirroring\n * smrt-prompts' `PromptSettingsService`: row shapes for the panel, revert\n * previews computed explicitly per scope, and writes delegated to the\n * authorizer-gated {@link InferenceOverrideService} (read-only without an\n * authorizer). Test connection and the doctor reuse `./node` (#4752).\n *\n * Makes no authorization decision of its own: call it only from server code\n * that has already established who the caller is.\n */\nimport { AI_PROVIDER_TYPES } from '@happyvertical/ai';\nimport { getDefaultAIKeyEnvName } from '@happyvertical/smrt-config';\nimport {\n type AiUsageReport,\n type AiUsageReportDimension,\n getInferenceConfig,\n INFERENCE_PRESET_IDS,\n INFERENCE_PRESETS,\n INFERENCE_TIERS,\n type InferenceAlias,\n type InferenceCandidate,\n type InferenceConfig,\n type InferenceTarget,\n type InferenceTier,\n reportAiUsage,\n resolveAutoTier,\n type SmrtClassOptions,\n type SmrtCollectionOptions,\n} from '@happyvertical/smrt-core';\nimport { findInferenceConfigIssues } from '../config.js';\nimport { getFusion, listFusions } from '../fusion.js';\nimport { APP_INFERENCE_SCOPE_ID } from '../models/InferenceOverride.js';\nimport {\n InferenceOverrideCollection,\n type InferenceOverrideScopeType,\n} from '../models/InferenceOverrideCollection.js';\nimport {\n checkInferenceBindings,\n type InferenceDoctorReport,\n inferenceProbeUrl,\n probeInferenceCandidate,\n} from '../node.js';\nimport type { InferenceConfigIssue } from '../types.js';\nimport {\n type InferenceKeyRecord,\n inferenceKeyFor,\n redactInferenceSecret,\n} from './keys.js';\nimport {\n type InferenceOverrideLayers,\n type InferenceSettingsConfig,\n loadInferenceOverrideLayers,\n mergeSettingsConfigs,\n} from './layers.js';\nimport {\n type InferenceOverrideAuthorizer,\n InferenceOverrideService,\n type InferenceTenantTargetPolicy,\n} from './override-service.js';\nimport {\n DEFAULT_TIER_BINDING,\n describeInferenceTarget,\n INFERENCE_BINDING_NAME_PATTERN,\n InferenceBindingValueError,\n stripKeyMembers,\n tierBinding,\n} from './targets.js';\n\n/** Which level supplied an effective value. */\nexport type InferenceSettingsLevel = 'default' | 'config' | 'app' | 'tenant';\n\n/** One tier, rendered for a settings screen. */\nexport interface InferenceTierSettingsRow {\n binding: `tier:${InferenceTier}`;\n tier: InferenceTier;\n /** What the tier resolves to for the requested tenant; `null` is the configured client. */\n effectiveTarget: InferenceTarget | null;\n effectiveLabel: string;\n supplyingLevel: InferenceSettingsLevel;\n /** The `inference` config's binding (code default + config files + env). */\n configTarget: InferenceTarget | null;\n /** The app row's raw value, `null` when none. */\n appTarget: InferenceTarget | null;\n /**\n * The tenant row's raw value, `null` when none or no tenant was requested.\n * Present even when {@link tenantBlocked}: the screen shows why it is not\n * in effect.\n */\n tenantTarget: InferenceTarget | null;\n /** What reverting the **app** scope produces: the config binding. */\n appRevertTarget: InferenceTarget | null;\n appRevertLabel: string;\n /**\n * What reverting the **tenant** scope produces: config plus the app row.\n * `null` label when no tenant was requested (nothing computed it).\n */\n inheritedTarget: InferenceTarget | null;\n inheritedLabel: string | null;\n /** The tenant allow-list on the app row (`null`: tenants may bind nothing). */\n tenantAllowList: string[] | null;\n /** A tenant row exists but the allow-list or policy refuses it now. */\n tenantBlocked: boolean;\n}\n\n/** `auto`'s default tier, rendered for a settings screen. */\nexport interface InferenceDefaultTierSetting {\n binding: typeof DEFAULT_TIER_BINDING;\n effectiveTier: InferenceTier;\n supplyingLevel: InferenceSettingsLevel;\n configTier: InferenceTier | null;\n appTier: InferenceTier | null;\n tenantTier: InferenceTier | null;\n /** What reverting the app scope produces (config, else `good`). */\n appRevertTier: InferenceTier;\n /** What reverting the tenant scope produces; `null` when no tenant was requested. */\n inheritedTier: InferenceTier | null;\n tenantAllowList: string[] | null;\n tenantBlocked: boolean;\n}\n\n/** One alias for the alias editor. */\nexport interface InferenceAliasSetting {\n name: string;\n /** Where the effective definition comes from (an app row replaces config whole). */\n source: 'config' | 'app';\n candidates: InferenceCandidate[];\n description?: string;\n /** True when config also defines it (reverting the app row restores that). */\n inConfig: boolean;\n}\n\n/** One fusion for the fusion list (#4753 owns their meaning). */\nexport interface InferenceFusionSetting {\n name: string;\n source: 'registry' | 'config' | 'app';\n /** The definition as JSON-safe data. */\n definition: Record<string, unknown>;\n description?: string;\n /** Config entries (app rows) can be edited as JSON; registry fusions are code. */\n editable: boolean;\n}\n\n/** One provider choice for the default view. */\nexport interface InferenceProviderOption {\n id: string;\n label: string;\n /** `preset` (#4674 endpoint presets) or `provider` (the AI SDK list). */\n kind: 'preset' | 'provider';\n baseUrl?: string;\n requiresApiKey: boolean;\n /** Needs a base URL before a key can be bound (gateways). */\n needsBaseUrl?: boolean;\n /** A loopback server (local discovery applies). */\n local?: boolean;\n}\n\n/** What Test connection probes. Never carries a key: the host supplies the stored record. */\nexport interface InferenceConnectionInput {\n provider?: string;\n preset?: string;\n baseUrl?: string;\n model?: string;\n}\n\n/** Key-free result of a test. */\nexport interface InferenceConnectionResult {\n reachable: boolean;\n skipped?: boolean;\n status?: number;\n durationMs: number;\n detail: string;\n /** Whether a stored key was sent (only ever to its bound origin). */\n keySent: boolean;\n}\n\n/** A fusion the host's registry knows (soft dependency on #4753's `defineFusion`). */\nexport interface RegisteredFusionLike {\n key: string;\n description?: string;\n [member: string]: unknown;\n}\n\n/** Options for {@link InferenceSettingsService.create}. */\nexport interface InferenceSettingsServiceOptions {\n /** Host authorizer. Omit for a read-only (load-function) service: writes fail closed. */\n authorize?: InferenceOverrideAuthorizer;\n /** The code half of the tenant allow-list (#4667 caps hosted tiers here). */\n tenantTargetPolicy?: InferenceTenantTargetPolicy;\n /** The config layer. Default core's merged `getInferenceConfig()`. */\n config?: () => InferenceConfig | undefined;\n /**\n * Registered fusions. Default: #4753's process registry (`defineFusion()`).\n */\n registryFusions?: () => Iterable<RegisteredFusionLike>;\n}\n\n/** #4753's process registry as {@link RegisteredFusionLike} entries. */\nfunction defaultRegistryFusions(): RegisteredFusionLike[] {\n return listFusions().flatMap((key) => {\n const definition = getFusion(key);\n return definition ? [{ ...definition, key }] : [];\n });\n}\n\n/** Thrown when a write would leave the layered config invalid. Nothing is written. */\nexport class InferenceSettingsConfigError extends Error {\n readonly status = 400;\n constructor(readonly issues: InferenceConfigIssue[]) {\n super(\n `Inference binding refused: ${issues.map((issue) => `${issue.path}: ${issue.message}`).join('; ')}`,\n );\n this.name = 'InferenceSettingsConfigError';\n }\n}\n\n/** Thrown when removing an alias or fusion that something still names. Nothing is written. */\nexport class InferenceBindingInUseError extends Error {\n readonly status = 409;\n constructor(\n readonly binding: string,\n /** App-scope and config referrers. */\n readonly referrers: string[],\n /** How many tenant rows name it (never which tenants). */\n readonly tenantReferences: number,\n ) {\n const parts = [...referrers];\n if (tenantReferences > 0) {\n parts.push(`${tenantReferences} tenant binding(s)`);\n }\n super(`${binding} is still referenced by ${parts.join(', ')}`);\n this.name = 'InferenceBindingInUseError';\n }\n}\n\n/** See prompts' `withoutListBounds`: an enumeration read must never be capped. */\nfunction withoutListBounds(\n options: SmrtClassOptions | SmrtCollectionOptions,\n): SmrtCollectionOptions {\n return { ...options, defaultListLimit: undefined, maxListLimit: undefined };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction rawValue(\n layers: InferenceOverrideLayers,\n scope: 'app' | 'tenant',\n binding: string,\n): unknown {\n const row = (scope === 'app' ? layers.appRows : layers.tenantRows).get(\n binding,\n );\n if (!row) return null;\n try {\n return row.getValue();\n } catch {\n return null;\n }\n}\n\nfunction allowListOf(\n layers: InferenceOverrideLayers,\n binding: string,\n): string[] | null {\n try {\n return layers.appRows.get(binding)?.getTenantAllowList() ?? null;\n } catch {\n return null;\n }\n}\n\n/** A config target for a screen: `null` when empty, never with a credential. */\nfunction nonEmpty(target: InferenceTarget | undefined | null) {\n return target && Object.keys(target).length > 0\n ? stripKeyMembers(target)\n : null;\n}\n\nfunction bindingRefersTo(\n binding: string,\n value: unknown,\n kind: 'alias' | 'fusion',\n name: string,\n): boolean {\n if (!isRecord(value)) return false;\n if (binding.startsWith('tier:')) {\n return kind === 'alias'\n ? value.alias === name\n : value.fusion === name ||\n (value.kind === 'fusion' && value.fusion === name);\n }\n if (\n kind === 'alias' &&\n binding.startsWith('alias:') &&\n binding !== `alias:${name}`\n ) {\n return (\n Array.isArray(value.candidates) &&\n value.candidates.some(\n (candidate) => isRecord(candidate) && candidate.alias === name,\n )\n );\n }\n return false;\n}\n\nconst PRESET_LABELS: Record<string, string> = {\n openrouter: 'OpenRouter',\n lemonade: 'Lemonade (local)',\n 'llama.cpp': 'llama.cpp (local)',\n 'lm-studio': 'LM Studio (local)',\n};\n\n/**\n * Read and write side of an inference settings screen. See the module doc.\n */\nexport class InferenceSettingsService {\n private readonly writes: InferenceOverrideService;\n private readonly readConfig: () => InferenceConfig | undefined;\n\n constructor(\n private readonly overrides: InferenceOverrideCollection,\n private readonly options: InferenceSettingsServiceOptions = {},\n ) {\n this.writes = new InferenceOverrideService(\n overrides,\n options.authorize ?? (() => false),\n { tenantTargetPolicy: options.tenantTargetPolicy },\n );\n this.readConfig = options.config ?? getInferenceConfig;\n }\n\n /** Build a service over the given database options (list bounds stripped). */\n static async create(\n options: SmrtClassOptions = {},\n serviceOptions: InferenceSettingsServiceOptions = {},\n ): Promise<InferenceSettingsService> {\n const overrides = await InferenceOverrideCollection.create(\n withoutListBounds(options),\n );\n return new InferenceSettingsService(\n overrides as InferenceOverrideCollection,\n serviceOptions,\n );\n }\n\n private loadLayers(tenantId: string | null | undefined) {\n return loadInferenceOverrideLayers(this.overrides, {\n tenantId,\n tenantTargetPolicy: this.options.tenantTargetPolicy,\n });\n }\n\n /**\n * The effective config for a tenant: config < app rows < allowed tenant\n * rows. What the override resolver binds and the doctor checks.\n * Server-side only: config layers may carry literal keys, so never send\n * this to a browser (the row methods strip them).\n */\n async getEffectiveConfig(\n options: { tenantId?: string | null } = {},\n ): Promise<InferenceSettingsConfig | undefined> {\n const layers = await this.loadLayers(options.tenantId);\n return mergeSettingsConfigs(this.readConfig(), layers.app, layers.tenant);\n }\n\n /**\n * One row per tier (`cheap`, `good`, `great`, `best`): the effective\n * target, each scope's raw override, and both revert previews.\n */\n async listInferenceSettings(\n options: { tenantId?: string | null } = {},\n ): Promise<InferenceTierSettingsRow[]> {\n const tenantId = options.tenantId ?? null;\n const layers = await this.loadLayers(tenantId);\n const config = this.readConfig();\n return INFERENCE_TIERS.map((tier) => {\n const binding = tierBinding(tier);\n const configTarget = nonEmpty(config?.tiers?.[tier]);\n const appTarget = rawValue(\n layers,\n 'app',\n binding,\n ) as InferenceTarget | null;\n const tenantTarget = tenantId\n ? (rawValue(layers, 'tenant', binding) as InferenceTarget | null)\n : null;\n const tenantBlocked = layers.blockedTenantBindings.includes(binding);\n const tenantEffective = tenantBlocked ? null : tenantTarget;\n // Computed separately per scope (the prompts invariant): the app\n // revert never includes the app row; the tenant revert always does.\n const appRevertTarget = configTarget;\n const inheritedTarget = appTarget ?? configTarget;\n const effectiveTarget = tenantEffective ?? appTarget ?? configTarget;\n const supplyingLevel: InferenceSettingsLevel = tenantEffective\n ? 'tenant'\n : appTarget\n ? 'app'\n : configTarget\n ? 'config'\n : 'default';\n return {\n binding,\n tier,\n effectiveTarget,\n effectiveLabel: describeInferenceTarget(effectiveTarget),\n supplyingLevel,\n configTarget,\n appTarget,\n tenantTarget,\n appRevertTarget,\n appRevertLabel: describeInferenceTarget(appRevertTarget),\n inheritedTarget: tenantId ? inheritedTarget : null,\n inheritedLabel: tenantId\n ? describeInferenceTarget(inheritedTarget)\n : null,\n tenantAllowList: allowListOf(layers, binding),\n tenantBlocked,\n };\n });\n }\n\n /** `auto`'s default tier with the same per-scope shape. */\n async getDefaultTierSetting(\n options: { tenantId?: string | null } = {},\n ): Promise<InferenceDefaultTierSetting> {\n const tenantId = options.tenantId ?? null;\n const layers = await this.loadLayers(tenantId);\n const config = this.readConfig();\n const configTier = config?.defaultTier ?? null;\n const appTier = rawValue(\n layers,\n 'app',\n DEFAULT_TIER_BINDING,\n ) as InferenceTier | null;\n const tenantTier = tenantId\n ? (rawValue(\n layers,\n 'tenant',\n DEFAULT_TIER_BINDING,\n ) as InferenceTier | null)\n : null;\n const tenantBlocked =\n layers.blockedTenantBindings.includes(DEFAULT_TIER_BINDING);\n const tenantEffective = tenantBlocked ? null : tenantTier;\n const appRevertTier = resolveAutoTier(config);\n const inheritedTier = appTier ?? appRevertTier;\n return {\n binding: DEFAULT_TIER_BINDING,\n effectiveTier: tenantEffective ?? inheritedTier,\n supplyingLevel: tenantEffective\n ? 'tenant'\n : appTier\n ? 'app'\n : configTier\n ? 'config'\n : 'default',\n configTier,\n appTier,\n tenantTier,\n appRevertTier,\n inheritedTier: tenantId ? inheritedTier : null,\n tenantAllowList: allowListOf(layers, DEFAULT_TIER_BINDING),\n tenantBlocked,\n };\n }\n\n /** Every alias: config entries, replaced whole by app rows. Sorted by name. */\n async listInferenceAliases(): Promise<InferenceAliasSetting[]> {\n const layers = await this.loadLayers(null);\n const configAliases = (this.readConfig()?.aliases ?? {}) as Record<\n string,\n InferenceAlias\n >;\n const names = new Set([\n ...Object.keys(configAliases),\n ...Object.keys(layers.app.aliases ?? {}),\n ]);\n return [...names].sort().map((name) => {\n const app = layers.app.aliases?.[name];\n const alias = app ?? configAliases[name];\n const setting: InferenceAliasSetting = {\n name,\n source: app ? 'app' : 'config',\n candidates: Array.isArray(alias?.candidates)\n ? stripKeyMembers(alias.candidates)\n : [],\n inConfig: Object.hasOwn(configAliases, name),\n };\n if (alias?.description) setting.description = alias.description;\n return setting;\n });\n }\n\n /**\n * Fusions from the host's registry (code, read-only), the config's\n * `inference.fusions`, and app rows (editable JSON). A later source\n * shadows an earlier one by name.\n */\n async listInferenceFusions(): Promise<InferenceFusionSetting[]> {\n const layers = await this.loadLayers(null);\n const out = new Map<string, InferenceFusionSetting>();\n const registry = this.options.registryFusions ?? defaultRegistryFusions;\n for (const fusion of registry()) {\n if (!fusion || typeof fusion.key !== 'string') continue;\n const definition = stripKeyMembers(\n JSON.parse(JSON.stringify(fusion)) as Record<string, unknown>,\n );\n out.set(fusion.key, {\n name: fusion.key,\n source: 'registry',\n definition,\n description:\n typeof fusion.description === 'string'\n ? fusion.description\n : undefined,\n editable: false,\n });\n }\n const configFusions = (this.readConfig() as InferenceSettingsConfig)\n ?.fusions;\n if (isRecord(configFusions)) {\n for (const [name, definition] of Object.entries(configFusions)) {\n if (!isRecord(definition)) continue;\n out.set(name, {\n name,\n source: 'config',\n definition: stripKeyMembers(definition),\n description:\n typeof definition.description === 'string'\n ? definition.description\n : undefined,\n editable: false,\n });\n }\n }\n for (const [name, definition] of Object.entries(layers.app.fusions ?? {})) {\n out.set(name, {\n name,\n source: 'app',\n definition: definition as unknown as Record<string, unknown>,\n description:\n typeof definition.description === 'string'\n ? definition.description\n : undefined,\n editable: true,\n });\n }\n return [...out.values()].sort((a, b) => a.name.localeCompare(b.name));\n }\n\n /**\n * Bind (or, with `null`, revert) one tier at one scope. Tenant writes must\n * pass the allow-list; every write goes through the authorizer.\n */\n async setTierOverride(request: {\n tier: InferenceTier;\n scopeType: InferenceOverrideScopeType;\n scopeId: string;\n target: InferenceTarget | null;\n }): Promise<void> {\n if (!INFERENCE_TIERS.includes(request.tier)) {\n throw new InferenceBindingValueError(\n `tier:${String(request.tier)}`,\n 'unknown tier',\n );\n }\n const binding = tierBinding(request.tier);\n if (request.target !== null) {\n await this.assertConfigStaysValid(request.scopeType, request.scopeId, {\n tiers: { [request.tier]: request.target },\n });\n }\n await this.writes.setValue(\n binding,\n request.scopeType,\n request.scopeId,\n request.target,\n );\n }\n\n /** Set (or revert) `auto`'s default tier at one scope. */\n async setDefaultTierOverride(request: {\n scopeType: InferenceOverrideScopeType;\n scopeId: string;\n tier: InferenceTier | null;\n }): Promise<void> {\n await this.writes.setValue(\n DEFAULT_TIER_BINDING,\n request.scopeType,\n request.scopeId,\n request.tier === null ? null : { tier: request.tier },\n );\n }\n\n /** Set (or remove) the tenant allow-list of a tier or of the default tier. */\n async setTenantAllowList(request: {\n binding: `tier:${InferenceTier}` | typeof DEFAULT_TIER_BINDING;\n allowList: string[] | null;\n }): Promise<void> {\n await this.writes.setTenantAllowList(request.binding, request.allowList);\n }\n\n /** Define (or, with `null`, remove) an app-level alias. */\n async setAlias(request: {\n name: string;\n alias: InferenceAlias | null;\n }): Promise<void> {\n const binding = `alias:${request.name}`;\n if (!INFERENCE_BINDING_NAME_PATTERN.test(String(request.name))) {\n throw new InferenceBindingValueError(binding, 'invalid alias name');\n }\n if (request.alias !== null) {\n await this.assertConfigStaysValid('app', APP_INFERENCE_SCOPE_ID, {\n aliases: { [request.name]: request.alias },\n });\n }\n await this.writes.setValue(\n binding,\n 'app',\n APP_INFERENCE_SCOPE_ID,\n request.alias,\n {\n // After authorization: the check reads rows of every tenant.\n beforeWrite: (normalized) =>\n normalized === null\n ? this.assertNotReferenced('alias', request.name)\n : Promise.resolve(),\n },\n );\n }\n\n /** Store (or, with `null`, remove) an app-level fusion config entry (#4753 shape). */\n async setFusion(request: {\n name: string;\n definition: Record<string, unknown> | null;\n }): Promise<void> {\n const binding = `fusion:${request.name}`;\n if (!INFERENCE_BINDING_NAME_PATTERN.test(String(request.name))) {\n throw new InferenceBindingValueError(binding, 'invalid fusion name');\n }\n await this.writes.setValue(\n binding,\n 'app',\n APP_INFERENCE_SCOPE_ID,\n request.definition,\n {\n // After authorization: the check reads rows of every tenant.\n beforeWrite: (normalized) =>\n normalized === null\n ? this.assertNotReferenced('fusion', request.name)\n : Promise.resolve(),\n },\n );\n }\n\n /**\n * Refuse removing an app alias or fusion that a stored binding (any\n * scope, any tenant) or the config still names, unless config or the\n * registry keeps defining it. Otherwise the removal would leave dangling\n * references that fail at call time.\n */\n private async assertNotReferenced(\n kind: 'alias' | 'fusion',\n name: string,\n ): Promise<void> {\n const config = this.readConfig() as InferenceSettingsConfig | undefined;\n const defined =\n kind === 'alias'\n ? Object.hasOwn(config?.aliases ?? {}, name)\n : Object.hasOwn(config?.fusions ?? {}, name) ||\n [...(this.options.registryFusions?.() ?? [])].some(\n (fusion) => fusion?.key === name,\n );\n if (defined) return;\n const referrers: string[] = [];\n for (const [tier, target] of Object.entries(config?.tiers ?? {})) {\n if (bindingRefersTo(`tier:${tier}`, target, kind, name)) {\n referrers.push(`config tiers.${tier}`);\n }\n }\n for (const [alias, definition] of Object.entries(config?.aliases ?? {})) {\n if (bindingRefersTo(`alias:${alias}`, definition, kind, name)) {\n referrers.push(`config aliases.${alias}`);\n }\n }\n // Every scope: a tenant row bound to this name would dangle too. Tenant\n // rows are only counted; the error never names another tenant.\n let tenantReferences = 0;\n for (const row of await this.overrides.list({})) {\n let value: unknown;\n try {\n value = row.getValue();\n } catch {\n continue;\n }\n if (!bindingRefersTo(row.binding, value, kind, name)) continue;\n if (row.tenantId) tenantReferences += 1;\n else referrers.push(row.binding);\n }\n if (referrers.length > 0 || tenantReferences > 0) {\n throw new InferenceBindingInUseError(\n `${kind}:${name}`,\n referrers,\n tenantReferences,\n );\n }\n }\n\n /**\n * Refuse a write that adds a config issue (unknown alias, cycle, key\n * without endpoint, unknown preset) to the layered config it lands in.\n * Issues that already existed do not block unrelated writes.\n */\n private async assertConfigStaysValid(\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n change: InferenceConfig,\n ): Promise<void> {\n const layers = await this.loadLayers(\n scopeType === 'tenant' ? scopeId : null,\n );\n const before = mergeSettingsConfigs(\n this.readConfig(),\n layers.app,\n layers.tenant,\n );\n const after = mergeSettingsConfigs(before, change);\n const seen = new Set(\n findInferenceConfigIssues(before).map(\n (issue) => `${issue.code}:${issue.path}`,\n ),\n );\n const added = findInferenceConfigIssues(after).filter(\n (issue) => !seen.has(`${issue.code}:${issue.path}`),\n );\n if (added.length > 0) throw new InferenceSettingsConfigError(added);\n }\n\n /**\n * Test connection for the default view: probe the endpoint's cheapest\n * list call (`./node`'s `probeInferenceCandidate`, 3 s). The stored key\n * record (from the host's `SecretService`) is sent only when the probe URL\n * is on the key's bound origin; no environment key is read.\n */\n async testConnection(\n input: InferenceConnectionInput,\n options: {\n key?: InferenceKeyRecord | null;\n fetch?: typeof fetch;\n timeoutMs?: number;\n } = {},\n ): Promise<InferenceConnectionResult> {\n const candidate: InferenceCandidate = {};\n if (input.provider) candidate.provider = input.provider;\n if (input.preset) candidate.preset = input.preset;\n if (input.baseUrl) candidate.baseUrl = input.baseUrl;\n if (input.model) candidate.model = input.model;\n const url = inferenceProbeUrl(candidate);\n const apiKey = url ? inferenceKeyFor(options.key, url) : undefined;\n if (options.key && url && !apiKey) {\n let where = 'that endpoint';\n try {\n where = new URL(url).origin;\n } catch {\n // keep the generic wording\n }\n return {\n reachable: false,\n durationMs: 0,\n keySent: false,\n detail: `the stored key is bound to ${options.key.origin} and is never sent to ${where}; save a key for this endpoint`,\n };\n }\n // The probe refuses a key without its own baseUrl (#4752's pair rule), so\n // a key bound to a provider's default origin travels with that endpoint\n // named explicitly: the base of the very URL the key was checked against.\n const probed: InferenceCandidate =\n apiKey && url\n ? {\n ...candidate,\n baseUrl:\n candidate.baseUrl ?? url.replace(/\\/(?:models|api\\/tags)$/, ''),\n apiKey,\n }\n : candidate;\n const result = await probeInferenceCandidate(probed, {\n env: {},\n fetch: options.fetch,\n timeoutMs: options.timeoutMs,\n });\n return {\n ...result,\n detail: redactInferenceSecret(result.detail, apiKey),\n keySent: !!apiKey,\n };\n }\n\n /**\n * Per-candidate test for the alias editor. A candidate already in the\n * effective config (config file or an app row) reads its key from\n * `apiKeyEnv` (or its provider's variable) exactly as the doctor does,\n * sent only to its own endpoint; any other candidate (an unsaved edit) is\n * probed with no key at all.\n */\n async testCandidate(\n candidate: InferenceCandidate,\n options: {\n env?: Record<string, string | undefined>;\n fetch?: typeof fetch;\n timeoutMs?: number;\n } = {},\n ): Promise<InferenceConnectionResult> {\n if (findKeyLike(candidate)) {\n throw new InferenceBindingValueError(\n 'candidate',\n 'a tested candidate never carries a literal key',\n );\n }\n // A key variable is read only for a candidate an authorized writer\n // already configured (config or an app row). An arbitrary candidate\n // naming `apiKeyEnv` with its own `baseUrl` would otherwise carry a\n // server key to an endpoint the caller chose.\n const configured = await this.isConfiguredCandidate(candidate);\n const result = await probeInferenceCandidate(candidate, {\n ...options,\n env: configured ? (options.env ?? process.env) : {},\n });\n return { ...result, keySent: false };\n }\n\n private async isConfiguredCandidate(\n candidate: InferenceCandidate,\n ): Promise<boolean> {\n const config = await this.getEffectiveConfig();\n const wanted = canonicalCandidate(candidate);\n return Object.values(config?.aliases ?? {}).some((alias) =>\n (alias?.candidates ?? []).some(\n (entry) => canonicalCandidate(entry) === wanted,\n ),\n );\n }\n\n /** `smrt doctor --ai` over the effective config for a tenant. */\n async doctor(\n options: {\n tenantId?: string | null;\n probe?: boolean;\n env?: Record<string, string | undefined>;\n fetch?: typeof fetch;\n } = {},\n ): Promise<InferenceDoctorReport> {\n const config = await this.getEffectiveConfig({\n tenantId: options.tenantId,\n });\n return checkInferenceBindings({\n config: config ?? {},\n probe: options.probe,\n env: options.env,\n fetch: options.fetch,\n });\n }\n\n /**\n * The Usage tab: `reportAiUsage()` (#4751) grouped by model, tier and/or\n * prompt key. Pass `by: ['model']` where tier and prompt tags do not exist\n * yet; NULL dimensions are their own bucket, never dropped.\n */\n usage(\n options: {\n by?: Array<Extract<AiUsageReportDimension, 'model' | 'tier' | 'prompt'>>;\n since?: Date;\n until?: Date;\n tenantId?: string | null;\n } = {},\n ): Promise<AiUsageReport> {\n return reportAiUsage(this.overrides.db, {\n ...options,\n by: options.by && options.by.length > 0 ? options.by : ['model'],\n });\n }\n\n /** Local discovery for the model field (#4674's loopback probe). */\n async discoverModels(): Promise<\n Array<{ service: string; baseUrl: string; models: string[] }>\n > {\n const { discoverLocalInferenceModels } = await import(\n '@happyvertical/smrt-core/byo-inference'\n );\n const found = await discoverLocalInferenceModels();\n return found\n .filter((entry) => entry.available)\n .map((entry) => ({\n service: entry.service,\n baseUrl: entry.baseUrl,\n models: entry.available ? entry.models : [],\n }));\n }\n\n /** Provider choices: #4674 presets, then the AI SDK provider list. */\n listProviderOptions(): InferenceProviderOption[] {\n const presets: InferenceProviderOption[] = INFERENCE_PRESET_IDS.map(\n (id) => {\n const preset = INFERENCE_PRESETS[id];\n return {\n id,\n label: PRESET_LABELS[id] ?? id,\n kind: 'preset',\n baseUrl: preset.baseUrl,\n requiresApiKey: preset.requiresApiKey,\n local: !preset.requiresApiKey,\n };\n },\n );\n const providers: InferenceProviderOption[] = AI_PROVIDER_TYPES.map(\n (id) => ({\n id,\n label: id,\n kind: 'provider' as const,\n requiresApiKey: !!getDefaultAIKeyEnvName(id),\n ...(id === 'litellm' || id === 'bifrost' ? { needsBaseUrl: true } : {}),\n ...(id === 'ollama' ? { local: true } : {}),\n }),\n );\n return [...presets, ...providers];\n }\n}\n\nfunction canonicalCandidate(candidate: InferenceCandidate): string {\n const fields = [\n 'provider',\n 'model',\n 'baseUrl',\n 'apiKeyEnv',\n 'preset',\n 'alias',\n ];\n return JSON.stringify(\n fields.map((field) => {\n const value = (candidate as Record<string, unknown>)[field];\n return typeof value === 'string' ? value.trim() : null;\n }),\n );\n}\n\nfunction findKeyLike(candidate: InferenceCandidate): boolean {\n return (\n typeof (candidate as Record<string, unknown>).apiKey === 'string' &&\n ((candidate as Record<string, unknown>).apiKey as string).length > 0\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** The current write generation. */
2
+ export declare function getInferenceOverrideGeneration(): number;
3
+ /** Record a write: cached layers read before it are stale. */
4
+ export declare function bumpInferenceOverrideGeneration(): void;
5
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/settings/cache.ts"],"names":[],"mappings":"AAWA,oCAAoC;AACpC,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,8DAA8D;AAC9D,wBAAgB,+BAA+B,IAAI,IAAI,CAGtD"}
@@ -0,0 +1,70 @@
1
+ /** What the host stores in smrt-secrets for one provider key. */
2
+ export interface InferenceKeyRecord {
3
+ provider: string;
4
+ preset?: string;
5
+ /** The base URL the key was entered for, when one was given. */
6
+ baseUrl?: string;
7
+ /** The only origin this key is ever sent to. */
8
+ origin: string;
9
+ apiKey: string;
10
+ }
11
+ /** Thrown when a key cannot be bound to an origin. Nothing should be stored. */
12
+ export declare class InferenceKeyOriginError extends Error {
13
+ readonly status = 400;
14
+ constructor(reason: string);
15
+ }
16
+ /**
17
+ * The origin a key for this endpoint is bound to, or `null` when none can be
18
+ * known (an unknown provider without a base URL, or a gateway without one).
19
+ */
20
+ export declare function inferenceKeyOrigin(input: {
21
+ provider?: string;
22
+ preset?: string;
23
+ baseUrl?: string;
24
+ }): string | null;
25
+ /**
26
+ * Bind a key to its endpoint's origin. The host stores the returned record
27
+ * through `SecretService` and hands the panel back only `keySet: true`.
28
+ *
29
+ * @throws {InferenceKeyOriginError} when the key is blank or no origin can be known.
30
+ */
31
+ export declare function bindInferenceKey(input: {
32
+ provider: string;
33
+ key: string;
34
+ preset?: string;
35
+ baseUrl?: string;
36
+ }): InferenceKeyRecord;
37
+ /**
38
+ * The #4756 stored provider config (`inference.provider-config`, written
39
+ * through the user resolver's `write(level, config)`) for a bound key: the
40
+ * one place a panel-saved key is stored, so the resolver uses it. The
41
+ * binding is self-contained: a preset key keeps its preset (the preset
42
+ * supplies the endpoint), a provider key names its endpoint explicitly
43
+ * (its base URL, else the provider default it was bound to), and the
44
+ * endpoint is checked against the bound origin again.
45
+ *
46
+ * @throws {InferenceKeyOriginError} when no endpoint on the bound origin is
47
+ * known, or a preset key has no model.
48
+ */
49
+ export declare function inferenceProviderConfigFromKey(record: InferenceKeyRecord, choice?: {
50
+ model?: string;
51
+ }): {
52
+ provider?: string;
53
+ preset?: string;
54
+ baseUrl?: string;
55
+ model?: string;
56
+ apiKey: string;
57
+ };
58
+ /**
59
+ * The key to send to `url`, or `undefined` when `url` is not on the key's
60
+ * bound origin (or is not a URL). The only way a stored key reaches a request.
61
+ */
62
+ export declare function inferenceKeyFor(record: InferenceKeyRecord | null | undefined, url: string): string | undefined;
63
+ /** A short placeholder for a stored key; never derived from the key itself. */
64
+ export declare const REDACTED_KEY = "[redacted]";
65
+ /**
66
+ * Remove a key (and common key shapes) from text bound for a screen or a
67
+ * log. Used on every test-connection detail before it leaves the server.
68
+ */
69
+ export declare function redactInferenceSecret(text: string, secret?: string): string;
70
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../src/settings/keys.ts"],"names":[],"mappings":"AAiBA,iEAAiE;AACjE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gFAAgF;AAChF,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,MAAM,OAAO;gBACV,MAAM,EAAE,MAAM;CAI3B;AA0CD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,GAAG,IAAI,CAahB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,kBAAkB,CAiBrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,kBAAkB,EAC1B,MAAM,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B;IACD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB,CAmDA;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,EAC7C,GAAG,EAAE,MAAM,GACV,MAAM,GAAG,SAAS,CAOpB;AAED,+EAA+E;AAC/E,eAAO,MAAM,YAAY,eAAe,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAM3E"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Layered read of `_smrt_inference_overrides` (#4754): the code default
3
+ * (core's built-in resolution), the `inference` config, the app row, then
4
+ * the tenant row, as in smrt-prompts. A tenant row takes effect only while
5
+ * the app row's allow-list and the policy hook still allow it.
6
+ */
7
+ import { type InferenceConfig } from '@happyvertical/smrt-core';
8
+ import { type InferenceOverride } from '../models/InferenceOverride.js';
9
+ import type { InferenceOverrideCollection } from '../models/InferenceOverrideCollection.js';
10
+ import { type InferenceTenantTargetPolicy } from './override-service.js';
11
+ /**
12
+ * The settings layers' config shape: core's `InferenceConfig`, whose
13
+ * `fusions` member is #4753's typed `InferenceFusionDefinition` map.
14
+ */
15
+ export type InferenceSettingsConfig = InferenceConfig;
16
+ /** The stored layers, each as an `InferenceConfig` fragment. */
17
+ export interface InferenceOverrideLayers {
18
+ app: InferenceSettingsConfig;
19
+ tenant: InferenceSettingsConfig;
20
+ /** Tenant bindings present but refused by the allow-list or policy at read time. */
21
+ blockedTenantBindings: string[];
22
+ /** Raw rows, for screens that show each scope. */
23
+ appRows: Map<string, InferenceOverride>;
24
+ tenantRows: Map<string, InferenceOverride>;
25
+ }
26
+ /**
27
+ * Merge config layers, lowest first. Core's merge (#4753) already replaces
28
+ * `fusions` whole per name, so this is `mergeInferenceConfigs()`.
29
+ */
30
+ export declare function mergeSettingsConfigs(...layers: Array<InferenceSettingsConfig | undefined | null>): InferenceSettingsConfig | undefined;
31
+ /** The raw rows of the app scope and (with a tenant) one tenant scope. */
32
+ export interface InferenceOverrideRows {
33
+ tenantId: string | null;
34
+ appRows: Map<string, InferenceOverride>;
35
+ tenantRows: Map<string, InferenceOverride>;
36
+ }
37
+ /** Two bounded reads: the app scope and, with `tenantId`, that tenant's. */
38
+ export declare function readInferenceOverrideRows(overrides: InferenceOverrideCollection, tenantId: string | null | undefined): Promise<InferenceOverrideRows>;
39
+ /**
40
+ * Read the app layer and (with `tenantId`) the tenant layer; the tenant
41
+ * layer keeps only tier and default-tier rows the allow-list and policy
42
+ * still allow.
43
+ */
44
+ export declare function loadInferenceOverrideLayers(overrides: InferenceOverrideCollection, options?: {
45
+ tenantId?: string | null;
46
+ tenantTargetPolicy?: InferenceTenantTargetPolicy;
47
+ }): Promise<InferenceOverrideLayers>;
48
+ /**
49
+ * Layers from rows already read. Applies the allow-list and the policy hook
50
+ * now, so a caller that caches rows (the override resolver) still re-checks
51
+ * the current policy on every use.
52
+ */
53
+ export declare function buildInferenceOverrideLayers(rows: InferenceOverrideRows, tenantTargetPolicy?: InferenceTenantTargetPolicy): Promise<InferenceOverrideLayers>;
54
+ //# sourceMappingURL=layers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/settings/layers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAEL,KAAK,eAAe,EAKrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AAC5F,OAAO,EACL,KAAK,2BAA2B,EAEjC,MAAM,uBAAuB,CAAC;AAG/B;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,eAAe,CAAC;AAEtD,gEAAgE;AAChE,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,uBAAuB,CAAC;IAC7B,MAAM,EAAE,uBAAuB,CAAC;IAChC,oFAAoF;IACpF,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,kDAAkD;IAClD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACxC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,MAAM,EAAE,KAAK,CAAC,uBAAuB,GAAG,SAAS,GAAG,IAAI,CAAC,GAC3D,uBAAuB,GAAG,SAAS,CAErC;AA0CD,0EAA0E;AAC1E,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IACxC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;CAC5C;AAED,4EAA4E;AAC5E,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,2BAA2B,EACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,OAAO,CAAC,qBAAqB,CAAC,CAShC;AAED;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,2BAA2B,EACtC,OAAO,GAAE;IACP,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kBAAkB,CAAC,EAAE,2BAA2B,CAAC;CAC7C,GACL,OAAO,CAAC,uBAAuB,CAAC,CAKlC;AAED;;;;GAIG;AACH,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,qBAAqB,EAC3B,kBAAkB,CAAC,EAAE,2BAA2B,GAC/C,OAAO,CAAC,uBAAuB,CAAC,CAgDlC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Authorized write path for `_smrt_inference_overrides` (#4754), mirroring
3
+ * smrt-prompts' `PromptOverrideService` (#3013): every write names its exact
4
+ * scope, asks the host authorizer, and fails closed. Tenant writes of a tier
5
+ * or the default tier must also pass the tenant allow-list (the app row's
6
+ * stored list, then the host's `tenantTargetPolicy` hook).
7
+ */
8
+ import type { InferenceTier } from '@happyvertical/smrt-core';
9
+ import type { InferenceOverrideCollection, InferenceOverrideScopeType } from '../models/InferenceOverrideCollection.js';
10
+ /** One override write the host must authorize before it runs. */
11
+ export interface InferenceOverrideWriteRequest {
12
+ operation: 'set' | 'remove' | 'allow-list';
13
+ binding: string;
14
+ scopeType: InferenceOverrideScopeType;
15
+ scopeId: string;
16
+ /** `set`: the normalized value (`null` reverts the scope to what it inherits). */
17
+ value?: unknown;
18
+ /** `allow-list`: the normalized list (`null` removes it). */
19
+ tenantAllowList?: string[] | null;
20
+ }
21
+ /**
22
+ * The host's authorization decision for one write. Return `true` only when
23
+ * the current caller may write that exact scope; anything else (including a
24
+ * throw) denies.
25
+ */
26
+ export type InferenceOverrideAuthorizer = (request: InferenceOverrideWriteRequest) => boolean | Promise<boolean>;
27
+ /** What the tenant-target policy hook is asked. */
28
+ export interface InferenceTenantTargetRequest {
29
+ binding: string;
30
+ /** The tier being bound (`tier:*` bindings). */
31
+ tier?: InferenceTier;
32
+ /** The allow-list reference of the requested target (`model:…`, `alias:…`, `fusion:…`, `tier:…`). */
33
+ ref: string;
34
+ /** The normalized target (a tier target, or a tier name for `default-tier`). */
35
+ value: unknown;
36
+ tenantId: string;
37
+ }
38
+ /**
39
+ * The code half of the tenant allow-list, after the stored list on the app
40
+ * row: return `true` only when the tenant may bind `ref`. This is the hook
41
+ * #4667 uses to cap the maximum hosted tier per plan. It runs on every
42
+ * tenant write and on every read that layers a tenant row, so narrowing it
43
+ * stops an existing tenant binding from taking effect.
44
+ */
45
+ export type InferenceTenantTargetPolicy = (request: InferenceTenantTargetRequest) => boolean | Promise<boolean>;
46
+ /** Thrown when the authorizer does not return `true`. Nothing is written. */
47
+ export declare class InferenceOverrideAuthorizationError extends Error {
48
+ readonly request: InferenceOverrideWriteRequest;
49
+ readonly status = 403;
50
+ constructor(request: InferenceOverrideWriteRequest);
51
+ }
52
+ /** Thrown when a tenant write names a target the allow-list does not open. Nothing is written. */
53
+ export declare class InferenceTenantTargetNotAllowedError extends Error {
54
+ readonly binding: string;
55
+ readonly ref: string;
56
+ readonly status = 403;
57
+ constructor(binding: string, ref: string);
58
+ }
59
+ /** Thrown for a scope the layered read never consults. Checked before the authorizer. */
60
+ export declare class InvalidInferenceScopeError extends Error {
61
+ readonly scopeType: string;
62
+ readonly scopeId: string;
63
+ readonly status = 400;
64
+ constructor(scopeType: string, scopeId: string, reason: string);
65
+ }
66
+ /** Reject any scope the layered read does not consult. */
67
+ export declare function assertValidInferenceScope(scopeType: InferenceOverrideScopeType, scopeId: string): void;
68
+ /** Normalize the value of one binding (throws {@link InferenceBindingValueError}). */
69
+ export declare function normalizeBindingValue(binding: string, value: unknown): unknown;
70
+ /** The allow-list reference of a normalized tier or default-tier value. */
71
+ export declare function bindingValueRef(binding: string, value: unknown): string;
72
+ /**
73
+ * Whether a tenant may hold `value` for `binding`: the app row's stored
74
+ * list, then the policy hook. A throwing policy denies.
75
+ */
76
+ export declare function isTenantValueAllowed(binding: string, value: unknown, tenantId: string, allowList: readonly string[] | null, policy: InferenceTenantTargetPolicy | undefined): Promise<boolean>;
77
+ /** Options for {@link InferenceOverrideService}. */
78
+ export interface InferenceOverrideServiceOptions {
79
+ /** See {@link InferenceTenantTargetPolicy}. */
80
+ tenantTargetPolicy?: InferenceTenantTargetPolicy;
81
+ }
82
+ /**
83
+ * The only write path a host should use for `_smrt_inference_overrides`.
84
+ * Construct it with an authorizer bound to the current caller; every write
85
+ * asks it first and fails closed.
86
+ */
87
+ export declare class InferenceOverrideService {
88
+ private readonly overrides;
89
+ private readonly authorize;
90
+ private readonly options;
91
+ constructor(overrides: InferenceOverrideCollection, authorize: InferenceOverrideAuthorizer, options?: InferenceOverrideServiceOptions);
92
+ /**
93
+ * Set (or, with `null`, revert) one binding at one scope. Tenants may write
94
+ * only `tier:*` and `default-tier`, and only an allow-listed target;
95
+ * reverting is always allowed.
96
+ *
97
+ * @throws {InvalidInferenceScopeError} for a scope nothing reads.
98
+ * @throws {InferenceBindingValueError} for an invalid binding or value.
99
+ * @throws {InferenceOverrideAuthorizationError} when the authorizer declines.
100
+ * @throws {InferenceTenantTargetNotAllowedError} when the allow-list refuses.
101
+ */
102
+ setValue(binding: string, scopeType: InferenceOverrideScopeType, scopeId: string, value: unknown, options?: {
103
+ /**
104
+ * Runs after the authorizer allowed the write and before it lands; a
105
+ * check that reads other rows belongs here, never before
106
+ * authorization.
107
+ */
108
+ beforeWrite?: (normalized: unknown) => Promise<void>;
109
+ }): Promise<void>;
110
+ /**
111
+ * Set (or, with `null`, remove) the tenant allow-list of a `tier:*` or
112
+ * `default-tier` binding. The list lives on the app row.
113
+ */
114
+ setTenantAllowList(binding: string, allowList: string[] | null): Promise<void>;
115
+ /** Delete the whole row (value and, on the app row, the allow-list). */
116
+ removeOverride(binding: string, scopeType: InferenceOverrideScopeType, scopeId: string): Promise<boolean>;
117
+ private require;
118
+ }
119
+ //# sourceMappingURL=override-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"override-service.d.ts","sourceRoot":"","sources":["../../src/settings/override-service.ts"],"names":[],"mappings":"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"}