@happyvertical/smrt-inference 0.0.0-stage → 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 (112) hide show
  1. package/AGENTS.md +206 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +73 -2
  5. package/agents/fusions.md +105 -0
  6. package/agents/hosted.md +161 -0
  7. package/agents/resolver.md +65 -0
  8. package/agents/settings.md +107 -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-Dat4TrK5.js +423 -0
  12. package/dist/chunks/InferenceOverrideCollection-Dat4TrK5.js.map +1 -0
  13. package/dist/chunks/provider-config-BSpIQRVM.js +1873 -0
  14. package/dist/chunks/provider-config-BSpIQRVM.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 +461 -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 +1414 -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 +291 -0
  56. package/dist/resolver.js.map +1 -0
  57. package/dist/server.d.ts +25 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +1138 -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 +68 -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 +41 -0
  72. package/dist/settings/resolver.d.ts.map +1 -0
  73. package/dist/settings/settings-service.d.ts +355 -0
  74. package/dist/settings/settings-service.d.ts.map +1 -0
  75. package/dist/settings/targets.d.ts +112 -0
  76. package/dist/settings/targets.d.ts.map +1 -0
  77. package/dist/settings/transcripts.d.ts +33 -0
  78. package/dist/settings/transcripts.d.ts.map +1 -0
  79. package/dist/smrt-knowledge.json +341 -0
  80. package/dist/smrt-knowledge.okf.md +80 -0
  81. package/dist/svelte/components/InferenceAliasEditor.svelte +210 -0
  82. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts +13 -0
  83. package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts.map +1 -0
  84. package/dist/svelte/components/InferenceDefaultView.svelte +330 -0
  85. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts +15 -0
  86. package/dist/svelte/components/InferenceDefaultView.svelte.d.ts.map +1 -0
  87. package/dist/svelte/components/InferenceFusionList.svelte +141 -0
  88. package/dist/svelte/components/InferenceFusionList.svelte.d.ts +16 -0
  89. package/dist/svelte/components/InferenceFusionList.svelte.d.ts.map +1 -0
  90. package/dist/svelte/components/InferenceSettingsPanel.svelte +392 -0
  91. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts +84 -0
  92. package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts.map +1 -0
  93. package/dist/svelte/components/InferenceTierRow.svelte +199 -0
  94. package/dist/svelte/components/InferenceTierRow.svelte.d.ts +14 -0
  95. package/dist/svelte/components/InferenceTierRow.svelte.d.ts.map +1 -0
  96. package/dist/svelte/components/InferenceTranscriptSettings.svelte +165 -0
  97. package/dist/svelte/components/InferenceTranscriptSettings.svelte.d.ts +18 -0
  98. package/dist/svelte/components/InferenceTranscriptSettings.svelte.d.ts.map +1 -0
  99. package/dist/svelte/components/InferenceUsageTable.svelte +177 -0
  100. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts +21 -0
  101. package/dist/svelte/components/InferenceUsageTable.svelte.d.ts.map +1 -0
  102. package/dist/svelte/index.d.ts +17 -0
  103. package/dist/svelte/index.d.ts.map +1 -0
  104. package/dist/svelte/index.js +14 -0
  105. package/dist/svelte/types.d.ts +262 -0
  106. package/dist/svelte/types.d.ts.map +1 -0
  107. package/dist/svelte/types.js +124 -0
  108. package/dist/types.d.ts +47 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +13 -0
  111. package/dist/types.js.map +1 -0
  112. package/package.json +105 -3
