@happyvertical/smrt-inference 0.0.0-stage → 0.60.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +201 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +73 -2
- package/agents/fusions.md +92 -0
- package/agents/hosted.md +145 -0
- package/agents/resolver.md +65 -0
- package/agents/settings.md +84 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js +398 -0
- package/dist/chunks/InferenceOverrideCollection-wl7sI-3C.js.map +1 -0
- package/dist/chunks/provider-config-4Bx8N52X.js +1847 -0
- package/dist/chunks/provider-config-4Bx8N52X.js.map +1 -0
- package/dist/classify.d.ts +32 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/config.d.ts +89 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/executor.d.ts +45 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/fusion-store.d.ts +16 -0
- package/dist/fusion-store.d.ts.map +1 -0
- package/dist/fusion.d.ts +155 -0
- package/dist/fusion.d.ts.map +1 -0
- package/dist/gate.d.ts +143 -0
- package/dist/gate.d.ts.map +1 -0
- package/dist/gate.js +94 -0
- package/dist/gate.js.map +1 -0
- package/dist/hosted.d.ts +454 -0
- package/dist/hosted.d.ts.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +453 -0
- package/dist/models/InferenceOverride.d.ts +38 -0
- package/dist/models/InferenceOverride.d.ts.map +1 -0
- package/dist/models/InferenceOverrideCollection.d.ts +35 -0
- package/dist/models/InferenceOverrideCollection.d.ts.map +1 -0
- package/dist/models.d.ts +11 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +2 -0
- package/dist/node.d.ts +110 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +1361 -0
- package/dist/node.js.map +1 -0
- package/dist/provider-config.d.ts +113 -0
- package/dist/provider-config.d.ts.map +1 -0
- package/dist/register.d.ts +30 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/resolver.d.ts +166 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +288 -0
- package/dist/resolver.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +985 -0
- package/dist/server.js.map +1 -0
- package/dist/settings/cache.d.ts +5 -0
- package/dist/settings/cache.d.ts.map +1 -0
- package/dist/settings/keys.d.ts +70 -0
- package/dist/settings/keys.d.ts.map +1 -0
- package/dist/settings/layers.d.ts +54 -0
- package/dist/settings/layers.d.ts.map +1 -0
- package/dist/settings/override-service.d.ts +119 -0
- package/dist/settings/override-service.d.ts.map +1 -0
- package/dist/settings/preferences.d.ts +137 -0
- package/dist/settings/preferences.d.ts.map +1 -0
- package/dist/settings/resolver.d.ts +34 -0
- package/dist/settings/resolver.d.ts.map +1 -0
- package/dist/settings/settings-service.d.ts +282 -0
- package/dist/settings/settings-service.d.ts.map +1 -0
- package/dist/settings/targets.d.ts +93 -0
- package/dist/settings/targets.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +335 -0
- package/dist/smrt-knowledge.okf.md +80 -0
- package/dist/svelte/components/InferenceAliasEditor.svelte +210 -0
- package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts +13 -0
- package/dist/svelte/components/InferenceAliasEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InferenceDefaultView.svelte +330 -0
- package/dist/svelte/components/InferenceDefaultView.svelte.d.ts +15 -0
- package/dist/svelte/components/InferenceDefaultView.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InferenceFusionList.svelte +135 -0
- package/dist/svelte/components/InferenceFusionList.svelte.d.ts +16 -0
- package/dist/svelte/components/InferenceFusionList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InferenceSettingsPanel.svelte +345 -0
- package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts +65 -0
- package/dist/svelte/components/InferenceSettingsPanel.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InferenceTierRow.svelte +199 -0
- package/dist/svelte/components/InferenceTierRow.svelte.d.ts +14 -0
- package/dist/svelte/components/InferenceTierRow.svelte.d.ts.map +1 -0
- package/dist/svelte/components/InferenceUsageTable.svelte +127 -0
- package/dist/svelte/components/InferenceUsageTable.svelte.d.ts +15 -0
- package/dist/svelte/components/InferenceUsageTable.svelte.d.ts.map +1 -0
- package/dist/svelte/index.d.ts +17 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +14 -0
- package/dist/svelte/types.d.ts +197 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +96 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +105 -3
|
@@ -0,0 +1,84 @@
|
|
|
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). Writes go through `InferenceOverrideService`
|
|
54
|
+
(authorizer first, fails closed; without `authorize` the service is
|
|
55
|
+
read-only) and refuse a value that adds a config issue to the layer it
|
|
56
|
+
lands in; removing an app alias or fusion that any row (every tenant) or
|
|
57
|
+
config still names is refused (`InferenceBindingInUseError`, tenant rows
|
|
58
|
+
only counted, checked after the authorizer allowed the write) unless
|
|
59
|
+
config or the registry keeps defining it. Every config-backed value a row method
|
|
60
|
+
returns passes `stripKeyMembers()` (config may carry literal keys);
|
|
61
|
+
`getEffectiveConfig()` is server-side only. `testConnection()`, `testCandidate()` and `doctor()` reuse
|
|
62
|
+
`./node`; `testCandidate()` reads a key variable only for a candidate
|
|
63
|
+
already in the effective config (an unsaved one is probed keyless);
|
|
64
|
+
`usage()` is `reportAiUsage()` by model (default), tier, prompt.
|
|
65
|
+
- **`createInferenceOverrideResolver({ db, base })`** applies stored
|
|
66
|
+
bindings at call time and defers unbound tiers to `base` or config; an
|
|
67
|
+
app-only alias is returned as its inline candidate chain because the
|
|
68
|
+
executor reads only the process config. Rows (not layers) are cached per
|
|
69
|
+
tenant for `ttlMs` (30 s) and invalidated by any write in the process; the
|
|
70
|
+
allow-list and policy are re-applied on every call, and only the requested
|
|
71
|
+
tier's chain is validated, so one dangling reference fails one tier.
|
|
72
|
+
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).
|
|
77
|
+
- **Panel** (`src/svelte`): built like `PromptSettingsPanel` (presentational,
|
|
78
|
+
`svelte` optional peer, prune script, two-project Vitest, `svelte-check`,
|
|
79
|
+
named `$props()` interfaces, types free of core). The default view is the
|
|
80
|
+
whole screen for one provider; Advanced (tier ladder, `auto` tier,
|
|
81
|
+
aliases, fusions) is not rendered until opened. The key input is its own
|
|
82
|
+
form, never in state, cleared on submit. Tenant scope disables targets
|
|
83
|
+
off the allow-list (display only) and makes aliases/fusions read-only.
|
|
84
|
+
Rows remount on `contextKey`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"__smrt-register__.d.ts","sourceRoot":"","sources":["../src/__smrt-register__.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { INFERENCE_TIERS, ObjectRegistry, SmrtCollection, SmrtObject, 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.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}`));
|
|
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 INFERENCE_BINDING_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
24
|
+
var InferenceBindingValueError = class extends Error {
|
|
25
|
+
constructor(binding, reason) {
|
|
26
|
+
super(`Invalid inference binding "${binding}": ${reason}`);
|
|
27
|
+
this.binding = binding;
|
|
28
|
+
this.name = "InferenceBindingValueError";
|
|
29
|
+
}
|
|
30
|
+
binding;
|
|
31
|
+
status = 400;
|
|
32
|
+
};
|
|
33
|
+
function parseInferenceBinding(binding) {
|
|
34
|
+
if (typeof binding !== "string") return null;
|
|
35
|
+
if (binding === "default-tier") return { kind: "default-tier" };
|
|
36
|
+
const match = /^(tier|alias|fusion):(.+)$/.exec(binding);
|
|
37
|
+
if (!match) return null;
|
|
38
|
+
const [, kind, name] = match;
|
|
39
|
+
if (kind === "tier") return isInferenceTier(name) ? {
|
|
40
|
+
kind: "tier",
|
|
41
|
+
name
|
|
42
|
+
} : null;
|
|
43
|
+
if (!INFERENCE_BINDING_NAME_PATTERN.test(name)) return null;
|
|
44
|
+
return {
|
|
45
|
+
kind,
|
|
46
|
+
name
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function tierBinding(tier) {
|
|
50
|
+
return `tier:${tier}`;
|
|
51
|
+
}
|
|
52
|
+
function isRecord(value) {
|
|
53
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
54
|
+
}
|
|
55
|
+
function text(value) {
|
|
56
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
|
|
57
|
+
}
|
|
58
|
+
var KEY_MEMBERS = /* @__PURE__ */ new Set([
|
|
59
|
+
"apikey",
|
|
60
|
+
"api_key",
|
|
61
|
+
"apikeys",
|
|
62
|
+
"secret",
|
|
63
|
+
"token",
|
|
64
|
+
"password",
|
|
65
|
+
"authorization",
|
|
66
|
+
"bearer"
|
|
67
|
+
]);
|
|
68
|
+
function stripKeyMembers(value) {
|
|
69
|
+
if (Array.isArray(value)) return value.map(stripKeyMembers);
|
|
70
|
+
if (!isRecord(value)) return value;
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const [member, inner] of Object.entries(value)) {
|
|
73
|
+
if (KEY_MEMBERS.has(member.toLowerCase())) continue;
|
|
74
|
+
out[member] = stripKeyMembers(inner);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function findKeyMember(value, path = "") {
|
|
79
|
+
if (Array.isArray(value)) {
|
|
80
|
+
for (const [index, item] of value.entries()) {
|
|
81
|
+
const found = findKeyMember(item, `${path}[${index}]`);
|
|
82
|
+
if (found) return found;
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!isRecord(value)) return null;
|
|
87
|
+
for (const [member, inner] of Object.entries(value)) {
|
|
88
|
+
const here = path ? `${path}.${member}` : member;
|
|
89
|
+
if (KEY_MEMBERS.has(member.toLowerCase())) return here;
|
|
90
|
+
const found = findKeyMember(inner, here);
|
|
91
|
+
if (found) return found;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function normalizeTierTarget(binding, value) {
|
|
96
|
+
if (value === null || value === void 0) return null;
|
|
97
|
+
if (!isRecord(value)) throw new InferenceBindingValueError(binding, "a tier target is an object");
|
|
98
|
+
const keyMember = findKeyMember(value);
|
|
99
|
+
if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
|
|
100
|
+
const fusion = text(value.fusion);
|
|
101
|
+
const alias = text(value.alias);
|
|
102
|
+
const model = text(value.model);
|
|
103
|
+
const provider = text(value.provider);
|
|
104
|
+
const kind = value.kind;
|
|
105
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
106
|
+
"kind",
|
|
107
|
+
"fusion",
|
|
108
|
+
"alias",
|
|
109
|
+
"model",
|
|
110
|
+
"provider"
|
|
111
|
+
]);
|
|
112
|
+
const extra = Object.keys(value).filter((member) => !allowed.has(member));
|
|
113
|
+
if (extra.length > 0) throw new InferenceBindingValueError(binding, `unsupported member(s) ${extra.join(", ")} (endpoints and inline candidates belong in an alias)`);
|
|
114
|
+
if ([
|
|
115
|
+
fusion,
|
|
116
|
+
alias,
|
|
117
|
+
model
|
|
118
|
+
].filter(Boolean).length !== 1) throw new InferenceBindingValueError(binding, "a tier target is exactly one of { model, provider? }, { alias } or { kind: \"fusion\", fusion }");
|
|
119
|
+
if (fusion) {
|
|
120
|
+
if (kind !== void 0 && kind !== "fusion") throw new InferenceBindingValueError(binding, "fusion kind mismatch");
|
|
121
|
+
if (!INFERENCE_BINDING_NAME_PATTERN.test(fusion)) throw new InferenceBindingValueError(binding, "invalid fusion name");
|
|
122
|
+
return {
|
|
123
|
+
kind: "fusion",
|
|
124
|
+
fusion
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (alias) {
|
|
128
|
+
if (kind !== void 0 && kind !== "alias") throw new InferenceBindingValueError(binding, "alias kind mismatch");
|
|
129
|
+
if (!INFERENCE_BINDING_NAME_PATTERN.test(alias)) throw new InferenceBindingValueError(binding, "invalid alias name");
|
|
130
|
+
return { alias };
|
|
131
|
+
}
|
|
132
|
+
if (kind !== void 0 && kind !== "single") throw new InferenceBindingValueError(binding, "model kind mismatch");
|
|
133
|
+
return provider ? {
|
|
134
|
+
provider,
|
|
135
|
+
model
|
|
136
|
+
} : { model };
|
|
137
|
+
}
|
|
138
|
+
function normalizeAliasDefinition(binding, value) {
|
|
139
|
+
if (value === null || value === void 0) return null;
|
|
140
|
+
if (!isRecord(value) || !Array.isArray(value.candidates)) throw new InferenceBindingValueError(binding, "an alias is { candidates: [...], description? }");
|
|
141
|
+
const keyMember = findKeyMember(value);
|
|
142
|
+
if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
|
|
143
|
+
if (value.candidates.length === 0) throw new InferenceBindingValueError(binding, "an alias needs candidates");
|
|
144
|
+
const allowed = /* @__PURE__ */ new Set([
|
|
145
|
+
"provider",
|
|
146
|
+
"model",
|
|
147
|
+
"baseUrl",
|
|
148
|
+
"apiKeyEnv",
|
|
149
|
+
"preset",
|
|
150
|
+
"alias",
|
|
151
|
+
"params"
|
|
152
|
+
]);
|
|
153
|
+
const candidates = value.candidates.map((candidate, index) => {
|
|
154
|
+
if (!isRecord(candidate)) throw new InferenceBindingValueError(binding, `candidates[${index}] must be an object`);
|
|
155
|
+
const extra = Object.keys(candidate).filter((member) => !allowed.has(member));
|
|
156
|
+
if (extra.length > 0) throw new InferenceBindingValueError(binding, `candidates[${index}] has unsupported member(s) ${extra.join(", ")}`);
|
|
157
|
+
const out = {};
|
|
158
|
+
for (const member of allowed) {
|
|
159
|
+
if (member === "params") continue;
|
|
160
|
+
const inner = text(candidate[member]);
|
|
161
|
+
if (inner) out[member] = inner;
|
|
162
|
+
}
|
|
163
|
+
if (candidate.params !== void 0) {
|
|
164
|
+
if (!isRecord(candidate.params)) throw new InferenceBindingValueError(binding, `candidates[${index}].params must be an object`);
|
|
165
|
+
out.params = { ...candidate.params };
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
168
|
+
});
|
|
169
|
+
const description = text(value.description);
|
|
170
|
+
return description ? {
|
|
171
|
+
candidates,
|
|
172
|
+
description
|
|
173
|
+
} : { candidates };
|
|
174
|
+
}
|
|
175
|
+
function normalizeFusionDefinition(binding, value) {
|
|
176
|
+
if (value === null || value === void 0) return null;
|
|
177
|
+
if (!isRecord(value)) throw new InferenceBindingValueError(binding, "a fusion is a JSON object");
|
|
178
|
+
const keyMember = findKeyMember(value);
|
|
179
|
+
if (keyMember) throw new InferenceBindingValueError(binding, `"${keyMember}" looks like a credential; keys are stored only through smrt-secrets`);
|
|
180
|
+
return JSON.parse(JSON.stringify(value));
|
|
181
|
+
}
|
|
182
|
+
function normalizeDefaultTier(binding, value) {
|
|
183
|
+
if (value === null || value === void 0) return null;
|
|
184
|
+
const tier = isRecord(value) ? value.tier : value;
|
|
185
|
+
if (!isInferenceTier(tier)) throw new InferenceBindingValueError(binding, `the default tier must be one of ${INFERENCE_TIERS.join(", ")}`);
|
|
186
|
+
return tier;
|
|
187
|
+
}
|
|
188
|
+
function inferenceTargetRef(target) {
|
|
189
|
+
const kind = inferenceTargetKind(target);
|
|
190
|
+
const record = target;
|
|
191
|
+
if (kind === "fusion") return `fusion:${record.fusion ?? ""}`;
|
|
192
|
+
if (kind === "alias") return `alias:${target.alias ?? ""}`;
|
|
193
|
+
if (kind === "candidates") return "candidates";
|
|
194
|
+
return `model:${target.provider ?? "default"}/${target.model ?? "default"}`;
|
|
195
|
+
}
|
|
196
|
+
function matchesAllowPattern(ref, pattern) {
|
|
197
|
+
if (pattern === "*") return true;
|
|
198
|
+
if (pattern.endsWith("*")) return ref.startsWith(pattern.slice(0, -1));
|
|
199
|
+
return ref === pattern;
|
|
200
|
+
}
|
|
201
|
+
function isAllowedByList(ref, allowList) {
|
|
202
|
+
return (allowList ?? []).some((pattern) => matchesAllowPattern(ref, pattern));
|
|
203
|
+
}
|
|
204
|
+
function normalizeAllowList(binding, value) {
|
|
205
|
+
if (value === null || value === void 0) return null;
|
|
206
|
+
if (!Array.isArray(value)) throw new InferenceBindingValueError(binding, "an allow-list is an array of references");
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const entry of value) {
|
|
209
|
+
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)}`);
|
|
210
|
+
if (!out.includes(entry)) out.push(entry);
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
function describeInferenceTarget(target) {
|
|
215
|
+
if (!target || Object.keys(target).length === 0) return "Same as default (configured client)";
|
|
216
|
+
const kind = inferenceTargetKind(target);
|
|
217
|
+
const record = target;
|
|
218
|
+
if (kind === "fusion") return `Fusion ${record.fusion ?? "?"}`;
|
|
219
|
+
if (kind === "alias") return `Alias ${target.alias ?? "?"}`;
|
|
220
|
+
if (kind === "candidates") return `${target.candidates?.length ?? 0} inline candidate(s)`;
|
|
221
|
+
return `${target.provider ?? "default"}/${target.model ?? "default model"}`;
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/models/InferenceOverride.ts
|
|
225
|
+
var __defProp = Object.defineProperty;
|
|
226
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
227
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, {
|
|
228
|
+
enumerable: true,
|
|
229
|
+
configurable: true,
|
|
230
|
+
writable: true,
|
|
231
|
+
value
|
|
232
|
+
}) : obj[key] = value;
|
|
233
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
234
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
235
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
236
|
+
if (kind && result) __defProp(target, key, result);
|
|
237
|
+
return result;
|
|
238
|
+
};
|
|
239
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
240
|
+
var APP_INFERENCE_SCOPE_ID = "__app__";
|
|
241
|
+
function parseJson(binding, raw, what) {
|
|
242
|
+
if (raw === null) return null;
|
|
243
|
+
try {
|
|
244
|
+
return JSON.parse(raw);
|
|
245
|
+
} catch {
|
|
246
|
+
throw new InferenceBindingValueError(binding, `${what} is not valid JSON`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
var InferenceOverride = class extends SmrtObject {
|
|
250
|
+
binding = "";
|
|
251
|
+
tenantId = null;
|
|
252
|
+
target = null;
|
|
253
|
+
tenantAllowList = null;
|
|
254
|
+
constructor(options = {}) {
|
|
255
|
+
super(options);
|
|
256
|
+
if (options.binding !== void 0) this.binding = options.binding;
|
|
257
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
258
|
+
if (options.target !== void 0) this.target = options.target;
|
|
259
|
+
if (options.tenantAllowList !== void 0) this.tenantAllowList = options.tenantAllowList;
|
|
260
|
+
}
|
|
261
|
+
/** The parsed, validated stored value (`null` inherits). */
|
|
262
|
+
getValue() {
|
|
263
|
+
return this.normalizedValue();
|
|
264
|
+
}
|
|
265
|
+
/** The parsed allow-list (`null` when the row carries none). */
|
|
266
|
+
getTenantAllowList() {
|
|
267
|
+
return normalizeAllowList(this.binding, parseJson(this.binding, this.tenantAllowList, "tenantAllowList"));
|
|
268
|
+
}
|
|
269
|
+
async save(options) {
|
|
270
|
+
this.validateOverride();
|
|
271
|
+
this.context = this.tenantId ?? "__app__";
|
|
272
|
+
const result = await super.save(options);
|
|
273
|
+
bumpInferenceOverrideGeneration();
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
async delete() {
|
|
277
|
+
await super.delete();
|
|
278
|
+
bumpInferenceOverrideGeneration();
|
|
279
|
+
}
|
|
280
|
+
normalizedValue() {
|
|
281
|
+
const parsed = parseInferenceBinding(this.binding);
|
|
282
|
+
if (!parsed) throw new InferenceBindingValueError(String(this.binding), "unknown binding id");
|
|
283
|
+
const raw = parseJson(this.binding, this.target, "target");
|
|
284
|
+
switch (parsed.kind) {
|
|
285
|
+
case "tier": return normalizeTierTarget(this.binding, raw);
|
|
286
|
+
case "default-tier": return normalizeDefaultTier(this.binding, raw);
|
|
287
|
+
case "alias": return normalizeAliasDefinition(this.binding, raw);
|
|
288
|
+
case "fusion": return normalizeFusionDefinition(this.binding, raw);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
validateOverride() {
|
|
292
|
+
const parsed = parseInferenceBinding(this.binding);
|
|
293
|
+
if (!parsed) throw new InferenceBindingValueError(String(this.binding), "unknown binding id");
|
|
294
|
+
if (this.tenantId !== null) {
|
|
295
|
+
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
|
+
if (parsed.kind === "alias" || parsed.kind === "fusion") throw new InferenceBindingValueError(this.binding, "aliases and fusions are defined at the app scope only");
|
|
297
|
+
if (this.tenantAllowList !== null) throw new InferenceBindingValueError(this.binding, "the tenant allow-list lives on the app row");
|
|
298
|
+
}
|
|
299
|
+
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");
|
|
300
|
+
const value = this.normalizedValue();
|
|
301
|
+
this.target = value === null ? null : JSON.stringify(value);
|
|
302
|
+
const allowList = this.getTenantAllowList();
|
|
303
|
+
this.tenantAllowList = allowList === null ? null : JSON.stringify(allowList);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
__publicField(InferenceOverride, "__smrtPackage__", "@happyvertical/smrt-inference");
|
|
307
|
+
__decorateClass([field({ required: true })], InferenceOverride.prototype, "binding", 2);
|
|
308
|
+
__decorateClass([field({
|
|
309
|
+
sqlType: "UUID",
|
|
310
|
+
nullable: true
|
|
311
|
+
})], InferenceOverride.prototype, "tenantId", 2);
|
|
312
|
+
__decorateClass([field({
|
|
313
|
+
type: "text",
|
|
314
|
+
nullable: true
|
|
315
|
+
})], InferenceOverride.prototype, "target", 2);
|
|
316
|
+
__decorateClass([field({
|
|
317
|
+
type: "text",
|
|
318
|
+
nullable: true
|
|
319
|
+
})], InferenceOverride.prototype, "tenantAllowList", 2);
|
|
320
|
+
InferenceOverride = __decorateClass([smrt({
|
|
321
|
+
tableName: "_smrt_inference_overrides",
|
|
322
|
+
conflictColumns: ["binding", "context"],
|
|
323
|
+
api: false,
|
|
324
|
+
cli: false,
|
|
325
|
+
mcp: false,
|
|
326
|
+
indexes: [{
|
|
327
|
+
name: "idx_smrt_inference_overrides_tenant",
|
|
328
|
+
columns: ["tenantId", "binding"]
|
|
329
|
+
}]
|
|
330
|
+
})], InferenceOverride);
|
|
331
|
+
//#endregion
|
|
332
|
+
//#region src/models/InferenceOverrideCollection.ts
|
|
333
|
+
var InferenceOverrideCollection = class extends SmrtCollection {
|
|
334
|
+
static _itemClass = InferenceOverride;
|
|
335
|
+
/**
|
|
336
|
+
* Every row of one scope, keyed by binding id: one bounded read. An
|
|
337
|
+
* enumeration read: build the collection without list bounds (the
|
|
338
|
+
* settings service does) so a host-wide `maxListLimit` cannot drop a row.
|
|
339
|
+
*/
|
|
340
|
+
async getScopeMap(scopeType, scopeId) {
|
|
341
|
+
const tenantId = scopeType === "app" ? null : scopeId;
|
|
342
|
+
const rows = await this.list({ where: { tenantId } });
|
|
343
|
+
const result = /* @__PURE__ */ new Map();
|
|
344
|
+
for (const row of rows) result.set(row.binding, row);
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
/** The row for one binding at one scope. */
|
|
348
|
+
async findByScope(binding, scopeType, scopeId) {
|
|
349
|
+
const tenantId = scopeType === "app" ? null : scopeId;
|
|
350
|
+
return (await this.list({
|
|
351
|
+
where: {
|
|
352
|
+
binding,
|
|
353
|
+
tenantId
|
|
354
|
+
},
|
|
355
|
+
limit: 1
|
|
356
|
+
}))[0] ?? null;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Set the stored value (and, for app rows, optionally the tenant
|
|
360
|
+
* allow-list) of one binding at one scope, creating the row when needed.
|
|
361
|
+
* `undefined` leaves a column untouched; `null` clears it. A row left with
|
|
362
|
+
* neither a value nor an allow-list is deleted.
|
|
363
|
+
*/
|
|
364
|
+
async setOverride(binding, scopeType, scopeId, change) {
|
|
365
|
+
const existing = await this.findByScope(binding, scopeType, scopeId);
|
|
366
|
+
const target = change.target === void 0 ? existing?.target ?? null : change.target === null ? null : JSON.stringify(change.target);
|
|
367
|
+
const tenantAllowList = change.tenantAllowList === void 0 ? existing?.tenantAllowList ?? null : change.tenantAllowList === null ? null : JSON.stringify(change.tenantAllowList);
|
|
368
|
+
if (target === null && tenantAllowList === null) {
|
|
369
|
+
if (existing) await existing.delete();
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
if (existing) {
|
|
373
|
+
existing.target = target;
|
|
374
|
+
existing.tenantAllowList = tenantAllowList;
|
|
375
|
+
await existing.save();
|
|
376
|
+
return existing;
|
|
377
|
+
}
|
|
378
|
+
const created = await this.create({
|
|
379
|
+
binding,
|
|
380
|
+
tenantId: scopeType === "app" ? null : scopeId,
|
|
381
|
+
target,
|
|
382
|
+
tenantAllowList
|
|
383
|
+
});
|
|
384
|
+
await created.save();
|
|
385
|
+
return created;
|
|
386
|
+
}
|
|
387
|
+
/** Delete the row for one binding at one scope, if any. */
|
|
388
|
+
async removeOverride(binding, scopeType, scopeId) {
|
|
389
|
+
const existing = await this.findByScope(binding, scopeType, scopeId);
|
|
390
|
+
if (!existing) return false;
|
|
391
|
+
await existing.delete();
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
//#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 };
|
|
397
|
+
|
|
398
|
+
//# sourceMappingURL=InferenceOverrideCollection-wl7sI-3C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InferenceOverrideCollection-wl7sI-3C.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.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}\"));\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 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/** A binding id stored in `_smrt_inference_overrides.binding`. */\nexport type InferenceBindingId =\n | `tier:${InferenceTier}`\n | typeof DEFAULT_TIER_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 = 'tier' | 'default-tier' | 'alias' | '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 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/**\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 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 '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 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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"}
|