@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/AGENTS.md CHANGED
@@ -67,8 +67,9 @@ the user resolver's `write(level, config)`; never a second secret type.
67
67
  `./node` provisions per-user gateway keys through `ai.admin` of the
68
68
  `bifrost` / `litellm` provider (`SMRT_INFERENCE_HOSTED_*`, read once), storing
69
69
  the key only in the user's `inference.provider-config` (`source: 'hosted'`).
70
- Budget update and key delete wait on happyvertical/sdk#1494 (feature-detected;
71
- revoke ends local use first and records owed deletions). `./gate`'s
70
+ Plan changes update the key budget, revoke ends local use and then deletes the
71
+ key (failures are owed and retried by reconcile), and the tenant project is
72
+ looked up before one is created (`@happyvertical/ai` >= 0.111.7). `./gate`'s
72
73
  `registerHostedInferenceGate({ killSwitch, spend })` is core's
73
74
  `InferenceGate`: kill switch `inference.hosted.enabled` (a global DISABLE
74
75
  always wins), tenant, entitlement, then plan spend caps; a block throws
@@ -156,8 +157,9 @@ its own `baseUrl` core's pair rule refuses it.
156
157
  ## Fusions (#4753)
157
158
 
158
159
  `{ fusion: '<key>' }` tier targets run consumer-defined fusions (`single` 1
159
- call, `pair` 3, `handoff` 2) from `inference.fusions` (wins) or
160
- `defineFusion()`. SMRT ships the interpreter, never a fusion. Seats are
160
+ call, `pair` 3, `handoff` 2) from, highest first, the resolver's stored app
161
+ rows (core's `lookupFusion()`, #4800), `inference.fusions`, or
162
+ `defineFusion()`; every source is re-validated before it runs. SMRT ships the interpreter, never a fusion. Seats are
161
163
  smrt-prompts keys run through `createChainClient()`; a dead seat never
162
164
  aborts a `pair`, all seats dead throws `InferenceFusionError`; each run writes
163
165
  one `_smrt_ai_runs` row. Fusions are defined at app scope only; a tenant
@@ -188,7 +190,10 @@ the `_smrt_inference_overrides` model (ships through `smrt db:migrate`).
188
190
  Keys go to smrt-secrets through the host and are bound to their endpoint's
189
191
  origin; user choices are the smrt-preferences kind `inference`; app/tenant
190
192
  bindings are override rows, tenants limited to the app row's allow-list and
191
- the `tenantTargetPolicy` hook. Details: [agents/settings.md](agents/settings.md),
193
+ the `tenantTargetPolicy` hook. The app's transcript setting (#4781) is the
194
+ app-only `transcripts` row, editable in the panel only with `appEditable`,
195
+ applied as core's transcript app settings layer; the Usage tab shows
196
+ transcript counts and links to a host route, never content. Details: [agents/settings.md](agents/settings.md),
192
197
  consumer guide [docs/content/inference-settings.md](../../docs/content/inference-settings.md).
193
198
 
194
199
  ## Validation
package/agents/fusions.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  `src/fusion.ts` runs `{ fusion: '<key>' }` tier targets. SMRT ships the
4
4
  interpreter and the vocabulary, never a fusion: modes are code, fusions are
5
- config. A fusion comes from `inference.fusions.<key>` (app config, wins) or
5
+ config. A fusion comes from, highest first (each replaces a lower one whole):
6
+ the registered resolver's stored layer through core's
7
+ `context.lookupFusion(key)` (#4800; the override resolver's app rows),
8
+ `inference.fusions.<key>` (app config), or
6
9
  `defineFusion({ key, mode, seats, gate?, description? })` (process registry
7
10
  on `globalThis`). A seat is `{ name?, prompt, target, temperature?, output? }`:
8
11
  `prompt` is a smrt-prompts key whose resolved template (code default,
@@ -88,5 +91,15 @@ await contract.do('Summarize this contract', { tier: 'best' }); // unchanged cal
88
91
  - **Fusions are app-scope only.** A fusion multiplies hosted model spend
89
92
  per call, so a tenant never defines or redefines one; with #4754's
90
93
  allow-list a tenant may bind a tier to a listed fusion (`fusion:<n>`).
91
- The executor reads fusions from app config and `defineFusion()`; a fusion
92
- defined only in an app override row fails `fusion-unknown` until #4800.
94
+ - **Stored fusions (#4800).** Core hands the executor
95
+ `context.lookupFusion(key)`: the instance's (or registered) resolver's
96
+ optional `lookupFusion(key, request, context)`, then the merged
97
+ `inference.fusions`; `undefined` falls to the registry. Without it (older
98
+ cores) the executor reads config, then the registry, and validates at bind
99
+ time; with it the lookup starts at bind time and an unknown or invalid
100
+ fusion fails the first call before any seat runs (nothing recorded). Every
101
+ definition, whatever its source, is validated before it runs (structure
102
+ against the call's aliases, every seat prompt key); a resolver's answer
103
+ fails under path `resolver.fusions.<key>`. The #4752 validating wrapper
104
+ and the #4756 user resolver forward `lookupFusion` to their base /
105
+ `fallback` (stored user and tenant configs never define a fusion).
package/agents/hosted.md CHANGED
@@ -11,18 +11,29 @@
11
11
  hand).
12
12
 
13
13
  - **Gateway.** Admin calls go through `ai.admin` (`AIAdminInterface`) of the
14
- `bifrost` / `litellm` provider of `@happyvertical/ai`; never reimplement
15
- gateway administration here. The SDK has `createProject` and
16
- `createVirtualKey` only: `updateVirtualKey` / `deleteVirtualKey` are
17
- feature-detected on `HostedGatewayAdmin` (happyvertical/sdk#1494). Without
18
- them budget sync returns `unsupported` (state keeps the old budget so a
19
- later sync retries). Revoke ends local use before touching the gateway
20
- (config restored, state retired with the key owed, then the delete); a
21
- deletion the gateway cannot perform (`unsupported`,
22
- `failed`, or a key a failed provision left) goes to the state's
23
- `pendingGatewayRevocations` (id + alias, never the key), is audited with
24
- `manualGatewayRevocation`, and is retried by `revoke()` /
25
- `reconcileTenant()`.
14
+ `bifrost` / `litellm` provider of `@happyvertical/ai` >= 0.111.7
15
+ (happyvertical/sdk#1494); never reimplement gateway administration here.
16
+ `HostedGatewayAdmin` is `HOSTED_GATEWAY_ADMIN_METHODS` (`createProject`,
17
+ `createVirtualKey`, `updateVirtualKey`, `deleteVirtualKey`,
18
+ `listProjects`); a provider missing one is `HOSTED_GATEWAY_UNSUPPORTED`.
19
+ SMRT persists only `AIVirtualKey.id`, the non-secret handle (Bifrost id,
20
+ LiteLLM `token_id`); an id that equals or looks like a key value (`sk-…`)
21
+ is dropped on create and when older state is read, so that key counts as
22
+ having no id. Budget sync sends
23
+ `updateVirtualKey(id, { budget })` only (Bifrost refuses `models` /
24
+ `metadata`, LiteLLM `providerConfigs`: `ADMIN_UNSUPPORTED_FIELD`);
25
+ `unsupported` now means the grant has no key id. Revoke ends local use
26
+ before touching the gateway (config restored, state retired with the key
27
+ owed, then `deleteVirtualKey`; `false` is `missing`, an already revoked
28
+ key, nothing owed). A deletion the gateway did not perform (`failed`, a
29
+ key a failed provision left, `unsupported`: no admin configured or no key
30
+ id; entries from before 0.111.7 also read `unsupported`) goes to the
31
+ state's `pendingGatewayRevocations` (id + alias, never the key), is
32
+ audited with `manualGatewayRevocation`, and is retried by `revoke()` /
33
+ `reconcileTenant()`; each retry that settles one writes
34
+ `inference.hosted.revoke-retry` with `settled: [{ keyId, keyAlias,
35
+ gateway }]`, closing those records. Entries without an id stay for an
36
+ operator (`acknowledgeGatewayRevocation`).
26
37
  - **Config, read once from the environment** (`getHostedGatewayConfig()`;
27
38
  unset provider = hosted off): `SMRT_INFERENCE_HOSTED_PROVIDER`
28
39
  (`bifrost|litellm`), `_BASE_URL` (the OpenAI-compatible endpoint keys are
@@ -43,7 +54,12 @@ hand).
43
54
  `inference.hosted:user:<id>` (category `inference.hosted`) holds status,
44
55
  generation, key id and alias, budget and the user's prior config;
45
56
  `inference.hosted:project` the tenant's gateway project (created once
46
- through `runOnce()`, LiteLLM id `smrt-<tenantId>-<nonce>`). Every write passes
57
+ through `runOnce()`, name `smrt tenant <tenantId>`, LiteLLM id
58
+ `smrt-<tenantId>-<nonce>`). Without a stored record, `listProjects({
59
+ tenantId })` runs first and adopts the lowest-id project with that name
60
+ (and id prefix on LiteLLM), audited `adopted: true`, so a crash between
61
+ create and persist never makes a second project; a failed lookup fails
62
+ the provision rather than create blind. Every write passes
47
63
  `auditDetails.operation` (`inference.hosted.provision|revoke|budget|project`)
48
64
  to `SecretAuditLog`.
49
65
  - **Idempotency and ordering.** An active grant whose binding is still the
@@ -50,7 +50,11 @@ Consumer guide: [docs/content/inference-settings.md](../../../docs/content/infer
50
50
  `inheritedTarget` = config + app row), plus `getDefaultTierSetting()`,
51
51
  `listInferenceAliases()`, `listInferenceFusions()` (registry: the
52
52
  `registryFusions` option, default #4753's `defineFusion()` registry;
53
- config `inference.fusions`; editable app rows). Writes go through `InferenceOverrideService`
53
+ config `inference.fusions`; editable app rows), each entry with
54
+ `shadows` (lower sources it replaces by name) and `issues` (what would make
55
+ the executor refuse it, prompt keys included), the executor's precedence
56
+ (#4800). `setFusion()` refuses credential members, then a definition that
57
+ adds a structural issue. Writes go through `InferenceOverrideService`
54
58
  (authorizer first, fails closed; without `authorize` the service is
55
59
  read-only) and refuse a value that adds a config issue to the layer it
56
60
  lands in; removing an app alias or fusion that any row (every tenant) or
@@ -61,7 +65,19 @@ Consumer guide: [docs/content/inference-settings.md](../../../docs/content/infer
61
65
  `getEffectiveConfig()` is server-side only. `testConnection()`, `testCandidate()` and `doctor()` reuse
62
66
  `./node`; `testCandidate()` reads a key variable only for a candidate
63
67
  already in the effective config (an unsaved one is probed keyless);
64
- `usage()` is `reportAiUsage()` by model (default), tier, prompt.
68
+ `usage()` is `reportAiUsage()` by model (default), tier, prompt, fusion,
69
+ template, with `transcripts: true` for per-bucket counts (#4781).
70
+ - **Transcript setting** (#4781, `src/settings/transcripts.ts`): binding
71
+ `transcripts`, app row only (service and model refuse a tenant row),
72
+ value `{ enabled?, sample?, maxBytes? }` checked by core's
73
+ `assertInferenceTranscriptsConfig()`. `setTranscriptSettings()` writes it
74
+ through the authorizer and installs it as core's
75
+ `setAiTranscriptAppSettings()` layer (config < app row < env); the
76
+ override resolver re-applies the app rows on every resolve (rows cached
77
+ for `ttlMs`), `loadInferenceTranscriptSettings()` is the worker startup
78
+ hook. `getTranscriptSettings()` adds the revert preview, the env override
79
+ (detected by forcing both values through the layer) and the sweep's
80
+ retention days, read-only (the sweep runs with its caller's policy).
65
81
  - **`createInferenceOverrideResolver({ db, base })`** applies stored
66
82
  bindings at call time and defers unbound tiers to `base` or config; an
67
83
  app-only alias is returned as its inline candidate chain because the
@@ -70,10 +86,14 @@ Consumer guide: [docs/content/inference-settings.md](../../../docs/content/infer
70
86
  allow-list and policy are re-applied on every call, and only the requested
71
87
  tier's chain is validated, so one dangling reference fails one tier.
72
88
  Compose it as the per-user resolver's `fallback` (see AGENTS.md,
73
- "Resolver composition"). App-row fusions are not yet visible to the
74
- executor (it reads the process config and the `defineFusion()` registry):
75
- a tier bound to a fusion defined only in an app row fails loudly
76
- (`fusion-unknown`) until stored fusions reach the executor (#4800).
89
+ "Resolver composition"). Its `lookupFusion()` (#4800) answers core's
90
+ executor lookup from app rows only (read with no tenant, so a tenant row
91
+ never defines a fusion), validated against config + app rows with every
92
+ seat prompt key (`InferenceConfigError`, paths `fusion:<name>…`), app-only
93
+ seat aliases inlined like tier aliases; an app row that exists but cannot
94
+ be read fails `fusion-invalid` (never its value) rather than deferring (the
95
+ list shows it as the app winner with that issue); no app row defers to
96
+ `base`, then config. An app row shadows a config fusion of the same name.
77
97
  - **Panel** (`src/svelte`): built like `PromptSettingsPanel` (presentational,
78
98
  `svelte` optional peer, prune script, two-project Vitest, `svelte-check`,
79
99
  named `$props()` interfaces, types free of core). The default view is the
@@ -81,4 +101,7 @@ Consumer guide: [docs/content/inference-settings.md](../../../docs/content/infer
81
101
  aliases, fusions) is not rendered until opened. The key input is its own
82
102
  form, never in state, cleared on submit. Tenant scope disables targets
83
103
  off the allow-list (display only) and makes aliases/fusions read-only.
84
- Rows remount on `contextKey`.
104
+ Rows remount on `contextKey`. The transcript section is read-only unless
105
+ `appEditable` and `onSaveTranscripts` are both passed; the Usage tab
106
+ links counts through the host's `transcriptHref` and renders no
107
+ transcript content (#4781).
@@ -1,6 +1,6 @@
1
- import { INFERENCE_TIERS, ObjectRegistry, SmrtCollection, SmrtObject, field, inferenceTargetKind, isInferenceTier, smrt } from "@happyvertical/smrt-core";
1
+ import { INFERENCE_TIERS, ObjectRegistry, SmrtCollection, SmrtObject, assertInferenceTranscriptsConfig, field, inferenceTargetKind, isInferenceTier, smrt } from "@happyvertical/smrt-core";
2
2
  //#region src/__smrt-register__.ts
3
- ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-inference","packageVersion":"0.60.15","objects":{"@happyvertical/smrt-inference:InferenceOverride":{"name":"inferenceoverride","className":"InferenceOverride","qualifiedName":"@happyvertical/smrt-inference:InferenceOverride","collection":"inferenceoverrides","filePath":"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverride.ts","packageName":"@happyvertical/smrt-inference","fields":{"binding":{"type":"text","required":true,"default":"","_meta":{"required":true}},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true}},"target":{"type":"text","required":false,"_meta":{"nullable":true}},"tenantAllowList":{"type":"text","required":false,"_meta":{"nullable":true}}},"methods":{"getValue":{"name":"getValue","async":false,"parameters":[],"returnType":"unknown","isStatic":false,"isPublic":true},"getTenantAllowList":{"name":"getTenantAllowList","async":false,"parameters":[],"returnType":"string[] | null","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_inference_overrides","conflictColumns":["binding","context"],"api":false,"cli":false,"mcp":false,"indexes":[{"name":"idx_smrt_inference_overrides_tenant","columns":["tenantId","binding"]}]},"extends":"SmrtObject","exportName":"InferenceOverride","collectionExportName":"InferenceOverrideCollection","validationRules":[{"field":"binding","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_inference_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_inference_overrides\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"binding\\" TEXT NOT NULL DEFAULT '',\\n \\"tenant_id\\" UUID,\\n \\"target\\" TEXT,\\n \\"tenant_allow_list\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"binding":{"type":"TEXT","notNull":true,"unique":false,"default":""},"tenant_id":{"type":"UUID","notNull":false,"unique":false},"target":{"type":"TEXT","notNull":false,"unique":false},"tenant_allow_list":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_inference_overrides_binding_context_idx","columns":["binding","context"],"unique":true},{"name":"_smrt_inference_overrides_slug_context_idx","columns":["slug","context"]},{"name":"idx_smrt_inference_overrides_tenant","columns":["tenant_id","binding"]},{"name":"_smrt_inference_overrides_created_at_idx","columns":["created_at"]}],"version":"4868b81b"},"importPath":"@happyvertical/smrt-inference/models"},"@happyvertical/smrt-inference:InferenceOverrideCollection":{"name":"inferenceoverridecollection","className":"InferenceOverrideCollection","qualifiedName":"@happyvertical/smrt-inference:InferenceOverrideCollection","collection":"inferenceoverrides","description":"Reads and unauthorized writes of _smrt_inference_overrides.","filePath":"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverrideCollection.ts","packageName":"@happyvertical/smrt-inference","fields":{},"methods":{"getScopeMap":{"name":"getScopeMap","async":true,"parameters":[{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<Map<string, InferenceOverride>>","isStatic":false,"isPublic":true},"findByScope":{"name":"findByScope","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<InferenceOverride | null>","isStatic":false,"isPublic":true},"setOverride":{"name":"setOverride","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false},{"name":"change","type":"object","optional":false,"memberTypes":["unknown","string[] | null"]}],"returnType":"Promise<InferenceOverride | null>","isStatic":false,"isPublic":true},"removeOverride":{"name":"removeOverride","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_inference_overrides","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"InferenceOverride","exportName":"InferenceOverrideCollection","collectionExportName":"InferenceOverrideCollectionCollection","collectionOf":"@happyvertical/smrt-inference:InferenceOverride","inheritedExposure":["api","mcp","cli"],"schema":{"tableName":"_smrt_inference_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_inference_overrides\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"binding\\" TEXT NOT NULL DEFAULT '',\\n \\"tenant_id\\" UUID,\\n \\"target\\" TEXT,\\n \\"tenant_allow_list\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"binding":{"type":"TEXT","notNull":true,"unique":false,"default":""},"tenant_id":{"type":"UUID","notNull":false,"unique":false},"target":{"type":"TEXT","notNull":false,"unique":false},"tenant_allow_list":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_inference_overrides_binding_context_idx","columns":["binding","context"],"unique":true},{"name":"_smrt_inference_overrides_slug_context_idx","columns":["slug","context"]},{"name":"idx_smrt_inference_overrides_tenant","columns":["tenant_id","binding"]},{"name":"_smrt_inference_overrides_created_at_idx","columns":["created_at"]}],"version":"4868b81b"},"importPath":"@happyvertical/smrt-inference/models"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-prompts","@happyvertical/smrt-secrets","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3617","#3624","#3626","#3627"],"via":["@happyvertical/smrt-profiles","@happyvertical/smrt-secrets","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
3
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-inference","packageVersion":"0.60.16","objects":{"@happyvertical/smrt-inference:InferenceOverride":{"name":"inferenceoverride","className":"InferenceOverride","qualifiedName":"@happyvertical/smrt-inference:InferenceOverride","collection":"inferenceoverrides","filePath":"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverride.ts","packageName":"@happyvertical/smrt-inference","fields":{"binding":{"type":"text","required":true,"default":"","_meta":{"required":true}},"tenantId":{"type":"text","required":false,"_meta":{"sqlType":"UUID","nullable":true}},"target":{"type":"text","required":false,"_meta":{"nullable":true}},"tenantAllowList":{"type":"text","required":false,"_meta":{"nullable":true}}},"methods":{"getValue":{"name":"getValue","async":false,"parameters":[],"returnType":"unknown","isStatic":false,"isPublic":true},"getTenantAllowList":{"name":"getTenantAllowList","async":false,"parameters":[],"returnType":"string[] | null","isStatic":false,"isPublic":true},"save":{"name":"save","async":true,"parameters":[{"name":"options","type":"SmrtSaveOptions","optional":true}],"returnType":"Promise<this>","isStatic":false,"isPublic":true},"delete":{"name":"delete","async":true,"parameters":[],"returnType":"Promise<void>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_inference_overrides","conflictColumns":["binding","context"],"api":false,"cli":false,"mcp":false,"indexes":[{"name":"idx_smrt_inference_overrides_tenant","columns":["tenantId","binding"]}]},"extends":"SmrtObject","exportName":"InferenceOverride","collectionExportName":"InferenceOverrideCollection","validationRules":[{"field":"binding","rule":"required","fieldType":"text"}],"schema":{"tableName":"_smrt_inference_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_inference_overrides\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"binding\\" TEXT NOT NULL DEFAULT '',\\n \\"tenant_id\\" UUID,\\n \\"target\\" TEXT,\\n \\"tenant_allow_list\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"binding":{"type":"TEXT","notNull":true,"unique":false,"default":""},"tenant_id":{"type":"UUID","notNull":false,"unique":false},"target":{"type":"TEXT","notNull":false,"unique":false},"tenant_allow_list":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_inference_overrides_binding_context_idx","columns":["binding","context"],"unique":true},{"name":"_smrt_inference_overrides_slug_context_idx","columns":["slug","context"]},{"name":"idx_smrt_inference_overrides_tenant","columns":["tenant_id","binding"]},{"name":"_smrt_inference_overrides_created_at_idx","columns":["created_at"]}],"version":"4868b81b"},"importPath":"@happyvertical/smrt-inference/models"},"@happyvertical/smrt-inference:InferenceOverrideCollection":{"name":"inferenceoverridecollection","className":"InferenceOverrideCollection","qualifiedName":"@happyvertical/smrt-inference:InferenceOverrideCollection","collection":"inferenceoverrides","description":"Reads and unauthorized writes of _smrt_inference_overrides.","filePath":"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverrideCollection.ts","packageName":"@happyvertical/smrt-inference","fields":{},"methods":{"getScopeMap":{"name":"getScopeMap","async":true,"parameters":[{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<Map<string, InferenceOverride>>","isStatic":false,"isPublic":true},"findByScope":{"name":"findByScope","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<InferenceOverride | null>","isStatic":false,"isPublic":true},"setOverride":{"name":"setOverride","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false},{"name":"change","type":"object","optional":false,"memberTypes":["unknown","string[] | null"]}],"returnType":"Promise<InferenceOverride | null>","isStatic":false,"isPublic":true},"removeOverride":{"name":"removeOverride","async":true,"parameters":[{"name":"binding","type":"string","optional":false},{"name":"scopeType","type":"InferenceOverrideScopeType","optional":false,"resolvedType":"'app' | 'tenant'"},{"name":"scopeId","type":"string","optional":false}],"returnType":"Promise<boolean>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"_smrt_inference_overrides","api":false,"mcp":false,"cli":false},"extends":"SmrtCollection","extendsTypeArg":"InferenceOverride","exportName":"InferenceOverrideCollection","collectionExportName":"InferenceOverrideCollectionCollection","collectionOf":"@happyvertical/smrt-inference:InferenceOverride","inheritedExposure":["api","mcp","cli"],"schema":{"tableName":"_smrt_inference_overrides","ddl":"CREATE TABLE IF NOT EXISTS \\"_smrt_inference_overrides\\" (\\n \\"_sync_origin_device_id\\" TEXT,\\n \\"_sync_revision\\" INTEGER,\\n \\"id\\" UUID PRIMARY KEY NOT NULL,\\n \\"slug\\" TEXT NOT NULL,\\n \\"context\\" TEXT NOT NULL DEFAULT '',\\n \\"created_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"updated_at\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\"binding\\" TEXT NOT NULL DEFAULT '',\\n \\"tenant_id\\" UUID,\\n \\"target\\" TEXT,\\n \\"tenant_allow_list\\" TEXT\\n);","columns":{"_sync_origin_device_id":{"type":"TEXT"},"_sync_revision":{"type":"INTEGER"},"id":{"type":"UUID","primaryKey":true,"referenceKind":"id","notNull":true},"slug":{"type":"TEXT","notNull":true},"context":{"type":"TEXT","notNull":true,"default":""},"created_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"updated_at":{"type":"TIMESTAMP","notNull":true,"default":"current_timestamp"},"binding":{"type":"TEXT","notNull":true,"unique":false,"default":""},"tenant_id":{"type":"UUID","notNull":false,"unique":false},"target":{"type":"TEXT","notNull":false,"unique":false},"tenant_allow_list":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"_smrt_inference_overrides_binding_context_idx","columns":["binding","context"],"unique":true},{"name":"_smrt_inference_overrides_slug_context_idx","columns":["slug","context"]},{"name":"idx_smrt_inference_overrides_tenant","columns":["tenant_id","binding"]},{"name":"_smrt_inference_overrides_created_at_idx","columns":["created_at"]}],"version":"4868b81b"},"importPath":"@happyvertical/smrt-inference/models"}},"moduleType":"smrt","smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-prompts","@happyvertical/smrt-secrets","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"],"browser":{"status":"server-only","issues":["#3617","#3624","#3626","#3627"],"via":["@happyvertical/smrt-profiles","@happyvertical/smrt-secrets","@happyvertical/smrt-tenancy","@happyvertical/smrt-users"]},"stampsConstructors":true}`));
4
4
  //#endregion
5
5
  //#region src/settings/cache.ts
6
6
  var GENERATION_KEY = /* @__PURE__ */ Symbol.for("smrt.inference.overrideGeneration");
@@ -20,6 +20,7 @@ var INFERENCE_MODES$1 = [
20
20
  "off"
21
21
  ];
22
22
  var DEFAULT_TIER_BINDING = "default-tier";
23
+ var TRANSCRIPTS_BINDING = "transcripts";
23
24
  var INFERENCE_BINDING_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
24
25
  var InferenceBindingValueError = class extends Error {
25
26
  constructor(binding, reason) {
@@ -33,6 +34,7 @@ var InferenceBindingValueError = class extends Error {
33
34
  function parseInferenceBinding(binding) {
34
35
  if (typeof binding !== "string") return null;
35
36
  if (binding === "default-tier") return { kind: "default-tier" };
37
+ if (binding === "transcripts") return { kind: "transcripts" };
36
38
  const match = /^(tier|alias|fusion):(.+)$/.exec(binding);
37
39
  if (!match) return null;
38
40
  const [, kind, name] = match;
@@ -185,6 +187,27 @@ function normalizeDefaultTier(binding, value) {
185
187
  if (!isInferenceTier(tier)) throw new InferenceBindingValueError(binding, `the default tier must be one of ${INFERENCE_TIERS.join(", ")}`);
186
188
  return tier;
187
189
  }
190
+ var TRANSCRIPT_SETTING_MEMBERS = /* @__PURE__ */ new Set([
191
+ "enabled",
192
+ "sample",
193
+ "maxBytes"
194
+ ]);
195
+ function normalizeTranscriptSettings(binding, value) {
196
+ if (value === null || value === void 0) return null;
197
+ if (!isRecord(value)) throw new InferenceBindingValueError(binding, "the transcript setting must be an object");
198
+ const extra = Object.keys(value).filter((member) => !TRANSCRIPT_SETTING_MEMBERS.has(member));
199
+ if (extra.length > 0) throw new InferenceBindingValueError(binding, `the transcript setting carries enabled, sample and maxBytes only (got ${extra.join(", ")})`);
200
+ try {
201
+ assertInferenceTranscriptsConfig(value);
202
+ } catch (error) {
203
+ throw new InferenceBindingValueError(binding, error instanceof Error ? error.message : String(error));
204
+ }
205
+ const out = {};
206
+ if (value.enabled !== void 0) out.enabled = value.enabled;
207
+ if (value.sample !== void 0) out.sample = value.sample;
208
+ if (value.maxBytes !== void 0) out.maxBytes = value.maxBytes;
209
+ return Object.keys(out).length > 0 ? out : null;
210
+ }
188
211
  function inferenceTargetRef(target) {
189
212
  const kind = inferenceTargetKind(target);
190
213
  const record = target;
@@ -284,6 +307,7 @@ var InferenceOverride = class extends SmrtObject {
284
307
  switch (parsed.kind) {
285
308
  case "tier": return normalizeTierTarget(this.binding, raw);
286
309
  case "default-tier": return normalizeDefaultTier(this.binding, raw);
310
+ case "transcripts": return normalizeTranscriptSettings(this.binding, raw);
287
311
  case "alias": return normalizeAliasDefinition(this.binding, raw);
288
312
  case "fusion": return normalizeFusionDefinition(this.binding, raw);
289
313
  }
@@ -294,6 +318,7 @@ var InferenceOverride = class extends SmrtObject {
294
318
  if (this.tenantId !== null) {
295
319
  if (typeof this.tenantId !== "string" || !this.tenantId.trim() || this.tenantId !== this.tenantId.trim() || this.tenantId === "__app__") throw new InferenceBindingValueError(this.binding, "invalid tenant id");
296
320
  if (parsed.kind === "alias" || parsed.kind === "fusion") throw new InferenceBindingValueError(this.binding, "aliases and fusions are defined at the app scope only");
321
+ if (parsed.kind === "transcripts") throw new InferenceBindingValueError(this.binding, "the transcript setting is app scope only");
297
322
  if (this.tenantAllowList !== null) throw new InferenceBindingValueError(this.binding, "the tenant allow-list lives on the app row");
298
323
  }
299
324
  if (this.tenantAllowList !== null && parsed.kind !== "tier" && parsed.kind !== "default-tier") throw new InferenceBindingValueError(this.binding, "only tier and default-tier bindings carry a tenant allow-list");
@@ -393,6 +418,6 @@ var InferenceOverrideCollection = class extends SmrtCollection {
393
418
  }
394
419
  };
395
420
  //#endregion
396
- export { normalizeTierTarget as _, INFERENCE_BINDING_NAME_PATTERN as a, tierBinding as b, describeInferenceTarget as c, isAllowedByList as d, matchesAllowPattern as f, normalizeFusionDefinition as g, normalizeDefaultTier as h, DEFAULT_TIER_BINDING as i, findKeyMember as l, normalizeAllowList as m, APP_INFERENCE_SCOPE_ID as n, INFERENCE_MODES$1 as o, normalizeAliasDefinition as p, InferenceOverride as r, InferenceBindingValueError as s, InferenceOverrideCollection as t, inferenceTargetRef as u, parseInferenceBinding as v, getInferenceOverrideGeneration as x, stripKeyMembers as y };
421
+ export { getInferenceOverrideGeneration as C, tierBinding as S, normalizeFusionDefinition as _, INFERENCE_BINDING_NAME_PATTERN as a, parseInferenceBinding as b, TRANSCRIPTS_BINDING as c, inferenceTargetRef as d, isAllowedByList as f, normalizeDefaultTier as g, normalizeAllowList as h, DEFAULT_TIER_BINDING as i, describeInferenceTarget as l, normalizeAliasDefinition as m, APP_INFERENCE_SCOPE_ID as n, INFERENCE_MODES$1 as o, matchesAllowPattern as p, InferenceOverride as r, InferenceBindingValueError as s, InferenceOverrideCollection as t, findKeyMember as u, normalizeTierTarget as v, stripKeyMembers as x, normalizeTranscriptSettings as y };
397
422
 
398
- //# sourceMappingURL=InferenceOverrideCollection-wl7sI-3C.js.map
423
+ //# sourceMappingURL=InferenceOverrideCollection-Dat4TrK5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InferenceOverrideCollection-Dat4TrK5.js","names":[],"sources":["../../src/__smrt-register__.ts","../../src/settings/cache.ts","../../src/settings/targets.ts","../../src/models/InferenceOverride.ts","../../src/models/InferenceOverrideCollection.ts"],"sourcesContent":["// Generated by @happyvertical/smrt-core smrtPlugin at library build time.\n// The package manifest is inlined so self-registration cannot miss when a\n// downstream bundler relocates this module away from dist/manifest.json\n// (https://github.com/happyvertical/smrt/issues/1506,\n// https://github.com/happyvertical/smrt/issues/1507).\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\nObjectRegistry.registerPackageManifest(JSON.parse(\"{\\\"version\\\":\\\"1.0.0\\\",\\\"timestamp\\\":0,\\\"packageName\\\":\\\"@happyvertical/smrt-inference\\\",\\\"packageVersion\\\":\\\"0.60.16\\\",\\\"objects\\\":{\\\"@happyvertical/smrt-inference:InferenceOverride\\\":{\\\"name\\\":\\\"inferenceoverride\\\",\\\"className\\\":\\\"InferenceOverride\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-inference:InferenceOverride\\\",\\\"collection\\\":\\\"inferenceoverrides\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverride.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-inference\\\",\\\"fields\\\":{\\\"binding\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":true,\\\"default\\\":\\\"\\\",\\\"_meta\\\":{\\\"required\\\":true}},\\\"tenantId\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"sqlType\\\":\\\"UUID\\\",\\\"nullable\\\":true}},\\\"target\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}},\\\"tenantAllowList\\\":{\\\"type\\\":\\\"text\\\",\\\"required\\\":false,\\\"_meta\\\":{\\\"nullable\\\":true}}},\\\"methods\\\":{\\\"getValue\\\":{\\\"name\\\":\\\"getValue\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"unknown\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"getTenantAllowList\\\":{\\\"name\\\":\\\"getTenantAllowList\\\",\\\"async\\\":false,\\\"parameters\\\":[],\\\"returnType\\\":\\\"string[] | null\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"save\\\":{\\\"name\\\":\\\"save\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"options\\\",\\\"type\\\":\\\"SmrtSaveOptions\\\",\\\"optional\\\":true}],\\\"returnType\\\":\\\"Promise<this>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"delete\\\":{\\\"name\\\":\\\"delete\\\",\\\"async\\\":true,\\\"parameters\\\":[],\\\"returnType\\\":\\\"Promise<void>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_inference_overrides\\\",\\\"conflictColumns\\\":[\\\"binding\\\",\\\"context\\\"],\\\"api\\\":false,\\\"cli\\\":false,\\\"mcp\\\":false,\\\"indexes\\\":[{\\\"name\\\":\\\"idx_smrt_inference_overrides_tenant\\\",\\\"columns\\\":[\\\"tenantId\\\",\\\"binding\\\"]}]},\\\"extends\\\":\\\"SmrtObject\\\",\\\"exportName\\\":\\\"InferenceOverride\\\",\\\"collectionExportName\\\":\\\"InferenceOverrideCollection\\\",\\\"validationRules\\\":[{\\\"field\\\":\\\"binding\\\",\\\"rule\\\":\\\"required\\\",\\\"fieldType\\\":\\\"text\\\"}],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_inference_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_inference_overrides\\\\\\\" (\\\\n \\\\\\\"_sync_origin_device_id\\\\\\\" TEXT,\\\\n \\\\\\\"_sync_revision\\\\\\\" INTEGER,\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"binding\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"tenant_id\\\\\\\" UUID,\\\\n \\\\\\\"target\\\\\\\" TEXT,\\\\n \\\\\\\"tenant_allow_list\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"_sync_origin_device_id\\\":{\\\"type\\\":\\\"TEXT\\\"},\\\"_sync_revision\\\":{\\\"type\\\":\\\"INTEGER\\\"},\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"binding\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"target\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"tenant_allow_list\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_inference_overrides_binding_context_idx\\\",\\\"columns\\\":[\\\"binding\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_inference_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"idx_smrt_inference_overrides_tenant\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"binding\\\"]},{\\\"name\\\":\\\"_smrt_inference_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"4868b81b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-inference/models\\\"},\\\"@happyvertical/smrt-inference:InferenceOverrideCollection\\\":{\\\"name\\\":\\\"inferenceoverridecollection\\\",\\\"className\\\":\\\"InferenceOverrideCollection\\\",\\\"qualifiedName\\\":\\\"@happyvertical/smrt-inference:InferenceOverrideCollection\\\",\\\"collection\\\":\\\"inferenceoverrides\\\",\\\"description\\\":\\\"Reads and unauthorized writes of _smrt_inference_overrides.\\\",\\\"filePath\\\":\\\"/home/runner/work/smrt/smrt/packages/inference/src/models/InferenceOverrideCollection.ts\\\",\\\"packageName\\\":\\\"@happyvertical/smrt-inference\\\",\\\"fields\\\":{},\\\"methods\\\":{\\\"getScopeMap\\\":{\\\"name\\\":\\\"getScopeMap\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"InferenceOverrideScopeType\\\",\\\"optional\\\":false,\\\"resolvedType\\\":\\\"'app' | 'tenant'\\\"},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<Map<string, InferenceOverride>>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"findByScope\\\":{\\\"name\\\":\\\"findByScope\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"binding\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"InferenceOverrideScopeType\\\",\\\"optional\\\":false,\\\"resolvedType\\\":\\\"'app' | 'tenant'\\\"},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<InferenceOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"setOverride\\\":{\\\"name\\\":\\\"setOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"binding\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"InferenceOverrideScopeType\\\",\\\"optional\\\":false,\\\"resolvedType\\\":\\\"'app' | 'tenant'\\\"},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"change\\\",\\\"type\\\":\\\"object\\\",\\\"optional\\\":false,\\\"memberTypes\\\":[\\\"unknown\\\",\\\"string[] | null\\\"]}],\\\"returnType\\\":\\\"Promise<InferenceOverride | null>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true},\\\"removeOverride\\\":{\\\"name\\\":\\\"removeOverride\\\",\\\"async\\\":true,\\\"parameters\\\":[{\\\"name\\\":\\\"binding\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false},{\\\"name\\\":\\\"scopeType\\\",\\\"type\\\":\\\"InferenceOverrideScopeType\\\",\\\"optional\\\":false,\\\"resolvedType\\\":\\\"'app' | 'tenant'\\\"},{\\\"name\\\":\\\"scopeId\\\",\\\"type\\\":\\\"string\\\",\\\"optional\\\":false}],\\\"returnType\\\":\\\"Promise<boolean>\\\",\\\"isStatic\\\":false,\\\"isPublic\\\":true}},\\\"decoratorConfig\\\":{\\\"tableName\\\":\\\"_smrt_inference_overrides\\\",\\\"api\\\":false,\\\"mcp\\\":false,\\\"cli\\\":false},\\\"extends\\\":\\\"SmrtCollection\\\",\\\"extendsTypeArg\\\":\\\"InferenceOverride\\\",\\\"exportName\\\":\\\"InferenceOverrideCollection\\\",\\\"collectionExportName\\\":\\\"InferenceOverrideCollectionCollection\\\",\\\"collectionOf\\\":\\\"@happyvertical/smrt-inference:InferenceOverride\\\",\\\"inheritedExposure\\\":[\\\"api\\\",\\\"mcp\\\",\\\"cli\\\"],\\\"schema\\\":{\\\"tableName\\\":\\\"_smrt_inference_overrides\\\",\\\"ddl\\\":\\\"CREATE TABLE IF NOT EXISTS \\\\\\\"_smrt_inference_overrides\\\\\\\" (\\\\n \\\\\\\"_sync_origin_device_id\\\\\\\" TEXT,\\\\n \\\\\\\"_sync_revision\\\\\\\" INTEGER,\\\\n \\\\\\\"id\\\\\\\" UUID PRIMARY KEY NOT NULL,\\\\n \\\\\\\"slug\\\\\\\" TEXT NOT NULL,\\\\n \\\\\\\"context\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"created_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"updated_at\\\\\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\\\n \\\\\\\"binding\\\\\\\" TEXT NOT NULL DEFAULT '',\\\\n \\\\\\\"tenant_id\\\\\\\" UUID,\\\\n \\\\\\\"target\\\\\\\" TEXT,\\\\n \\\\\\\"tenant_allow_list\\\\\\\" TEXT\\\\n);\\\",\\\"columns\\\":{\\\"_sync_origin_device_id\\\":{\\\"type\\\":\\\"TEXT\\\"},\\\"_sync_revision\\\":{\\\"type\\\":\\\"INTEGER\\\"},\\\"id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"primaryKey\\\":true,\\\"referenceKind\\\":\\\"id\\\",\\\"notNull\\\":true},\\\"slug\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true},\\\"context\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"\\\"},\\\"created_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"updated_at\\\":{\\\"type\\\":\\\"TIMESTAMP\\\",\\\"notNull\\\":true,\\\"default\\\":\\\"current_timestamp\\\"},\\\"binding\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":true,\\\"unique\\\":false,\\\"default\\\":\\\"\\\"},\\\"tenant_id\\\":{\\\"type\\\":\\\"UUID\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"target\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false},\\\"tenant_allow_list\\\":{\\\"type\\\":\\\"TEXT\\\",\\\"notNull\\\":false,\\\"unique\\\":false}},\\\"indexes\\\":[{\\\"name\\\":\\\"_smrt_inference_overrides_binding_context_idx\\\",\\\"columns\\\":[\\\"binding\\\",\\\"context\\\"],\\\"unique\\\":true},{\\\"name\\\":\\\"_smrt_inference_overrides_slug_context_idx\\\",\\\"columns\\\":[\\\"slug\\\",\\\"context\\\"]},{\\\"name\\\":\\\"idx_smrt_inference_overrides_tenant\\\",\\\"columns\\\":[\\\"tenant_id\\\",\\\"binding\\\"]},{\\\"name\\\":\\\"_smrt_inference_overrides_created_at_idx\\\",\\\"columns\\\":[\\\"created_at\\\"]}],\\\"version\\\":\\\"4868b81b\\\"},\\\"importPath\\\":\\\"@happyvertical/smrt-inference/models\\\"}},\\\"moduleType\\\":\\\"smrt\\\",\\\"smrtDependencies\\\":[\\\"@happyvertical/smrt-core\\\",\\\"@happyvertical/smrt-prompts\\\",\\\"@happyvertical/smrt-secrets\\\",\\\"@happyvertical/smrt-tenancy\\\",\\\"@happyvertical/smrt-users\\\"],\\\"browser\\\":{\\\"status\\\":\\\"server-only\\\",\\\"issues\\\":[\\\"#3617\\\",\\\"#3624\\\",\\\"#3626\\\",\\\"#3627\\\"],\\\"via\\\":[\\\"@happyvertical/smrt-profiles\\\",\\\"@happyvertical/smrt-secrets\\\",\\\"@happyvertical/smrt-tenancy\\\",\\\"@happyvertical/smrt-users\\\"]},\\\"stampsConstructors\\\":true}\"));\n","/**\n * Process-wide write generation for `_smrt_inference_overrides` (#4754).\n * Every save or delete of an override row bumps it, so the override\n * resolver's short-lived layer cache never serves a value read before a\n * write in the same process. Kept on `globalThis` so duplicate module copies\n * (HMR, two bundles) share it, like core's resolver registration.\n */\nconst GENERATION_KEY = Symbol.for('smrt.inference.overrideGeneration');\n\ntype GenerationHolder = { [GENERATION_KEY]?: number };\n\n/** The current write generation. */\nexport function getInferenceOverrideGeneration(): number {\n return (globalThis as GenerationHolder)[GENERATION_KEY] ?? 0;\n}\n\n/** Record a write: cached layers read before it are stale. */\nexport function bumpInferenceOverrideGeneration(): void {\n const holder = globalThis as GenerationHolder;\n holder[GENERATION_KEY] = (holder[GENERATION_KEY] ?? 0) + 1;\n}\n","/**\n * The vocabulary of stored inference bindings (#4754): binding ids, the\n * target shapes a settings write may store, the allow-list references a\n * tenant write is checked against, and key-free labels.\n *\n * Pure and browser-safe: no `node:`, no SDK, no smrt-config.\n */\nimport {\n assertInferenceTranscriptsConfig,\n INFERENCE_TIERS,\n type InferenceAlias,\n type InferenceCandidate,\n type InferenceTarget,\n type InferenceTier,\n inferenceTargetKind,\n isInferenceTier,\n} from '@happyvertical/smrt-core';\n\n/** How a user or app gets inference. Stored as a user choice, never a binding. */\nexport const INFERENCE_MODES = ['byo', 'local', 'hosted', 'off'] as const;\nexport type InferenceMode = (typeof INFERENCE_MODES)[number];\n\n/** Binding id of `auto`'s default tier. */\nexport const DEFAULT_TIER_BINDING = 'default-tier';\n\n/**\n * Binding id of the app's transcript setting (#4781). App scope only:\n * tenants cannot override transcripts (owner decision 2026-11-08).\n */\nexport const TRANSCRIPTS_BINDING = 'transcripts';\n\n/** A binding id stored in `_smrt_inference_overrides.binding`. */\nexport type InferenceBindingId =\n | `tier:${InferenceTier}`\n | typeof DEFAULT_TIER_BINDING\n | typeof TRANSCRIPTS_BINDING\n | `alias:${string}`\n | `fusion:${string}`;\n\n/** `inference.aliases` / fusion names: what a binding id may carry after its prefix. */\nexport const INFERENCE_BINDING_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;\n\nexport type InferenceBindingKind =\n | 'tier'\n | 'default-tier'\n | 'transcripts'\n | 'alias'\n | 'fusion';\n\n/** A parsed binding id. */\nexport interface ParsedInferenceBinding {\n kind: InferenceBindingKind;\n /** The tier, alias or fusion name (absent for `default-tier`). */\n name?: string;\n}\n\n/** Thrown for a malformed binding id or an invalid stored value. Nothing is written. */\nexport class InferenceBindingValueError extends Error {\n readonly status = 400;\n constructor(\n readonly binding: string,\n reason: string,\n ) {\n super(`Invalid inference binding \"${binding}\": ${reason}`);\n this.name = 'InferenceBindingValueError';\n }\n}\n\n/** Parse a binding id, or `null` when it is not one this package stores. */\nexport function parseInferenceBinding(\n binding: unknown,\n): ParsedInferenceBinding | null {\n if (typeof binding !== 'string') return null;\n if (binding === DEFAULT_TIER_BINDING) return { kind: 'default-tier' };\n if (binding === TRANSCRIPTS_BINDING) return { kind: 'transcripts' };\n const match = /^(tier|alias|fusion):(.+)$/.exec(binding);\n if (!match) return null;\n const [, kind, name] = match as unknown as [string, string, string];\n if (kind === 'tier') {\n return isInferenceTier(name) ? { kind: 'tier', name } : null;\n }\n if (!INFERENCE_BINDING_NAME_PATTERN.test(name)) return null;\n return { kind: kind as 'alias' | 'fusion', name };\n}\n\n/** The binding id of one tier. */\nexport function tierBinding(tier: InferenceTier): `tier:${InferenceTier}` {\n return `tier:${tier}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() !== ''\n ? value.trim()\n : undefined;\n}\n\n/**\n * Key-shaped members are never stored in a binding row: keys live only in\n * smrt-secrets, written by the host (#4754 storage split). `apiKeyEnv` (the\n * name of a variable) is allowed, and so is `key`: #4753 fusion\n * definitions carry one as their identifier.\n */\nconst KEY_MEMBERS = new Set([\n 'apikey',\n 'api_key',\n 'apikeys',\n 'secret',\n 'token',\n 'password',\n 'authorization',\n 'bearer',\n]);\n\n/**\n * A deep copy of `value` with every credential-shaped member removed. Used\n * on every config-backed value a settings screen receives: config tiers and\n * alias candidates may legitimately carry a literal `apiKey` (resolvers and\n * endpoint-bound config), and none of it may reach a browser.\n */\nexport function stripKeyMembers<T>(value: T): T {\n if (Array.isArray(value)) return value.map(stripKeyMembers) as T;\n if (!isRecord(value)) return value;\n const out: Record<string, unknown> = {};\n for (const [member, inner] of Object.entries(value)) {\n if (KEY_MEMBERS.has(member.toLowerCase())) continue;\n out[member] = stripKeyMembers(inner);\n }\n return out as T;\n}\n\n/** Dotted path of the first key-shaped member anywhere in `value`, or `null`. */\nexport function findKeyMember(value: unknown, path = ''): string | null {\n if (Array.isArray(value)) {\n for (const [index, item] of value.entries()) {\n const found = findKeyMember(item, `${path}[${index}]`);\n if (found) return found;\n }\n return null;\n }\n if (!isRecord(value)) return null;\n for (const [member, inner] of Object.entries(value)) {\n const here = path ? `${path}.${member}` : member;\n if (KEY_MEMBERS.has(member.toLowerCase())) return here;\n const found = findKeyMember(inner, here);\n if (found) return found;\n }\n return null;\n}\n\n/**\n * The tier target a settings write may store. A tier binds exactly one of:\n *\n * - a model: `{ provider?, model }` (no `baseUrl`: an endpoint needs its own\n * key, and keys are never stored here; bind an alias whose candidate names\n * `baseUrl` + `apiKeyEnv` or a preset instead);\n * - an alias: `{ alias }`;\n * - a fusion (#4753): `{ kind: 'fusion', fusion }`.\n *\n * `null` means \"same as default\" (inherit the lower layer).\n */\nexport function normalizeTierTarget(\n binding: string,\n value: unknown,\n): InferenceTarget | null {\n if (value === null || value === undefined) return null;\n if (!isRecord(value)) {\n throw new InferenceBindingValueError(binding, 'a tier target is an object');\n }\n const keyMember = findKeyMember(value);\n if (keyMember) {\n throw new InferenceBindingValueError(\n binding,\n `\"${keyMember}\" looks like a credential; keys are stored only through smrt-secrets`,\n );\n }\n const fusion = text(value.fusion);\n const alias = text(value.alias);\n const model = text(value.model);\n const provider = text(value.provider);\n const kind = value.kind;\n const allowed = new Set(['kind', 'fusion', 'alias', 'model', 'provider']);\n const extra = Object.keys(value).filter((member) => !allowed.has(member));\n if (extra.length > 0) {\n throw new InferenceBindingValueError(\n binding,\n `unsupported member(s) ${extra.join(', ')} (endpoints and inline candidates belong in an alias)`,\n );\n }\n const shapes = [fusion, alias, model].filter(Boolean).length;\n if (shapes !== 1) {\n throw new InferenceBindingValueError(\n binding,\n 'a tier target is exactly one of { model, provider? }, { alias } or { kind: \"fusion\", fusion }',\n );\n }\n if (fusion) {\n if (kind !== undefined && kind !== 'fusion') {\n throw new InferenceBindingValueError(binding, 'fusion kind mismatch');\n }\n if (!INFERENCE_BINDING_NAME_PATTERN.test(fusion)) {\n throw new InferenceBindingValueError(binding, 'invalid fusion name');\n }\n return { kind: 'fusion', fusion } as unknown as InferenceTarget;\n }\n if (alias) {\n if (kind !== undefined && kind !== 'alias') {\n throw new InferenceBindingValueError(binding, 'alias kind mismatch');\n }\n if (!INFERENCE_BINDING_NAME_PATTERN.test(alias)) {\n throw new InferenceBindingValueError(binding, 'invalid alias name');\n }\n return { alias };\n }\n if (kind !== undefined && kind !== 'single') {\n throw new InferenceBindingValueError(binding, 'model kind mismatch');\n }\n return provider ? { provider, model } : { model };\n}\n\n/**\n * An alias definition a settings write may store: `{ candidates, description? }`,\n * every candidate exactly one of provider/model, preset/model or alias, and\n * never a literal key (`apiKeyEnv` names a variable and needs `baseUrl`).\n * Structural checks only; cross-references are checked against the layered\n * config by the settings service.\n */\nexport function normalizeAliasDefinition(\n binding: string,\n value: unknown,\n): InferenceAlias | null {\n if (value === null || value === undefined) return null;\n if (!isRecord(value) || !Array.isArray(value.candidates)) {\n throw new InferenceBindingValueError(\n binding,\n 'an alias is { candidates: [...], description? }',\n );\n }\n const keyMember = findKeyMember(value);\n if (keyMember) {\n throw new InferenceBindingValueError(\n binding,\n `\"${keyMember}\" looks like a credential; keys are stored only through smrt-secrets`,\n );\n }\n if (value.candidates.length === 0) {\n throw new InferenceBindingValueError(binding, 'an alias needs candidates');\n }\n const allowed = new Set([\n 'provider',\n 'model',\n 'baseUrl',\n 'apiKeyEnv',\n 'preset',\n 'alias',\n 'params',\n ]);\n const candidates: InferenceCandidate[] = value.candidates.map(\n (candidate: unknown, index: number) => {\n if (!isRecord(candidate)) {\n throw new InferenceBindingValueError(\n binding,\n `candidates[${index}] must be an object`,\n );\n }\n const extra = Object.keys(candidate).filter(\n (member) => !allowed.has(member),\n );\n if (extra.length > 0) {\n throw new InferenceBindingValueError(\n binding,\n `candidates[${index}] has unsupported member(s) ${extra.join(', ')}`,\n );\n }\n const out: InferenceCandidate = {};\n for (const member of allowed) {\n if (member === 'params') continue;\n const inner = text(candidate[member]);\n if (inner) (out as Record<string, string>)[member] = inner;\n }\n if (candidate.params !== undefined) {\n if (!isRecord(candidate.params)) {\n throw new InferenceBindingValueError(\n binding,\n `candidates[${index}].params must be an object`,\n );\n }\n out.params = { ...candidate.params };\n }\n return out;\n },\n );\n const description = text(value.description);\n return description ? { candidates, description } : { candidates };\n}\n\n/**\n * A fusion config entry (#4753) a settings write may store: a JSON object\n * with no credential members. Its meaning belongs to the fusion interpreter;\n * this package only stores it (app scope only).\n */\nexport function normalizeFusionDefinition(\n binding: string,\n value: unknown,\n): Record<string, unknown> | null {\n if (value === null || value === undefined) return null;\n if (!isRecord(value)) {\n throw new InferenceBindingValueError(binding, 'a fusion is a JSON object');\n }\n const keyMember = findKeyMember(value);\n if (keyMember) {\n throw new InferenceBindingValueError(\n binding,\n `\"${keyMember}\" looks like a credential; keys are stored only through smrt-secrets`,\n );\n }\n return JSON.parse(JSON.stringify(value)) as Record<string, unknown>;\n}\n\n/** `default-tier` stores `{ tier }`. */\nexport function normalizeDefaultTier(\n binding: string,\n value: unknown,\n): InferenceTier | null {\n if (value === null || value === undefined) return null;\n const tier = isRecord(value) ? value.tier : value;\n if (!isInferenceTier(tier)) {\n throw new InferenceBindingValueError(\n binding,\n `the default tier must be one of ${INFERENCE_TIERS.join(', ')}`,\n );\n }\n return tier;\n}\n\n/** The stored transcript setting (#4781): core's app settings layer. */\nexport interface InferenceTranscriptSettingsValue {\n enabled?: boolean;\n /** Success capture rate, 0..1. */\n sample?: number;\n /** Per-row byte cap. */\n maxBytes?: number;\n}\n\nconst TRANSCRIPT_SETTING_MEMBERS = new Set(['enabled', 'sample', 'maxBytes']);\n\n/**\n * `transcripts` stores `{ enabled?, sample?, maxBytes? }`, validated by\n * core's `assertInferenceTranscriptsConfig()`. Per-prompt sampling stays in\n * config; any other member is refused.\n */\nexport function normalizeTranscriptSettings(\n binding: string,\n value: unknown,\n): InferenceTranscriptSettingsValue | null {\n if (value === null || value === undefined) return null;\n if (!isRecord(value)) {\n throw new InferenceBindingValueError(\n binding,\n 'the transcript setting must be an object',\n );\n }\n const extra = Object.keys(value).filter(\n (member) => !TRANSCRIPT_SETTING_MEMBERS.has(member),\n );\n if (extra.length > 0) {\n throw new InferenceBindingValueError(\n binding,\n `the transcript setting carries enabled, sample and maxBytes only (got ${extra.join(', ')})`,\n );\n }\n try {\n assertInferenceTranscriptsConfig(value);\n } catch (error) {\n throw new InferenceBindingValueError(\n binding,\n error instanceof Error ? error.message : String(error),\n );\n }\n const out: InferenceTranscriptSettingsValue = {};\n if (value.enabled !== undefined) out.enabled = value.enabled as boolean;\n if (value.sample !== undefined) out.sample = value.sample as number;\n if (value.maxBytes !== undefined) out.maxBytes = value.maxBytes as number;\n return Object.keys(out).length > 0 ? out : null;\n}\n\n/**\n * The allow-list reference of a tier target: `model:<provider>/<model>`\n * (`model:default/<model>` without a provider), `alias:<name>` or\n * `fusion:<name>`; for the default tier, `tier:<name>`. Key-free.\n */\nexport function inferenceTargetRef(target: InferenceTarget): string {\n const kind = inferenceTargetKind(target);\n const record = target as InferenceTarget & { fusion?: string };\n if (kind === 'fusion') return `fusion:${record.fusion ?? ''}`;\n if (kind === 'alias') return `alias:${target.alias ?? ''}`;\n if (kind === 'candidates') return 'candidates';\n return `model:${target.provider ?? 'default'}/${target.model ?? 'default'}`;\n}\n\n/**\n * Whether `ref` matches one allow-list pattern. A pattern is an exact\n * reference, a prefix ending in `*` (`model:openai/*`, `alias:*`), or `*`.\n */\nexport function matchesAllowPattern(ref: string, pattern: string): boolean {\n if (pattern === '*') return true;\n if (pattern.endsWith('*')) return ref.startsWith(pattern.slice(0, -1));\n return ref === pattern;\n}\n\n/**\n * The tenant allow-list check: a tenant may bind only a reference the app\n * row's list matches. No list (or an empty one) allows nothing, so a tenant\n * cannot bind a tier the operator has not opened.\n */\nexport function isAllowedByList(\n ref: string,\n allowList: readonly string[] | null | undefined,\n): boolean {\n return (allowList ?? []).some((pattern) => matchesAllowPattern(ref, pattern));\n}\n\n/** Validate and normalize an allow-list (patterns are short, key-free references). */\nexport function normalizeAllowList(\n binding: string,\n value: unknown,\n): string[] | null {\n if (value === null || value === undefined) return null;\n if (!Array.isArray(value)) {\n throw new InferenceBindingValueError(\n binding,\n 'an allow-list is an array of references',\n );\n }\n const out: string[] = [];\n for (const entry of value) {\n if (\n typeof entry !== 'string' ||\n !/^(\\*|(model|alias|fusion|tier):[A-Za-z0-9._/:*-]{1,160})$/.test(entry)\n ) {\n throw new InferenceBindingValueError(\n binding,\n `invalid allow-list entry ${JSON.stringify(entry)}`,\n );\n }\n if (!out.includes(entry)) out.push(entry);\n }\n return out;\n}\n\n/** Key-free description of a tier target for screens and reports. */\nexport function describeInferenceTarget(\n target: InferenceTarget | null | undefined,\n): string {\n if (!target || Object.keys(target).length === 0) {\n return 'Same as default (configured client)';\n }\n const kind = inferenceTargetKind(target);\n const record = target as InferenceTarget & { fusion?: string };\n if (kind === 'fusion') return `Fusion ${record.fusion ?? '?'}`;\n if (kind === 'alias') return `Alias ${target.alias ?? '?'}`;\n if (kind === 'candidates') {\n return `${target.candidates?.length ?? 0} inline candidate(s)`;\n }\n return `${target.provider ?? 'default'}/${target.model ?? 'default model'}`;\n}\n","import {\n field,\n SmrtObject,\n type SmrtObjectOptions,\n type SmrtSaveOptions,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { bumpInferenceOverrideGeneration } from '../settings/cache.js';\nimport {\n InferenceBindingValueError,\n normalizeAliasDefinition,\n normalizeAllowList,\n normalizeDefaultTier,\n normalizeFusionDefinition,\n normalizeTierTarget,\n normalizeTranscriptSettings,\n parseInferenceBinding,\n} from '../settings/targets.js';\n\n/** `context` (and `scopeId`) of the app-wide row. */\nexport const APP_INFERENCE_SCOPE_ID = '__app__';\n\nexport interface InferenceOverrideOptions extends SmrtObjectOptions {\n binding?: string;\n tenantId?: string | null;\n target?: string | null;\n tenantAllowList?: string | null;\n}\n\nfunction parseJson(binding: string, raw: string | null, what: string): unknown {\n if (raw === null) return null;\n try {\n return JSON.parse(raw);\n } catch {\n throw new InferenceBindingValueError(binding, `${what} is not valid JSON`);\n }\n}\n\n// One row per (binding, scope): `tier:<tier>`, `default-tier`,\n// `alias:<name>` or `fusion:<name>`, at the app scope (`tenantId = null`) or\n// one tenant's. Rows are authorization state for every scope and `tenantId`\n// is an ordinary settable field, not request-derived tenant scoping, so the\n// generated REST, CLI and MCP surfaces are closed (mirrors\n// `_smrt_prompt_overrides`, #3013). Hosts write through\n// `InferenceOverrideService`, which asks a host authorizer before every write\n// and fails closed. Keys never live here: `save()` refuses any credential-\n// shaped member; keys go to smrt-secrets through the host.\n@smrt({\n tableName: '_smrt_inference_overrides',\n conflictColumns: ['binding', 'context'],\n api: false,\n cli: false,\n mcp: false,\n indexes: [\n {\n name: 'idx_smrt_inference_overrides_tenant',\n columns: ['tenantId', 'binding'],\n },\n ],\n})\nexport class InferenceOverride extends SmrtObject { static __smrtPackage__ = \"@happyvertical/smrt-inference\";\n @field({ required: true })\n binding: string = '';\n\n // Not tenant-scoped (any scope is writable through the authorized service),\n // but the value is a tenant id: uuid on PostgreSQL, like every tenant_id.\n @field({ sqlType: 'UUID', nullable: true })\n tenantId: string | null = null;\n\n /**\n * JSON of the bound value; `null` inherits the lower layer. Tier rows hold\n * a tier target, `default-tier` holds `{ tier }`, alias rows an alias, and\n * fusion rows a fusion config entry.\n */\n @field({ type: 'text', nullable: true })\n target: string | null = null;\n\n /**\n * App rows of `tier:*` and `default-tier` only: JSON array of the target\n * references a tenant may bind (`model:openai/*`, `alias:local`,\n * `fusion:review-pair`, `tier:good`, `*`). No list allows nothing. This is\n * the stored half of the tenant allow-list; the service's\n * `tenantTargetPolicy` hook is the code half (#4667 caps the maximum\n * hosted tier there).\n */\n @field({ type: 'text', nullable: true })\n tenantAllowList: string | null = null;\n\n constructor(options: InferenceOverrideOptions = {}) {\n super(options);\n if (options.binding !== undefined) this.binding = options.binding;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.target !== undefined) this.target = options.target;\n if (options.tenantAllowList !== undefined) {\n this.tenantAllowList = options.tenantAllowList;\n }\n }\n\n /** The parsed, validated stored value (`null` inherits). */\n getValue(): unknown {\n return this.normalizedValue();\n }\n\n /** The parsed allow-list (`null` when the row carries none). */\n getTenantAllowList(): string[] | null {\n return normalizeAllowList(\n this.binding,\n parseJson(this.binding, this.tenantAllowList, 'tenantAllowList'),\n );\n }\n\n override async save(options?: SmrtSaveOptions): Promise<this> {\n this.validateOverride();\n // `context` is the uniqueness scope so the app row stays unique even\n // though `tenantId` is nullable (as in `_smrt_prompt_overrides`).\n this.context = this.tenantId ?? APP_INFERENCE_SCOPE_ID;\n const result = await super.save(options);\n bumpInferenceOverrideGeneration();\n return result;\n }\n\n override async delete(): Promise<void> {\n await super.delete();\n bumpInferenceOverrideGeneration();\n }\n\n private normalizedValue(): unknown {\n const parsed = parseInferenceBinding(this.binding);\n if (!parsed) {\n throw new InferenceBindingValueError(\n String(this.binding),\n 'unknown binding id',\n );\n }\n const raw = parseJson(this.binding, this.target, 'target');\n switch (parsed.kind) {\n case 'tier':\n return normalizeTierTarget(this.binding, raw);\n case 'default-tier':\n return normalizeDefaultTier(this.binding, raw);\n case 'transcripts':\n return normalizeTranscriptSettings(this.binding, raw);\n case 'alias':\n return normalizeAliasDefinition(this.binding, raw);\n case 'fusion':\n return normalizeFusionDefinition(this.binding, raw);\n }\n }\n\n private validateOverride(): void {\n const parsed = parseInferenceBinding(this.binding);\n if (!parsed) {\n throw new InferenceBindingValueError(\n String(this.binding),\n 'unknown binding id',\n );\n }\n if (this.tenantId !== null) {\n if (\n typeof this.tenantId !== 'string' ||\n !this.tenantId.trim() ||\n this.tenantId !== this.tenantId.trim() ||\n this.tenantId === APP_INFERENCE_SCOPE_ID\n ) {\n throw new InferenceBindingValueError(this.binding, 'invalid tenant id');\n }\n // Tenants bind tiers to allow-listed targets; they never define what a\n // name means. A tenant alias or fusion could redefine an allow-listed\n // name and bypass the list.\n if (parsed.kind === 'alias' || parsed.kind === 'fusion') {\n throw new InferenceBindingValueError(\n this.binding,\n 'aliases and fusions are defined at the app scope only',\n );\n }\n // Tenants cannot override transcripts (owner decision, #4781).\n if (parsed.kind === 'transcripts') {\n throw new InferenceBindingValueError(\n this.binding,\n 'the transcript setting is app scope only',\n );\n }\n if (this.tenantAllowList !== null) {\n throw new InferenceBindingValueError(\n this.binding,\n 'the tenant allow-list lives on the app row',\n );\n }\n }\n if (\n this.tenantAllowList !== null &&\n parsed.kind !== 'tier' &&\n parsed.kind !== 'default-tier'\n ) {\n throw new InferenceBindingValueError(\n this.binding,\n 'only tier and default-tier bindings carry a tenant allow-list',\n );\n }\n const value = this.normalizedValue();\n this.target = value === null ? null : JSON.stringify(value);\n const allowList = this.getTenantAllowList();\n this.tenantAllowList =\n allowList === null ? null : JSON.stringify(allowList);\n }\n}\n","import { SmrtCollection } from '@happyvertical/smrt-core';\nimport {\n APP_INFERENCE_SCOPE_ID,\n InferenceOverride,\n} from './InferenceOverride.js';\n\n/** `app` (the `__app__` row) or one tenant. */\nexport type InferenceOverrideScopeType = 'app' | 'tenant';\n\n/**\n * Reads and unauthorized writes of `_smrt_inference_overrides`. Hosts write\n * through `InferenceOverrideService` (authorizer-gated, fails closed); call\n * {@link setOverride} / {@link removeOverride} directly only after your own\n * authorization decision.\n */\nexport class InferenceOverrideCollection extends SmrtCollection<InferenceOverride> {\n static readonly _itemClass = InferenceOverride;\n\n /**\n * Every row of one scope, keyed by binding id: one bounded read. An\n * enumeration read: build the collection without list bounds (the\n * settings service does) so a host-wide `maxListLimit` cannot drop a row.\n */\n async getScopeMap(\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n ): Promise<Map<string, InferenceOverride>> {\n const tenantId = scopeType === 'app' ? null : scopeId;\n const rows = await this.list({ where: { tenantId } });\n const result = new Map<string, InferenceOverride>();\n for (const row of rows) result.set(row.binding, row);\n return result;\n }\n\n /** The row for one binding at one scope. */\n async findByScope(\n binding: string,\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n ): Promise<InferenceOverride | null> {\n const tenantId = scopeType === 'app' ? null : scopeId;\n const rows = await this.list({ where: { binding, tenantId }, limit: 1 });\n return rows[0] ?? null;\n }\n\n /**\n * Set the stored value (and, for app rows, optionally the tenant\n * allow-list) of one binding at one scope, creating the row when needed.\n * `undefined` leaves a column untouched; `null` clears it. A row left with\n * neither a value nor an allow-list is deleted.\n */\n async setOverride(\n binding: string,\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n change: { target?: unknown; tenantAllowList?: string[] | null },\n ): Promise<InferenceOverride | null> {\n const existing = await this.findByScope(binding, scopeType, scopeId);\n const target =\n change.target === undefined\n ? (existing?.target ?? null)\n : change.target === null\n ? null\n : JSON.stringify(change.target);\n const tenantAllowList =\n change.tenantAllowList === undefined\n ? (existing?.tenantAllowList ?? null)\n : change.tenantAllowList === null\n ? null\n : JSON.stringify(change.tenantAllowList);\n\n if (target === null && tenantAllowList === null) {\n if (existing) await existing.delete();\n return null;\n }\n if (existing) {\n existing.target = target;\n existing.tenantAllowList = tenantAllowList;\n await existing.save();\n return existing;\n }\n const created = await this.create({\n binding,\n tenantId: scopeType === 'app' ? null : scopeId,\n target,\n tenantAllowList,\n });\n await created.save();\n return created;\n }\n\n /** Delete the row for one binding at one scope, if any. */\n async removeOverride(\n binding: string,\n scopeType: InferenceOverrideScopeType,\n scopeId: string,\n ): Promise<boolean> {\n const existing = await this.findByScope(binding, scopeType, scopeId);\n if (!existing) return false;\n await existing.delete();\n return true;\n }\n}\n\nexport { APP_INFERENCE_SCOPE_ID };\n"],"mappings":"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"}
@@ -1514,15 +1514,15 @@ var FusionRun = class {
1514
1514
  function isDegraded(seat) {
1515
1515
  return seat.status === "failed" || seat.status === "ok" && (seat.substitutions?.length ?? 0) > 0;
1516
1516
  }
1517
- function fusionFor(context) {
1518
- const key = String(context.resolution.target.fusion ?? "").trim();
1519
- const definition = key ? lookupFusion(key, context.config) : void 0;
1520
- if (!definition) throw new InferenceConfigError([{
1517
+ function unknownFusion(context, key) {
1518
+ return new InferenceConfigError([{
1521
1519
  path: `resolution.${context.resolution.tierResolved}`,
1522
1520
  code: "fusion-unknown",
1523
1521
  message: `tier "${context.resolution.tierResolved}" names unknown fusion "${key}"`
1524
1522
  }]);
1525
- const path = Object.hasOwn(context.config?.fusions ?? {}, key) ? `inference.fusions.${key}` : `fusions.${key}`;
1523
+ }
1524
+ function validatedFusion(context, key, definition, origin) {
1525
+ const path = origin === "config" ? `inference.fusions.${key}` : origin === "resolver" ? `resolver.fusions.${key}` : `fusions.${key}`;
1526
1526
  const issues = [...findFusionDefinitionIssues(definition, path, context.config), ...findFusionDefinitionPromptIssues(definition, path)];
1527
1527
  if (issues.length > 0) throw new InferenceConfigError(issues);
1528
1528
  return {
@@ -1530,8 +1530,26 @@ function fusionFor(context) {
1530
1530
  definition
1531
1531
  };
1532
1532
  }
1533
- function runFusion(context, input, callerOptions = {}, options = {}) {
1534
- const { key, definition } = fusionFor(context);
1533
+ function fusionForSync(context) {
1534
+ const key = String(context.resolution.target.fusion ?? "").trim();
1535
+ const definition = key ? lookupFusion(key, context.config) : void 0;
1536
+ if (!definition) throw unknownFusion(context, key);
1537
+ return validatedFusion(context, key, definition, Object.hasOwn(context.config?.fusions ?? {}, key) ? "config" : "registry");
1538
+ }
1539
+ async function fusionFor(context) {
1540
+ if (typeof context.lookupFusion !== "function") return fusionForSync(context);
1541
+ const key = String(context.resolution.target.fusion ?? "").trim();
1542
+ if (!key) throw unknownFusion(context, key);
1543
+ const found = await context.lookupFusion(key);
1544
+ if (found) return validatedFusion(context, key, found.definition, found.source);
1545
+ const registered = fusionStore().get(key);
1546
+ if (!registered) throw unknownFusion(context, key);
1547
+ return validatedFusion(context, key, registered, "registry");
1548
+ }
1549
+ async function runFusion(context, input, callerOptions = {}, options = {}) {
1550
+ return runResolvedFusion(context, await fusionFor(context), input, callerOptions, options);
1551
+ }
1552
+ function runResolvedFusion(context, { key, definition }, input, callerOptions, options) {
1535
1553
  return new FusionRun(context, key, definition, callerOptions, {
1536
1554
  retryDelayMs: options.retryDelayMs ?? (() => 200 + Math.random() * 300),
1537
1555
  sleep: options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms))),
@@ -1542,11 +1560,15 @@ function unsupported(method) {
1542
1560
  throw new TypeError(`A fusion target runs through message(); ${method}() is not supported (#4753).`);
1543
1561
  }
1544
1562
  function createFusionClient(context, options = {}) {
1545
- fusionFor(context);
1563
+ let fusion;
1564
+ if (typeof context.lookupFusion === "function") {
1565
+ fusion = fusionFor(context);
1566
+ fusion.catch(() => {});
1567
+ } else fusion = Promise.resolve(fusionForSync(context));
1546
1568
  return {
1547
1569
  async message(text, callerOptions) {
1548
1570
  const { onFusionResult, ...rest } = callerOptions ?? {};
1549
- const result = await runFusion(context, text, rest, options);
1571
+ const result = await runResolvedFusion(context, await fusion, text, rest, options);
1550
1572
  if (typeof onFusionResult === "function") onFusionResult(result);
1551
1573
  return result.text;
1552
1574
  },
@@ -1582,6 +1604,10 @@ function createInferenceResolver(base) {
1582
1604
  const mode = base.mode.bind(base);
1583
1605
  resolver.mode = (request) => mode(request);
1584
1606
  }
1607
+ if (base && typeof base.lookupFusion === "function") {
1608
+ const lookupFusion = base.lookupFusion.bind(base);
1609
+ resolver.lookupFusion = (key, request, context) => lookupFusion(key, request, context);
1610
+ }
1585
1611
  resolver[OWN_RESOLVER] = true;
1586
1612
  return resolver;
1587
1613
  }
@@ -1842,6 +1868,6 @@ function inferenceProviderConfigSecretName(scope) {
1842
1868
  return userId ? `${INFERENCE_PROVIDER_CONFIG_SECRET_TYPE}:user:${userId}` : `${INFERENCE_PROVIDER_CONFIG_SECRET_TYPE}:tenant`;
1843
1869
  }
1844
1870
  //#endregion
1845
- export { candidateLabel as A, InferenceExhaustedError as B, createChainClient as C, EXECUTOR_KINDS as D, CHAIN_KINDS as E, findInferenceConfigIssues as F, hasInferenceChains as I, validateInferenceConfig as L, expandTarget as M, findFusionDefinitionIssues as N, FUSION_GATE_CONTINUE as O, findFusionPromptIssues as P, validateInferenceTarget as R, setFusionPromptResolver as S, isInferenceAbort as T, InferenceFusionError as V, createFusionClient as _, inferenceProviderBindingFor as a, listFusions as b, inferenceProviderTargets as c, redactInferenceTarget as d, createInferenceResolver as f, createChainExecutor as g, clearFusions as h, InferenceProviderConfigError as i, candidateToStep as j, FUSION_SEAT_COUNTS as k, parseInferenceProviderConfig as l, registerInference as m, INFERENCE_PROVIDER_CONFIG_SOURCES as n, inferenceProviderConfigSecretName as o, isInferenceRegistered as p, INFERENCE_PROVIDER_PARAM_KEYS as r, inferenceProviderSource as s, INFERENCE_PROVIDER_CONFIG_SECRET_TYPE as t, redactInferenceProviderConfig as u, defineFusion as v, classifyInferenceFailure as w, runFusion as x, getFusion as y, InferenceConfigError as z };
1871
+ export { candidateLabel as A, InferenceConfigError as B, createChainClient as C, EXECUTOR_KINDS as D, CHAIN_KINDS as E, findFusionPromptIssues as F, InferenceFusionError as H, findInferenceConfigIssues as I, hasInferenceChains as L, expandTarget as M, findFusionDefinitionIssues as N, FUSION_GATE_CONTINUE as O, findFusionDefinitionPromptIssues as P, validateInferenceConfig as R, setFusionPromptResolver as S, isInferenceAbort as T, InferenceExhaustedError as V, createFusionClient as _, inferenceProviderBindingFor as a, listFusions as b, inferenceProviderTargets as c, redactInferenceTarget as d, createInferenceResolver as f, createChainExecutor as g, clearFusions as h, InferenceProviderConfigError as i, candidateToStep as j, FUSION_SEAT_COUNTS as k, parseInferenceProviderConfig as l, registerInference as m, INFERENCE_PROVIDER_CONFIG_SOURCES as n, inferenceProviderConfigSecretName as o, isInferenceRegistered as p, INFERENCE_PROVIDER_PARAM_KEYS as r, inferenceProviderSource as s, INFERENCE_PROVIDER_CONFIG_SECRET_TYPE as t, redactInferenceProviderConfig as u, defineFusion as v, classifyInferenceFailure as w, runFusion as x, getFusion as y, validateInferenceTarget as z };
1846
1872
 
1847
- //# sourceMappingURL=provider-config-4Bx8N52X.js.map
1873
+ //# sourceMappingURL=provider-config-BSpIQRVM.js.map