@@ -0,0 +1,107 @@
1
+ # Settings UI and binding storage (#4754)
2
+
3
+ Consumer guide: [docs/content/inference-settings.md](../../../docs/content/inference-settings.md).
4
+
5
+ - **Storage split, enforced by API shape.** Keys: the panel emits
6
+ `onSaveKey({ provider, key, preset?, baseUrl? })` (the endpoint shown when
7
+ the key was typed; the key input remounts on a provider or base URL
8
+ change), the host binds it with `bindInferenceKey()` and stores it once,
9
+ as the #4756 `inference.provider-config` secret, through the user
10
+ resolver's `write()` (`inferenceProviderConfigFromKey()` converts the
11
+ record and re-checks the bound origin), and the panel only ever gets
12
+ `keySet` back. User choices (mode `byo | local | hosted |
13
+ off`, provider, base URL, model, default tier): smrt-preferences kind
14
+ `inference` (`inferencePreferenceKind`, permissions `inference.customize`
15
+ / `inference.personalize`; the host registers both, smrt-preferences is
16
+ not a dependency). App/tenant bindings: `_smrt_inference_overrides`.
17
+ - **Key rule** (`src/settings/keys.ts`, ported from the planner's BYO
18
+ screen): a key is bound when saved to the origin of its endpoint
19
+ (explicit base URL, else preset, else `inferenceProbeUrl()`'s provider
20
+ default, a provider id that is a preset id counting as that preset;
21
+ https only, plain http for loopback; gateways such as
22
+ `litellm`/`bifrost` need a base URL) and `inferenceKeyFor()` releases it
23
+ only for a URL on that origin. `testConnection()` reads no environment key
24
+ and never probes another origin with a stored key; details pass
25
+ `redactInferenceSecret()`. Binding rows, preference payloads and tested
26
+ candidates refuse credential-shaped members (`findKeyMember()`; `key` is
27
+ allowed because #4753 fusions carry one).
28
+ - **Override table**: one row per `(binding, context)`, binding
29
+ `tier:<tier> | default-tier | alias:<name> | fusion:<name>`, `context`
30
+ `__app__` or the tenant id (the prompts pattern), `target` JSON,
31
+ `tenant_allow_list` JSON on app rows. Generated surfaces are closed; ships
32
+ through `smrt db:migrate` (`override-migration.test.ts`). A tier target is
33
+ exactly a model `{ provider?, model }`, an alias `{ alias }` or a fusion
34
+ `{ kind: 'fusion', fusion }`: no endpoint, key or inline chain (those go
35
+ in an alias). Layers, low to high: code default (configured client),
36
+ `inference` config, app row, tenant row; each target replaced whole.
37
+ - **Tenant allow-list**: a tenant writes only `tier:*` and `default-tier`,
38
+ only to a reference (`model:<provider>/<model>`, `alias:<n>`,
39
+ `fusion:<n>`, `tier:<t>`; trailing `*` and `*` match) on the app row's
40
+ list (no list allows nothing), and only when the
41
+ `tenantTargetPolicy` hook returns `true`; this hook is where #4667 caps
42
+ the maximum hosted tier. Both are re-checked on every layered read, so a
43
+ narrowed list or policy disables existing tenant rows (`tenantBlocked`).
44
+ Aliases and fusions are app-scope only (a tenant definition could
45
+ redefine an allow-listed name).
46
+ - **`InferenceSettingsService`** mirrors `PromptSettingsService`:
47
+ `listInferenceSettings()` returns one row per tier with the effective
48
+ target, each scope's raw value and both revert previews computed
49
+ separately (`appRevertTarget` = config, never the app row;
50
+ `inheritedTarget` = config + app row), plus `getDefaultTierSetting()`,
51
+ `listInferenceAliases()`, `listInferenceFusions()` (registry: the
52
+ `registryFusions` option, default #4753's `defineFusion()` registry;
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`
58
+ (authorizer first, fails closed; without `authorize` the service is
59
+ read-only) and refuse a value that adds a config issue to the layer it
60
+ lands in; removing an app alias or fusion that any row (every tenant) or
61
+ config still names is refused (`InferenceBindingInUseError`, tenant rows
62
+ only counted, checked after the authorizer allowed the write) unless
63
+ config or the registry keeps defining it. Every config-backed value a row method
64
+ returns passes `stripKeyMembers()` (config may carry literal keys);
65
+ `getEffectiveConfig()` is server-side only. `testConnection()`, `testCandidate()` and `doctor()` reuse
66
+ `./node`; `testCandidate()` reads a key variable only for a candidate
67
+ already in the effective config (an unsaved one is probed keyless);
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).
81
+ - **`createInferenceOverrideResolver({ db, base })`** applies stored
82
+ bindings at call time and defers unbound tiers to `base` or config; an
83
+ app-only alias is returned as its inline candidate chain because the
84
+ executor reads only the process config. Rows (not layers) are cached per
85
+ tenant for `ttlMs` (30 s) and invalidated by any write in the process; the
86
+ allow-list and policy are re-applied on every call, and only the requested
87
+ tier's chain is validated, so one dangling reference fails one tier.
88
+ Compose it as the per-user resolver's `fallback` (see AGENTS.md,
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.
97
+ - **Panel** (`src/svelte`): built like `PromptSettingsPanel` (presentational,
98
+ `svelte` optional peer, prune script, two-project Vitest, `svelte-check`,
99
+ named `$props()` interfaces, types free of core). The default view is the
100
+ whole screen for one provider; Advanced (tier ladder, `auto` tier,
101
+ aliases, fusions) is not rendered until opened. The key input is its own
102
+ form, never in state, cleared on submit. Tenant scope disables targets
103
+ off the allow-list (display only) and makes aliases/fusions read-only.
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).
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=__smrt-register__.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
@@ -0,0 +1,423 @@
1
+ import { INFERENCE_TIERS, ObjectRegistry, SmrtCollection, SmrtObject, assertInferenceTranscriptsConfig, field, inferenceTargetKind, isInferenceTier, smrt } from "@happyvertical/smrt-core";
2
+ //#region src/__smrt-register__.ts
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
+ //#endregion
5
+ //#region src/settings/cache.ts
6
+ var GENERATION_KEY = /* @__PURE__ */ Symbol.for("smrt.inference.overrideGeneration");
7
+ function getInferenceOverrideGeneration() {
8
+ return globalThis[GENERATION_KEY] ?? 0;
9
+ }
10
+ function bumpInferenceOverrideGeneration() {
11
+ const holder = globalThis;
12
+ holder[GENERATION_KEY] = (holder[GENERATION_KEY] ?? 0) + 1;
13
+ }
14
+ //#endregion
15
+ //#region src/settings/targets.ts
16
+ var INFERENCE_MODES$1 = [
17
+ "byo",
18
+ "local",
19
+ "hosted",
20
+ "off"
21
+ ];
22
+ var DEFAULT_TIER_BINDING = "default-tier";
23
+ var TRANSCRIPTS_BINDING = "transcripts";
24
+ var INFERENCE_BINDING_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
25
+ var InferenceBindingValueError = class extends Error {
26
+ constructor(binding, reason) {
27
+ super(`Invalid inference binding "${binding}": ${reason}`);
28
+ this.binding = binding;
29
+ this.name = "InferenceBindingValueError";
30
+ }
31
+ binding;
32
+ status = 400;
33
+ };
34
+ function parseInferenceBinding(binding) {
35
+ if (typeof binding !== "string") return null;
36
+ if (binding === "default-tier") return { kind: "default-tier" };
37
+ if (binding === "transcripts") return { kind: "transcripts" };
38
+ const match = /^(tier|alias|fusion):(.+)$/.exec(binding);
39
+ if (!match) return null;
40
+ const [, kind, name] = match;
41
+ if (kind === "tier") return isInferenceTier(name) ? {
42
+ kind: "tier",
43
+ name
44
+ } : null;
45
+ if (!INFERENCE_BINDING_NAME_PATTERN.test(name)) return null;
46
+ return {
47
+ kind,
48
+ name
49
+ };
50
+ }
51
+ function tierBinding(tier) {
52
+ return `tier:${tier}`;
53
+ }
54
+ function isRecord(value) {
55
+ return !!value && typeof value === "object" && !Array.isArray(value);
56
+ }
57
+ function text(value) {
58
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
59
+ }
60
+ var KEY_MEMBERS = /* @__PURE__ */ new Set([
61
+ "apikey",
62
+ "api_key",
63
+ "apikeys",
64
+ "secret",
65
+ "token",
66
+ "password",
67
+ "authorization",
68
+ "bearer"
69
+ ]);
70
+ function stripKeyMembers(value) {
71
+ if (Array.isArray(value)) return value.map(stripKeyMembers);
72
+ if (!isRecord(value)) return value;
73
+ const out = {};
74
+ for (const [member, inner] of Object.entries(value)) {
75
+ if (KEY_MEMBERS.has(member.toLowerCase())) continue;
76
+ out[member] = stripKeyMembers(inner);
77
+ }
78
+ return out;
79
+ }
80
+ function findKeyMember(value, path = "") {
81
+ if (Array.isArray(value)) {
82
+ for (const [index, item] of value.entries()) {
83
+ const found = findKeyMember(item, `${path}[${index}]`);
84
+ if (found) return found;
85
+ }
86
+ return null;
87
+ }
88
+ if (!isRecord(value)) return null;
89
+ for (const [member, inner] of Object.entries(value)) {
90
+ const here = path ? `${path}.${member}` : member;
91
+ if (KEY_MEMBERS.has(member.toLowerCase())) return here;
92
+ const found = findKeyMember(inner, here);
93
+ if (found) return found;
94
+ }
95
+ return null;
96
+ }
97
+ function normalizeTierTarget(binding, value) {
98
+ if (value === null || value === void 0) return null;
99
+ if (!isRecord(value)) throw new InferenceBindingValueError(binding, "a tier target is an object");
100
+ const keyMember = findKeyMember(value);
101
+ if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
102
+ const fusion = text(value.fusion);
103
+ const alias = text(value.alias);
104
+ const model = text(value.model);
105
+ const provider = text(value.provider);
106
+ const kind = value.kind;
107
+ const allowed = /* @__PURE__ */ new Set([
108
+ "kind",
109
+ "fusion",
110
+ "alias",
111
+ "model",
112
+ "provider"
113
+ ]);
114
+ const extra = Object.keys(value).filter((member) => !allowed.has(member));
115
+ if (extra.length > 0) throw new InferenceBindingValueError(binding, `unsupported member(s) ${extra.join(", ")} (endpoints and inline candidates belong in an alias)`);
116
+ if ([
117
+ fusion,
118
+ alias,
119
+ model
120
+ ].filter(Boolean).length !== 1) throw new InferenceBindingValueError(binding, "a tier target is exactly one of { model, provider? }, { alias } or { kind: \"fusion\", fusion }");
121
+ if (fusion) {
122
+ if (kind !== void 0 && kind !== "fusion") throw new InferenceBindingValueError(binding, "fusion kind mismatch");
123
+ if (!INFERENCE_BINDING_NAME_PATTERN.test(fusion)) throw new InferenceBindingValueError(binding, "invalid fusion name");
124
+ return {
125
+ kind: "fusion",
126
+ fusion
127
+ };
128
+ }
129
+ if (alias) {
130
+ if (kind !== void 0 && kind !== "alias") throw new InferenceBindingValueError(binding, "alias kind mismatch");
131
+ if (!INFERENCE_BINDING_NAME_PATTERN.test(alias)) throw new InferenceBindingValueError(binding, "invalid alias name");
132
+ return { alias };
133
+ }
134
+ if (kind !== void 0 && kind !== "single") throw new InferenceBindingValueError(binding, "model kind mismatch");
135
+ return provider ? {
136
+ provider,
137
+ model
138
+ } : { model };
139
+ }
140
+ function normalizeAliasDefinition(binding, value) {
141
+ if (value === null || value === void 0) return null;
142
+ if (!isRecord(value) || !Array.isArray(value.candidates)) throw new InferenceBindingValueError(binding, "an alias is { candidates: [...], description? }");
143
+ const keyMember = findKeyMember(value);
144
+ if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
145
+ if (value.candidates.length === 0) throw new InferenceBindingValueError(binding, "an alias needs candidates");
146
+ const allowed = /* @__PURE__ */ new Set([
147
+ "provider",
148
+ "model",
149
+ "baseUrl",
150
+ "apiKeyEnv",
151
+ "preset",
152
+ "alias",
153
+ "params"
154
+ ]);
155
+ const candidates = value.candidates.map((candidate, index) => {
156
+ if (!isRecord(candidate)) throw new InferenceBindingValueError(binding, `candidates[${index}] must be an object`);
157
+ const extra = Object.keys(candidate).filter((member) => !allowed.has(member));
158
+ if (extra.length > 0) throw new InferenceBindingValueError(binding, `candidates[${index}] has unsupported member(s) ${extra.join(", ")}`);
159
+ const out = {};
160
+ for (const member of allowed) {
161
+ if (member === "params") continue;
162
+ const inner = text(candidate[member]);
163
+ if (inner) out[member] = inner;
164
+ }
165
+ if (candidate.params !== void 0) {
166
+ if (!isRecord(candidate.params)) throw new InferenceBindingValueError(binding, `candidates[${index}].params must be an object`);
167
+ out.params = { ...candidate.params };
168
+ }
169
+ return out;
170
+ });
171
+ const description = text(value.description);
172
+ return description ? {
173
+ candidates,
174
+ description
175
+ } : { candidates };
176
+ }
177
+ function normalizeFusionDefinition(binding, value) {
178
+ if (value === null || value === void 0) return null;
179
+ if (!isRecord(value)) throw new InferenceBindingValueError(binding, "a fusion is a JSON object");
180
+ const keyMember = findKeyMember(value);
181
+ if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
182
+ return JSON.parse(JSON.stringify(value));
183
+ }
184
+ function normalizeDefaultTier(binding, value) {
185
+ if (value === null || value === void 0) return null;
186
+ const tier = isRecord(value) ? value.tier : value;
187
+ if (!isInferenceTier(tier)) throw new InferenceBindingValueError(binding, `the default tier must be one of ${INFERENCE_TIERS.join(", ")}`);
188
+ return tier;
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
+ }
211
+ function inferenceTargetRef(target) {
212
+ const kind = inferenceTargetKind(target);
213
+ const record = target;
214
+ if (kind === "fusion") return `fusion:${record.fusion ?? ""}`;
215
+ if (kind === "alias") return `alias:${target.alias ?? ""}`;
216
+ if (kind === "candidates") return "candidates";
217
+ return `model:${target.provider ?? "default"}/${target.model ?? "default"}`;
218
+ }
219
+ function matchesAllowPattern(ref, pattern) {
220
+ if (pattern === "*") return true;
221
+ if (pattern.endsWith("*")) return ref.startsWith(pattern.slice(0, -1));
222
+ return ref === pattern;
223
+ }
224
+ function isAllowedByList(ref, allowList) {
225
+ return (allowList ?? []).some((pattern) => matchesAllowPattern(ref, pattern));
226
+ }
227
+ function normalizeAllowList(binding, value) {
228
+ if (value === null || value === void 0) return null;
229
+ if (!Array.isArray(value)) throw new InferenceBindingValueError(binding, "an allow-list is an array of references");
230
+ const out = [];
231
+ for (const entry of value) {
232
+ if (typeof entry !== "string" || !/^(\*|(model|alias|fusion|tier):[A-Za-z0-9._/:*-]{1,160})$/.test(entry)) throw new InferenceBindingValueError(binding, `invalid allow-list entry ${JSON.stringify(entry)}`);
233
+ if (!out.includes(entry)) out.push(entry);
234
+ }
235
+ return out;
236
+ }
237
+ function describeInferenceTarget(target) {
238
+ if (!target || Object.keys(target).length === 0) return "Same as default (configured client)";
239
+ const kind = inferenceTargetKind(target);
240
+ const record = target;
241
+ if (kind === "fusion") return `Fusion ${record.fusion ?? "?"}`;
242
+ if (kind === "alias") return `Alias ${target.alias ?? "?"}`;
243
+ if (kind === "candidates") return `${target.candidates?.length ?? 0} inline candidate(s)`;
244
+ return `${target.provider ?? "default"}/${target.model ?? "default model"}`;
245
+ }
246
+ //#endregion
247
+ //#region src/models/InferenceOverride.ts
248
+ var __defProp = Object.defineProperty;
249
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
250
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
251
+ enumerable: true,
252
+ configurable: true,
253
+ writable: true,
254
+ value
255
+ }) : obj[key] = value;
256
+ var __decorateClass = (decorators, target, key, kind) => {
257
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
258
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
259
+ if (kind && result) __defProp(target, key, result);
260
+ return result;
261
+ };
262
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
263
+ var APP_INFERENCE_SCOPE_ID = "__app__";
264
+ function parseJson(binding, raw, what) {
265
+ if (raw === null) return null;
266
+ try {
267
+ return JSON.parse(raw);
268
+ } catch {
269
+ throw new InferenceBindingValueError(binding, `${what} is not valid JSON`);
270
+ }
271
+ }
272
+ var InferenceOverride = class extends SmrtObject {
273
+ binding = "";
274
+ tenantId = null;
275
+ target = null;
276
+ tenantAllowList = null;
277
+ constructor(options = {}) {
278
+ super(options);
279
+ if (options.binding !== void 0) this.binding = options.binding;
280
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
281
+ if (options.target !== void 0) this.target = options.target;
282
+ if (options.tenantAllowList !== void 0) this.tenantAllowList = options.tenantAllowList;
283
+ }
284
+ /** The parsed, validated stored value (`null` inherits). */
285
+ getValue() {
286
+ return this.normalizedValue();
287
+ }
288
+ /** The parsed allow-list (`null` when the row carries none). */
289
+ getTenantAllowList() {
290
+ return normalizeAllowList(this.binding, parseJson(this.binding, this.tenantAllowList, "tenantAllowList"));
291
+ }
292
+ async save(options) {
293
+ this.validateOverride();
294
+ this.context = this.tenantId ?? "__app__";
295
+ const result = await super.save(options);
296
+ bumpInferenceOverrideGeneration();
297
+ return result;
298
+ }
299
+ async delete() {
300
+ await super.delete();
301
+ bumpInferenceOverrideGeneration();
302
+ }
303
+ normalizedValue() {
304
+ const parsed = parseInferenceBinding(this.binding);
305
+ if (!parsed) throw new InferenceBindingValueError(String(this.binding), "unknown binding id");
306
+ const raw = parseJson(this.binding, this.target, "target");
307
+ switch (parsed.kind) {
308
+ case "tier": return normalizeTierTarget(this.binding, raw);
309
+ case "default-tier": return normalizeDefaultTier(this.binding, raw);
310
+ case "transcripts": return normalizeTranscriptSettings(this.binding, raw);
311
+ case "alias": return normalizeAliasDefinition(this.binding, raw);
312
+ case "fusion": return normalizeFusionDefinition(this.binding, raw);
313
+ }
314
+ }
315
+ validateOverride() {
316
+ const parsed = parseInferenceBinding(this.binding);
317
+ if (!parsed) throw new InferenceBindingValueError(String(this.binding), "unknown binding id");
318
+ if (this.tenantId !== null) {
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");
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");
322
+ if (this.tenantAllowList !== null) throw new InferenceBindingValueError(this.binding, "the tenant allow-list lives on the app row");
323
+ }
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");
325
+ const value = this.normalizedValue();
326
+ this.target = value === null ? null : JSON.stringify(value);
327
+ const allowList = this.getTenantAllowList();
328
+ this.tenantAllowList = allowList === null ? null : JSON.stringify(allowList);
329
+ }
330
+ };
331
+ __publicField(InferenceOverride, "__smrtPackage__", "@happyvertical/smrt-inference");
332
+ __decorateClass([field({ required: true })], InferenceOverride.prototype, "binding", 2);
333
+ __decorateClass([field({
334
+ sqlType: "UUID",
335
+ nullable: true
336
+ })], InferenceOverride.prototype, "tenantId", 2);
337
+ __decorateClass([field({
338
+ type: "text",
339
+ nullable: true
340
+ })], InferenceOverride.prototype, "target", 2);
341
+ __decorateClass([field({
342
+ type: "text",
343
+ nullable: true
344
+ })], InferenceOverride.prototype, "tenantAllowList", 2);
345
+ InferenceOverride = __decorateClass([smrt({
346
+ tableName: "_smrt_inference_overrides",
347
+ conflictColumns: ["binding", "context"],
348
+ api: false,
349
+ cli: false,
350
+ mcp: false,
351
+ indexes: [{
352
+ name: "idx_smrt_inference_overrides_tenant",
353
+ columns: ["tenantId", "binding"]
354
+ }]
355
+ })], InferenceOverride);
356
+ //#endregion
357
+ //#region src/models/InferenceOverrideCollection.ts
358
+ var InferenceOverrideCollection = class extends SmrtCollection {
359
+ static _itemClass = InferenceOverride;
360
+ /**
361
+ * Every row of one scope, keyed by binding id: one bounded read. An
362
+ * enumeration read: build the collection without list bounds (the
363
+ * settings service does) so a host-wide `maxListLimit` cannot drop a row.
364
+ */
365
+ async getScopeMap(scopeType, scopeId) {
366
+ const tenantId = scopeType === "app" ? null : scopeId;
367
+ const rows = await this.list({ where: { tenantId } });
368
+ const result = /* @__PURE__ */ new Map();
369
+ for (const row of rows) result.set(row.binding, row);
370
+ return result;
371
+ }
372
+ /** The row for one binding at one scope. */
373
+ async findByScope(binding, scopeType, scopeId) {
374
+ const tenantId = scopeType === "app" ? null : scopeId;
375
+ return (await this.list({
376
+ where: {
377
+ binding,
378
+ tenantId
379
+ },
380
+ limit: 1
381
+ }))[0] ?? null;
382
+ }
383
+ /**
384
+ * Set the stored value (and, for app rows, optionally the tenant
385
+ * allow-list) of one binding at one scope, creating the row when needed.
386
+ * `undefined` leaves a column untouched; `null` clears it. A row left with
387
+ * neither a value nor an allow-list is deleted.
388
+ */
389
+ async setOverride(binding, scopeType, scopeId, change) {
390
+ const existing = await this.findByScope(binding, scopeType, scopeId);
391
+ const target = change.target === void 0 ? existing?.target ?? null : change.target === null ? null : JSON.stringify(change.target);
392
+ const tenantAllowList = change.tenantAllowList === void 0 ? existing?.tenantAllowList ?? null : change.tenantAllowList === null ? null : JSON.stringify(change.tenantAllowList);
393
+ if (target === null && tenantAllowList === null) {
394
+ if (existing) await existing.delete();
395
+ return null;
396
+ }
397
+ if (existing) {
398
+ existing.target = target;
399
+ existing.tenantAllowList = tenantAllowList;
400
+ await existing.save();
401
+ return existing;
402
+ }
403
+ const created = await this.create({
404
+ binding,
405
+ tenantId: scopeType === "app" ? null : scopeId,
406
+ target,
407
+ tenantAllowList
408
+ });
409
+ await created.save();
410
+ return created;
411
+ }
412
+ /** Delete the row for one binding at one scope, if any. */
413
+ async removeOverride(binding, scopeType, scopeId) {
414
+ const existing = await this.findByScope(binding, scopeType, scopeId);
415
+ if (!existing) return false;
416
+ await existing.delete();
417
+ return true;
418
+ }
419
+ };
420
+ //#endregion
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 };
422
+
423
+ //# sourceMappingURL=InferenceOverrideCollection-Dat4TrK5.js.map