@happyvertical/smrt-config 0.53.2 → 0.53.4

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 CHANGED
@@ -22,6 +22,7 @@ Configuration management with cosmiconfig, secret sanitization, and SSG export.
22
22
  | `sanitizeConfig(config)` | Strips keys matching: apiKey, password, secret, token, credential, private, auth, key |
23
23
  | `resolveApplicationRuntime(config)` | Resolve and validate a local, self-hosted, or cloud infrastructure profile |
24
24
  | `resolveConfiguredApplicationRuntime()` | Resolve loaded file config plus highest-priority `setConfig()` runtime overrides |
25
+ | `resolveConfiguredAIProvider()` / `tryResolveAIProviderConfig()` | One AI provider resolver (`src/ai.ts`): explicit > `ai` block > `SMRT_AI_*`/`HAVE_AI_*` env > selected provider's key var > auto-detect; see README |
25
26
  | `getApplicationRuntimePreset(profile)` | Inspect a profile's safe provider defaults |
26
27
 
27
28
  ## Key Files
@@ -34,6 +35,7 @@ Configuration management with cosmiconfig, secret sanitization, and SSG export.
34
35
 
35
36
  ## Gotchas
36
37
 
38
+ - **AI credentials are bound to their source's provider**: a lower-priority key/baseUrl is dropped when another provider wins; redacted base URLs show only the origin. **AI keys come from the environment**: the `ai` block names a variable (`apiKeyEnv`); resolver errors/`toJSON()` never contain keys or URL credentials. Core and the chat dev routes use this resolver; agents share its provider key-name map (`getDefaultAIKeyEnvName`) and keep per-tenant secret lookup; do not add new provider-env parsing elsewhere.
37
39
  - **clearCache() is global**: affects all modules sharing the config instance
38
40
  - **SSG export defaults to no secrets**: must explicitly set `includeSecrets: true` to include them
39
41
  - **Deep merge**: later values override earlier ones at each key level
package/README.md CHANGED
@@ -169,6 +169,56 @@ const safeJson = exportConfig(config, { includeSecrets: false });
169
169
  const sanitized = sanitizeConfig(config);
170
170
  ```
171
171
 
172
+ ## AI provider
173
+
174
+ Declare the assistant model once in `smrt.config.ts`; keep the secret in the environment.
175
+
176
+ ```ts
177
+ export default defineConfig({
178
+ ai: { provider: 'openai', model: 'gpt-4o', apiKeyEnv: 'OPENAI_API_KEY' },
179
+ });
180
+ ```
181
+
182
+ ```ts
183
+ import { resolveConfiguredAIProvider, toAIClientOptions } from '@happyvertical/smrt-config';
184
+ import { getAI } from '@happyvertical/ai';
185
+
186
+ const ai = await getAI(toAIClientOptions(resolveConfiguredAIProvider()));
187
+ ```
188
+
189
+ Default precedence, applied per field (`provider`, `apiKey`, `baseUrl`, `model`), highest first:
190
+
191
+ 1. `explicit` values passed by the caller (for example a class's `options.ai`)
192
+ 2. the `ai` block (`provider`, `model`, `baseUrl`, `apiKeyEnv`; legacy `packages.ai` is read below `ai`)
193
+ 3. environment, one prefix at a time: `<PREFIX>_PROVIDER`, `_API_KEY`, `_BASE_URL`, `_MODEL` for `SMRT_AI`, then `HAVE_AI`
194
+ 4. the selected provider's own key variable (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`); a key for any other provider is never used
195
+ 5. with no provider selected, auto-detect the first provider whose key variable is set (openai, anthropic, gemini)
196
+
197
+ Credential binding: a key or base URL belongs to the provider named by its own source (explicit, the
198
+ `ai` block, or one env prefix). If a higher-priority source selects a different provider, the lower
199
+ source's key and base URL are discarded and the key comes only from sources valid for the selected
200
+ provider (then that provider's own key variable). A source that supplies a key but names no provider is
201
+ generic and binds to whichever provider is selected. Displayed base URLs (`toJSON()`,
202
+ `describeAIProviderConfig()`, `redactBaseUrl()`) expose only the origin; the real URL still goes to the client.
203
+ The rule holds at every layer: the `ai` block layers (runtime `ai`, runtime `packages.ai`, file `ai`, file `packages.ai`)
204
+ are merged with provider ownership (`mergeAIConfigLayers`): when a higher layer names a different provider, the lower
205
+ layers' `apiKey`, `apiKeyEnv`, `baseUrl` and `model` are dropped (a model id is provider-specific). A layer with no provider
206
+ binds to the provider selected so far. Core applies the same rule between its global `ai` config and `options.ai`, and always
207
+ builds its client from the resolver's bound result. Aliases are normalised before coalescing (`provider`/`type`,
208
+ `model`/`defaultModel`), so a blank primary never hides the alias.
209
+ All object merges go through one canonicalising helper, `mergeAIConfigObjects(lower, higher)`
210
+ (folds `provider`/`type` and `model`/`defaultModel`, drops blanks, and drops the lower side's provider, key, key variable, base URL and model
211
+ when the higher side names a different provider): accumulating `setConfig({ ai })` / `setConfig({ packages: { ai } })`, `mergeExportedConfig`
212
+ `ai` blocks, and core's global `ai` + `options.ai`. `withAIAliases()` sets both client-facing aliases on the final result.
213
+ Core consults the block only when it names a `provider`; a partial block behaves like no block.
214
+
215
+ Call sites keep their historical order through options: the chat dev routes pass
216
+ `prefixes: ['SMRT_CHAT_DEV', 'SMRT_AI', 'HAVE_AI']` and `envOverridesConfig: true`; core passes
217
+ `prefixes: ['SMRT_AI']`, `autoDetect: false`. `resolveAIProviderConfig` throws
218
+ `AIProviderNotConfiguredError` (naming variables only) when nothing is configured;
219
+ `tryResolveAIProviderConfig` returns `undefined`. Results redact the key in `toJSON()` /
220
+ `describeAIProviderConfig()`; errors never include key or URL credentials.
221
+
172
222
  ## API
