@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 +2 -0
- package/README.md +54 -0
- package/dist/ai.d.ts +130 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/export.d.ts +0 -8
- package/dist/export.d.ts.map +1 -1
- package/dist/index.d.ts +27 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +283 -3
- package/dist/index.js.map +1 -1
- package/dist/merge.d.ts.map +1 -1
- package/dist/types.d.ts +44 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
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
|
package/dist/ai.d.ts.map
ADDED
|
@@ -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
|
*
|
package/dist/export.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"export.d.ts","sourceRoot":"","sources":["../src/export.ts"],"names":[],"mappings":"
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
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] =
|
|
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
|