173
223
 
174
224
  ### Functions
@@ -190,6 +240,10 @@ const sanitized = sanitizeConfig(config);
190
240
  | `getApplicationRuntimePreset(profile)` | Inspect an immutable copy of a profile preset |
191
241
  | `mergeExportedConfig(baseConfig, exportedConfig)` | Merge an exported config over a base |
192
242
  | `parseExportedConfig(raw)` | Parse an exported config string |
243
+ | `resolveAIProviderConfig(options?)` / `tryResolveAIProviderConfig(options?)` | Pure resolver (throws / returns `undefined` when unconfigured) |
244
+ | `resolveConfiguredAIProvider(options?)` / `tryResolveConfiguredAIProvider(options?)` | Same, against the loaded `ai` block |
245
+ | `getAIConfigBlock()` | Effective `ai` block (runtime > file; `ai` > `packages.ai`) |
246
+ | `toAIClientOptions(resolved)` | `getAI()` options (`type`, `provider`, `apiKey`, `baseUrl`, `defaultModel`) |
193
247
 
194
248
  ### Priority Order
195
249
 
package/dist/ai.d.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Shared AI provider resolution (#3372).
3
+ *
4
+ * One resolver for every AI consumer. Default precedence, highest first, applied
5
+ * per field (`provider`, `apiKey`, `baseUrl`, `model`):
6
+ *
7
+ * 1. `explicit` — values the caller passes in (for example `options.ai`).
8
+ * 2. The `ai` block of `smrt.config.ts` (`apiKeyEnv` names the key variable).
9
+ * 3. Environment variables, one prefix at a time, in `prefixes` order:
10
+ * `<PREFIX>_PROVIDER`, `<PREFIX>_API_KEY`, `<PREFIX>_BASE_URL`,
11
+ * `<PREFIX>_MODEL`. Default prefixes: `SMRT_AI`, then `HAVE_AI`.
12
+ * 4. Provider-specific key variable for the *selected* provider only
13
+ * (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`).
14
+ * 5. Auto-detect: with no provider selected, the first provider whose key
15
+ * variable is set (openai, anthropic, gemini).
16
+ *
17
+ * `envOverridesConfig` swaps (2) and (3) for call sites that historically let
18
+ * env win.
19
+ *
20
+ * Credential binding: a key or base URL is bound to the provider named by its
21
+ * own source (explicit, the config block, or one env prefix). When a
22
+ * higher-priority source selects a different provider, the lower source's key
23
+ * and base URL are discarded, then the selected provider's own key variable is
24
+ * used. A source that supplies a key but names no provider is generic and
25
+ * binds to whichever provider is selected.
26
+ *
27
+ * Redaction: displayed base URLs expose only the origin. Secrets are never included in errors, `toJSON()` or `describe`.
28
+ */
29
+ import type { AIConfigBlock } from './types.js';
30
+ /** Provider -> conventional API key variable. */
31
+ export declare const AI_PROVIDER_KEY_ENV: Readonly<Record<string, string>>;
32
+ /** Default env prefixes, highest priority first. */
33
+ export declare const DEFAULT_AI_ENV_PREFIXES: readonly string[];
34
+ /** Conventional key variable for a provider name, if it has one. */
35
+ export declare function getDefaultAIKeyEnvName(provider: string | undefined | null): string | undefined;
36
+ export type AIConfigField = 'provider' | 'apiKey' | 'baseUrl' | 'model';
37
+ /** Where a field came from. Names only; never values. */
38
+ export type AIConfigSource = 'explicit' | 'config' | `env:${string}` | 'provider-key-env' | 'auto-detect';
39
+ export interface AIExplicitConfig {
40
+ provider?: string;
41
+ /** Alias of `provider` accepted for `AIClientOptions` compatibility. */
42
+ type?: string;
43
+ apiKey?: string;
44
+ baseUrl?: string;
45
+ model?: string;
46
+ /** Alias of `model` accepted for `AIClientOptions` compatibility. */
47
+ defaultModel?: string;
48
+ }
49
+ export interface ResolveAIProviderOptions {
50
+ /** Caller-supplied values; highest priority. */
51
+ explicit?: AIExplicitConfig | null;
52
+ /**
53
+ * The `smrt.config.ts` `ai` block. Omit to use the loaded config (via
54
+ * `resolveConfiguredAIProvider`); pass `null` to ignore config entirely.
55
+ */
56
+ config?: AIConfigBlock | null;
57
+ /** Environment to read. Defaults to `process.env`. */
58
+ env?: Readonly<Record<string, string | undefined>>;
59
+ /** Env prefixes, highest priority first. Default `['SMRT_AI', 'HAVE_AI']`. */
60
+ prefixes?: readonly string[];
61
+ /**
62
+ * Fall back to the selected provider's own key variable (`OPENAI_API_KEY`...).
63
+ * Default `true`.
64
+ */
65
+ providerKeyEnvFallback?: boolean;
66
+ /** Auto-detect provider from well-known key variables. Default `true`. */
67
+ autoDetect?: boolean;
68
+ /** Let env prefixes beat the config block. Default `false`. */
69
+ envOverridesConfig?: boolean;
70
+ /**
71
+ * Treat "no provider" as unconfigured even when an API key is present.
72
+ * Default `true`; when `false`, a key alone is enough (`provider` stays
73
+ * undefined and the provider layer applies its own default).
74
+ */
75
+ requireProvider?: boolean;
76
+ }
77
+ export interface ResolvedAIProviderConfig {
78
+ provider?: string;
79
+ apiKey?: string;
80
+ baseUrl?: string;
81
+ model?: string;
82
+ /** Which source supplied each field (names only). */
83
+ sources: Partial<Record<AIConfigField, AIConfigSource>>;
84
+ /** Redacted view: safe to log or serialize. */
85
+ toJSON(): Record<string, unknown>;
86
+ }
87
+ /** Options for `getAI` (from `@happyvertical/ai`). */
88
+ export interface AIProviderClientOptions {
89
+ type?: string;
90
+ provider?: string;
91
+ apiKey?: string;
92
+ baseUrl?: string;
93
+ defaultModel?: string;
94
+ }
95
+ /** Thrown when no provider is configured. The message names variables only. */
96
+ export declare class AIProviderNotConfiguredError extends Error {
97
+ readonly code = "SMRT_AI_NOT_CONFIGURED";
98
+ constructor(message: string);
99
+ }
100
+ /**
101
+ * Reduce a base URL to its origin (scheme, host, port) for display. Userinfo,
102
+ * path, query and fragment can all carry credentials, so none are shown. The
103
+ * real URL is still what is passed to the client.
104
+ */
105
+ export declare function redactBaseUrl(value: string | undefined): string | undefined;
106
+ export declare function tryResolveAIProviderConfig(options?: ResolveAIProviderOptions): ResolvedAIProviderConfig | undefined;
107
+ /** Resolve or throw {@link AIProviderNotConfiguredError}. */
108
+ export declare function resolveAIProviderConfig(options?: ResolveAIProviderOptions): ResolvedAIProviderConfig;
109
+ /** Redacted description: presence flags and source names, never secrets. */
110
+ export declare function describeAIProviderConfig(resolved: Pick<ResolvedAIProviderConfig, 'provider' | 'apiKey' | 'baseUrl' | 'model' | 'sources'>): Record<string, unknown>;
111
+ /** Convert a resolved config into `getAI()` options, omitting unset fields. */
112
+ export declare function toAIClientOptions(resolved: Pick<ResolvedAIProviderConfig, 'provider' | 'apiKey' | 'baseUrl' | 'model'>): AIProviderClientOptions;
113
+ /**
114
+ * Canonicalise one AI config object: blank strings are removed, `type` folds
115
+ * into `provider` and `defaultModel` into `model` (`provider`/`model` win when
116
+ * both are non-blank). Other keys pass through untouched.
117
+ */
118
+ export declare function canonicalizeAIConfig(input: unknown): Record<string, unknown>;
119
+ /**
120
+ * The one merge for two AI config objects (`lower` < `higher`). Both sides are
121
+ * canonicalised first. When `higher` names a provider and `lower` names a
122
+ * different one, `lower`'s provider, key, key variable, base URL and model are
123
+ * dropped so no credential is paired with another provider. Everything else is
124
+ * a shallow overlay with `higher` winning. The result carries canonical
125
+ * `provider` / `model` only; use {@link withAIAliases} for client-facing aliases.
126
+ */
127
+ export declare function mergeAIConfigObjects(lower: unknown, higher: unknown): Record<string, unknown>;
128
+ /** Set both client-facing aliases (`provider`+`type`, `model`+`defaultModel`). */
129
+ export declare function withAIAliases<T extends Record<string, unknown>>(config: T): T;
130
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"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"}
package/dist/export.d.ts CHANGED
@@ -1,11 +1,3 @@
1
- /**
2
- * Config export utilities for static site generation
3
- *
4
- * These utilities enable exporting database-backed agent configurations
5
- * to static files for SSG sites.
6
- *
7
- * @module
8
- */
9
1
  /**
10
2
  * Deep-clone a config object, removing every key that looks like a secret.
11
3
  *
@@ -1 +1 @@
1
- {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqKH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAyCvD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAaR;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAY5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,UAAU,EAAE,CAAC,EACb,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,GACzB,CAAC,CA6BH"}
1
+ {"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"AA8KA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAyCvD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAEvB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAaR;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAY5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,UAAU,EAAE,CAAC,EACb,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,GACzB,CAAC,CA2CH"}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,12 @@
1
+ import { type ResolveAIProviderOptions, type ResolvedAIProviderConfig } from './ai.js';
1
2
  import { type ResolvedApplicationRuntime } from './runtime-profile.js';
2
- import type { LoadConfigOptions, SmrtConfig } from './types.js';
3
+ import type { AIConfigBlock, LoadConfigOptions, SmrtConfig } from './types.js';
4
+ export { AI_PROVIDER_KEY_ENV, type AIConfigField, type AIConfigSource, type AIExplicitConfig, type AIProviderClientOptions, AIProviderNotConfiguredError, canonicalizeAIConfig, DEFAULT_AI_ENV_PREFIXES, describeAIProviderConfig, getDefaultAIKeyEnvName, mergeAIConfigObjects, type ResolveAIProviderOptions, type ResolvedAIProviderConfig, redactBaseUrl, resolveAIProviderConfig, toAIClientOptions, tryResolveAIProviderConfig, withAIAliases, } from './ai.js';
3
5
  export { type ExportConfigOptions, exportConfig, mergeExportedConfig, parseExportedConfig, sanitizeConfig, } from './export.js';
4
6
  export { clearRuntimeConfig, getRuntimeConfig, mergeConfigs } from './merge.js';
5
7
  export type { ApplicationRuntimeConfig, ApplicationRuntimeProfile, AssetOwnership, AssetStorageProvider, AuthenticationProvider, BackupProvider, ConnectionOwnership, DatabaseEngine, JobTopology, NetworkExposure, OwnerBootstrapMode, ResolvedApplicationRuntime, RuntimeAssetProvider, RuntimeAuthenticationProvider, RuntimeCapabilities, RuntimeDatabaseProvider, RuntimeInvariants, RuntimeJobProvider, RuntimeNetworkProvider, RuntimeOverrideReport, RuntimePortabilityProvider, RuntimeProfileValidationIssue, RuntimeProviderOverrides, RuntimeProviders, RuntimeSecretProvider, RuntimeTenancyProvider, SecretOwnership, SecretProvider, TenancyMode, TenantContextMode, TenantIsolation, } from './runtime-profile.js';
6
8
  export { getApplicationRuntimePreset, RuntimeProfileValidationError, resolveApplicationRuntime, } from './runtime-profile.js';
7
- export type { CliConfig, DatabaseConfig, ExportConfig, ExportFileConfig, ExportFilterValue, LoadConfigOptions, MigrationsConfig, MigrationsPostgresConfig, PostgresPermissionsConfig, SchemaContractConfig, SiteConfig, SiteLocation, SiteNavigation, SiteNavigationLink, SitePublisher, SiteTheme, SmrtConfig, SmrtGlobalConfig, } from './types.js';
9
+ export type { AIConfigBlock, CliConfig, DatabaseConfig, ExportConfig, ExportFileConfig, ExportFilterValue, LoadConfigOptions, MigrationsConfig, MigrationsPostgresConfig, PostgresPermissionsConfig, SchemaContractConfig, SiteConfig, SiteLocation, SiteNavigation, SiteNavigationLink, SitePublisher, SiteTheme, SmrtConfig, SmrtGlobalConfig, } from './types.js';
8
10
  /**
9
11
  * Extend globalThis to include our config cache properties.
10
12
  * Using globalThis ensures all module instances share the same config,
@@ -251,4 +253,27 @@ export declare function clearCache(): void;
251
253
  * @see {@link loadConfig}
252
254
  */
253
255
  export declare function defineConfig(config: SmrtConfig): SmrtConfig;
256
+ /**
257
+ * The effective `ai` block: runtime `ai` > runtime `packages.ai` > file `ai` >
258
+ * file `packages.ai` (`smrt init` historically wrote `packages.ai`), merged
259
+ * field by field. Returns `null` when none is declared.
260
+ */
261
+ export declare function getAIConfigBlock(): AIConfigBlock | null;
262
+ /**
263
+ * Merge `ai` block layers, lowest priority first, preserving provider
264
+ * ownership: `apiKey`, `apiKeyEnv`, `baseUrl` and `model` belong to the
265
+ * provider their layer names. When a higher layer names a different provider,
266
+ * the credentials and model already collected from lower layers for another
267
+ * provider are dropped (a model id is provider-specific, so it is dropped
268
+ * too). A layer that names no provider contributes generic values that bind to
269
+ * the provider selected so far.
270
+ */
271
+ export declare function mergeAIConfigLayers(layers: ReadonlyArray<unknown>): AIConfigBlock | null;
272
+ /**
273
+ * {@link resolveAIProviderConfig} against the loaded `smrt.config.ts` `ai`
274
+ * block. Throws `AIProviderNotConfiguredError` when nothing is configured.
275
+ */
276
+ export declare function resolveConfiguredAIProvider(options?: ResolveAIProviderOptions): ResolvedAIProviderConfig;
277
+ /** Non-throwing variant of {@link resolveConfiguredAIProvider}. */
278
+ export declare function tryResolveConfiguredAIProvider(options?: ResolveAIProviderOptions): ResolvedAIProviderConfig | undefined;
254
279
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,5 +1,221 @@
1
1
  import { cosmiconfig } from "cosmiconfig";
2
2
  import { createJiti } from "jiti";
3
+ //#region src/ai.ts
4
+ var AI_PROVIDER_KEY_ENV = Object.freeze({
5
+ openai: "OPENAI_API_KEY",
6
+ anthropic: "ANTHROPIC_API_KEY",
7
+ gemini: "GEMINI_API_KEY"
8
+ });
9
+ var AUTO_DETECT_ORDER = [
10
+ "openai",
11
+ "anthropic",
12
+ "gemini"
13
+ ];
14
+ var DEFAULT_AI_ENV_PREFIXES = Object.freeze(["SMRT_AI", "HAVE_AI"]);
15
+ function getDefaultAIKeyEnvName(provider) {
16
+ const normalized = nonEmpty(provider)?.toLowerCase();
17
+ return normalized && Object.hasOwn(AI_PROVIDER_KEY_ENV, normalized) ? AI_PROVIDER_KEY_ENV[normalized] : void 0;
18
+ }
19
+ var AIProviderNotConfiguredError = class extends Error {
20
+ code = "SMRT_AI_NOT_CONFIGURED";
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = "AIProviderNotConfiguredError";
24
+ }
25
+ };
26
+ function nonEmpty(value) {
27
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
28
+ }
29
+ function redactBaseUrl(value) {
30
+ if (!value) return void 0;
31
+ try {
32
+ const url = new URL(value);
33
+ return `${url.protocol}//${url.host}`;
34
+ } catch {
35
+ return "[unparseable-url]";
36
+ }
37
+ }
38
+ function tryResolveAIProviderConfig(options = {}) {
39
+ const env = options.env ?? process.env;
40
+ const read = (name) => nonEmpty(env[name]);
41
+ const prefixes = options.prefixes ?? DEFAULT_AI_ENV_PREFIXES;
42
+ const explicit = options.explicit ?? {};
43
+ const block = options.config ?? {};
44
+ const fromEnv = (suffix) => prefixes.map((prefix) => ({
45
+ source: `env:${prefix}_${suffix}`,
46
+ value: read(`${prefix}_${suffix}`)
47
+ }));
48
+ const explicitCand = (value) => ({
49
+ source: "explicit",
50
+ value: nonEmpty(value)
51
+ });
52
+ const configCand = (value) => ({
53
+ source: "config",
54
+ value: nonEmpty(value)
55
+ });
56
+ const layered = (exp, cfg, envs) => options.envOverridesConfig ? [
57
+ ...exp,
58
+ ...envs,
59
+ ...cfg
60
+ ] : [
61
+ ...exp,
62
+ ...cfg,
63
+ ...envs
64
+ ];
65
+ const first = (candidates) => {
66
+ for (const c of candidates) if (c.value) return {
67
+ value: c.value,
68
+ source: c.source
69
+ };
70
+ };
71
+ const sources = {};
72
+ const result = {};
73
+ const set = (field, hit) => {
74
+ if (hit) {
75
+ result[field] = hit.value;
76
+ sources[field] = hit.source;
77
+ }
78
+ };
79
+ set("provider", first(layered([explicitCand(nonEmpty(explicit.provider) ?? nonEmpty(explicit.type))], [configCand(block.provider)], fromEnv("PROVIDER"))));
80
+ set("model", first(layered([explicitCand(nonEmpty(explicit.model) ?? nonEmpty(explicit.defaultModel))], [configCand(block.model)], fromEnv("MODEL"))));
81
+ const autoDetected = result.provider || options.autoDetect === false ? void 0 : AUTO_DETECT_ORDER.find((p) => read(AI_PROVIDER_KEY_ENV[p]));
82
+ if (autoDetected) {
83
+ result.provider = autoDetected;
84
+ sources.provider = "auto-detect";
85
+ }
86
+ const selected = result.provider?.toLowerCase();
87
+ const configKeyEnv = nonEmpty(block.apiKeyEnv);
88
+ const explicitGroup = {
89
+ source: "explicit",
90
+ provider: nonEmpty(explicit.provider) ?? nonEmpty(explicit.type),
91
+ apiKey: nonEmpty(explicit.apiKey),
92
+ baseUrl: nonEmpty(explicit.baseUrl)
93
+ };
94
+ const configGroup = {
95
+ source: "config",
96
+ provider: nonEmpty(block.provider),
97
+ apiKey: nonEmpty(block.apiKey) ?? (configKeyEnv ? read(configKeyEnv) : void 0),
98
+ baseUrl: nonEmpty(block.baseUrl)
99
+ };
100
+ const envGroups = prefixes.map((prefix) => ({
101
+ source: `env:${prefix}`,
102
+ provider: read(`${prefix}_PROVIDER`),
103
+ apiKey: read(`${prefix}_API_KEY`),
104
+ baseUrl: read(`${prefix}_BASE_URL`)
105
+ }));
106
+ const groups = options.envOverridesConfig ? [
107
+ explicitGroup,
108
+ ...envGroups,
109
+ configGroup
110
+ ] : [
111
+ explicitGroup,
112
+ configGroup,
113
+ ...envGroups
114
+ ];
115
+ const usable = (g) => !g.provider || g.provider.toLowerCase() === selected;
116
+ const sourceName = (g, suffix) => g.source.startsWith("env:") ? `${g.source}_${suffix}` : g.source;
117
+ for (const g of groups) {
118
+ if (!usable(g)) continue;
119
+ if (g.baseUrl && !result.baseUrl) {
120
+ result.baseUrl = g.baseUrl;
121
+ sources.baseUrl = sourceName(g, "BASE_URL");
122
+ }
123
+ if (g.apiKey && !result.apiKey) {
124
+ result.apiKey = g.apiKey;
125
+ sources.apiKey = sourceName(g, "API_KEY");
126
+ }
127
+ }
128
+ const providerKeyEnv = getDefaultAIKeyEnvName(result.provider);
129
+ if (!result.apiKey && providerKeyEnv && options.providerKeyEnvFallback !== false && read(providerKeyEnv)) {
130
+ result.apiKey = read(providerKeyEnv);
131
+ sources.apiKey = "provider-key-env";
132
+ }
133
+ if (!result.provider && (options.requireProvider ?? true)) return;
134
+ if (!result.provider && !result.apiKey) return;
135
+ const resolved = {
136
+ ...result,
137
+ sources,
138
+ toJSON: () => describeAIProviderConfig(resolved)
139
+ };
140
+ Object.defineProperty(resolved, "toJSON", { enumerable: false });
141
+ return resolved;
142
+ }
143
+ function resolveAIProviderConfig(options = {}) {
144
+ const resolved = tryResolveAIProviderConfig(options);
145
+ if (resolved) return resolved;
146
+ const envNames = (options.prefixes ?? DEFAULT_AI_ENV_PREFIXES).map((p) => `${p}_PROVIDER`).join(", ");
147
+ const keyNames = Object.values(AI_PROVIDER_KEY_ENV).join(", ");
148
+ throw new AIProviderNotConfiguredError(`No AI provider is configured. Set the \`ai\` block in smrt.config.ts (provider, model, apiKeyEnv), or set ${envNames}` + (options.autoDetect === false ? "." : `, or one of ${keyNames} to auto-detect the provider.`));
149
+ }
150
+ function describeAIProviderConfig(resolved) {
151
+ return {
152
+ provider: resolved.provider,
153
+ model: resolved.model,
154
+ baseUrl: redactBaseUrl(resolved.baseUrl),
155
+ apiKey: resolved.apiKey ? "[redacted]" : void 0,
156
+ sources: { ...resolved.sources }
157
+ };
158
+ }
159
+ function toAIClientOptions(resolved) {
160
+ const out = {};
161
+ if (resolved.provider) {
162
+ out.type = resolved.provider;
163
+ out.provider = resolved.provider;
164
+ }
165
+ if (resolved.apiKey) out.apiKey = resolved.apiKey;
166
+ if (resolved.baseUrl) out.baseUrl = resolved.baseUrl;
167
+ if (resolved.model) out.defaultModel = resolved.model;
168
+ return out;
169
+ }
170
+ var OWNED_AI_FIELDS = [
171
+ "apiKey",
172
+ "apiKeyEnv",
173
+ "baseUrl",
174
+ "model"
175
+ ];
176
+ function canonicalizeAIConfig(input) {
177
+ if (!input || typeof input !== "object") return {};
178
+ const out = {};
179
+ for (const [key, value] of Object.entries(input)) {
180
+ if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
181
+ if (typeof value === "string" && value.trim().length === 0) continue;
182
+ out[key] = value;
183
+ }
184
+ const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);
185
+ const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);
186
+ delete out.type;
187
+ delete out.defaultModel;
188
+ if (provider) out.provider = provider;
189
+ else delete out.provider;
190
+ if (model) out.model = model;
191
+ else delete out.model;
192
+ return out;
193
+ }
194
+ function mergeAIConfigObjects(lower, higher) {
195
+ const low = canonicalizeAIConfig(lower);
196
+ const high = canonicalizeAIConfig(higher);
197
+ const lowName = nonEmpty(low.provider)?.toLowerCase();
198
+ const highName = nonEmpty(high.provider)?.toLowerCase();
199
+ if (lowName && highName && lowName !== highName) {
200
+ delete low.provider;
201
+ for (const f of OWNED_AI_FIELDS) delete low[f];
202
+ }
203
+ return {
204
+ ...low,
205
+ ...high
206
+ };
207
+ }
208
+ function withAIAliases(config) {
209
+ const out = { ...config };
210
+ const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);
211
+ const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);
212
+ for (const k of ["provider", "type"]) if (provider) out[k] = provider;
213
+ else delete out[k];
214
+ for (const k of ["model", "defaultModel"]) if (model) out[k] = model;
215
+ else delete out[k];
216
+ return out;
217
+ }
218
+ //#endregion
3
219
  //#region src/loader.ts
4
220
  var MODULE_NAME = "smrt";
5
221
  var jitiInstance;
@@ -581,7 +797,12 @@ function setConfig$1(config) {
581
797
  const resetRuntime = current.runtime;
582
798
  delete resetRuntime.providers;
583
799
  }
584
- globalThis.__smrtRuntimeConfig = deepMerge(current, config);
800
+ const merged = deepMerge(current, config);
801
+ const aiOf = (c) => isPlainObject(c) && isPlainObject(c.ai) ? c.ai : void 0;
802
+ const pkgAiOf = (c) => isPlainObject(c) && isPlainObject(c.packages) && isPlainObject(c.packages.ai) ? c.packages.ai : void 0;
803
+ if (aiOf(config)) merged.ai = mergeAIConfigObjects(aiOf(current) ?? {}, aiOf(config));
804
+ if (pkgAiOf(config)) merged.packages.ai = mergeAIConfigObjects(pkgAiOf(current) ?? {}, pkgAiOf(config));
805
+ globalThis.__smrtRuntimeConfig = merged;
585
806
  }
586
807
  function getRuntimeConfig() {
587
808
  return globalThis.__smrtRuntimeConfig || {};
@@ -714,7 +935,8 @@ function mergeExportedConfig(baseConfig, exportedConfig) {
714
935
  const result = { ...baseConfig };
715
936
  for (const [key, value] of Object.entries(exportedConfig)) {
716
937
  if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
717
- if (value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeExportedConfig(result[key], value);
938
+ if (key === "ai" && value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeAIConfigObjects(result[key], value);
939
+ else if (value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) result[key] = mergeExportedConfig(result[key], value);
718
940
  else result[key] = value;
719
941
  }
720
942
  return result;
@@ -783,7 +1005,65 @@ function clearCache() {
783
1005
  function defineConfig(config) {
784
1006
  return config;
785
1007
  }
1008
+ function getAIConfigBlock() {
1009
+ const file = getLoadedConfig();
1010
+ const runtime = getRuntimeConfig();
1011
+ return mergeAIConfigLayers([
1012
+ file?.packages?.ai,
1013
+ file?.ai,
1014
+ runtime.packages?.ai,
1015
+ runtime.ai
1016
+ ]);
1017
+ }
1018
+ function mergeAIConfigLayers(layers) {
1019
+ const FIELDS = [
1020
+ "model",
1021
+ "baseUrl",
1022
+ "apiKeyEnv",
1023
+ "apiKey"
1024
+ ];
1025
+ let provider;
1026
+ const values = {};
1027
+ const owners = {};
1028
+ const text = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
1029
+ for (const layer of layers) {
1030
+ if (!layer || typeof layer !== "object") continue;
1031
+ const rec = layer;
1032
+ const named = text(rec.provider) ?? text(rec.type);
1033
+ if (named && provider && named.toLowerCase() !== provider.toLowerCase()) for (const f of FIELDS) {
1034
+ const owner = owners[f];
1035
+ if (owner && owner.toLowerCase() !== named.toLowerCase()) {
1036
+ delete values[f];
1037
+ delete owners[f];
1038
+ }
1039
+ }
1040
+ if (named) provider = named;
1041
+ for (const f of FIELDS) {
1042
+ const v = f === "model" ? text(rec.model) ?? text(rec.defaultModel) : text(rec[f]);
1043
+ if (v) {
1044
+ values[f] = v;
1045
+ owners[f] = provider;
1046
+ }
1047
+ }
1048
+ }
1049
+ if (!provider && FIELDS.every((f) => !values[f])) return null;
1050
+ const merged = { ...values };
1051
+ if (provider) merged.provider = provider;
1052
+ return merged;
1053
+ }
1054
+ function resolveConfiguredAIProvider(options = {}) {
1055
+ return resolveAIProviderConfig({
1056
+ ...options,
1057
+ config: options.config === void 0 ? getAIConfigBlock() : options.config
1058
+ });
1059
+ }
1060
+ function tryResolveConfiguredAIProvider(options = {}) {
1061
+ return tryResolveAIProviderConfig({
1062
+ ...options,
1063
+ config: options.config === void 0 ? getAIConfigBlock() : options.config
1064
+ });
1065
+ }
786
1066
  //#endregion
787
- export { RuntimeProfileValidationError, clearCache, clearRuntimeConfig, defineConfig, exportConfig, getApplicationRuntimePreset, getConfig, getModuleConfig, getPackageConfig, getRuntimeConfig, getSiteConfig, loadConfig, mergeConfigs, mergeExportedConfig, parseExportedConfig, resolveApplicationRuntime, resolveConfiguredApplicationRuntime, sanitizeConfig, setConfig };
1067
+ export { AIProviderNotConfiguredError, AI_PROVIDER_KEY_ENV, DEFAULT_AI_ENV_PREFIXES, RuntimeProfileValidationError, canonicalizeAIConfig, clearCache, clearRuntimeConfig, defineConfig, describeAIProviderConfig, exportConfig, getAIConfigBlock, getApplicationRuntimePreset, getConfig, getDefaultAIKeyEnvName, getModuleConfig, getPackageConfig, getRuntimeConfig, getSiteConfig, loadConfig, mergeAIConfigLayers, mergeAIConfigObjects, mergeConfigs, mergeExportedConfig, parseExportedConfig, redactBaseUrl, resolveAIProviderConfig, resolveApplicationRuntime, resolveConfiguredAIProvider, resolveConfiguredApplicationRuntime, sanitizeConfig, setConfig, toAIClientOptions, tryResolveAIProviderConfig, tryResolveConfiguredAIProvider, withAIAliases };
788
1068
 
789
1069
  //# sourceMappingURL=index.js.map