@actuate-media/cms-core 0.42.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/__tests__/ai/ai.test.d.ts +2 -0
  2. package/dist/__tests__/ai/ai.test.d.ts.map +1 -0
  3. package/dist/__tests__/ai/ai.test.js +261 -0
  4. package/dist/__tests__/ai/ai.test.js.map +1 -0
  5. package/dist/__tests__/ai/resolve.test.d.ts +2 -0
  6. package/dist/__tests__/ai/resolve.test.d.ts.map +1 -0
  7. package/dist/__tests__/ai/resolve.test.js +164 -0
  8. package/dist/__tests__/ai/resolve.test.js.map +1 -0
  9. package/dist/ai/catalog.d.ts +29 -0
  10. package/dist/ai/catalog.d.ts.map +1 -0
  11. package/dist/ai/catalog.js +340 -0
  12. package/dist/ai/catalog.js.map +1 -0
  13. package/dist/ai/config-store.d.ts +55 -0
  14. package/dist/ai/config-store.d.ts.map +1 -0
  15. package/dist/ai/config-store.js +177 -0
  16. package/dist/ai/config-store.js.map +1 -0
  17. package/dist/ai/feature-defaults.d.ts +20 -0
  18. package/dist/ai/feature-defaults.d.ts.map +1 -0
  19. package/dist/ai/feature-defaults.js +104 -0
  20. package/dist/ai/feature-defaults.js.map +1 -0
  21. package/dist/ai/index.d.ts +14 -0
  22. package/dist/ai/index.d.ts.map +1 -0
  23. package/dist/ai/index.js +14 -0
  24. package/dist/ai/index.js.map +1 -0
  25. package/dist/ai/models.d.ts +37 -0
  26. package/dist/ai/models.d.ts.map +1 -0
  27. package/dist/ai/models.js +126 -0
  28. package/dist/ai/models.js.map +1 -0
  29. package/dist/ai/providers/anthropic.d.ts +9 -0
  30. package/dist/ai/providers/anthropic.d.ts.map +1 -0
  31. package/dist/ai/providers/anthropic.js +36 -0
  32. package/dist/ai/providers/anthropic.js.map +1 -0
  33. package/dist/ai/providers/custom.d.ts +11 -0
  34. package/dist/ai/providers/custom.d.ts.map +1 -0
  35. package/dist/ai/providers/custom.js +70 -0
  36. package/dist/ai/providers/custom.js.map +1 -0
  37. package/dist/ai/providers/http.d.ts +32 -0
  38. package/dist/ai/providers/http.d.ts.map +1 -0
  39. package/dist/ai/providers/http.js +90 -0
  40. package/dist/ai/providers/http.js.map +1 -0
  41. package/dist/ai/providers/index.d.ts +16 -0
  42. package/dist/ai/providers/index.d.ts.map +1 -0
  43. package/dist/ai/providers/index.js +25 -0
  44. package/dist/ai/providers/index.js.map +1 -0
  45. package/dist/ai/providers/openai.d.ts +10 -0
  46. package/dist/ai/providers/openai.d.ts.map +1 -0
  47. package/dist/ai/providers/openai.js +41 -0
  48. package/dist/ai/providers/openai.js.map +1 -0
  49. package/dist/ai/providers/xai.d.ts +9 -0
  50. package/dist/ai/providers/xai.d.ts.map +1 -0
  51. package/dist/ai/providers/xai.js +35 -0
  52. package/dist/ai/providers/xai.js.map +1 -0
  53. package/dist/ai/resolve.d.ts +41 -0
  54. package/dist/ai/resolve.d.ts.map +1 -0
  55. package/dist/ai/resolve.js +142 -0
  56. package/dist/ai/resolve.js.map +1 -0
  57. package/dist/ai/types.d.ts +161 -0
  58. package/dist/ai/types.d.ts.map +1 -0
  59. package/dist/ai/types.js +11 -0
  60. package/dist/ai/types.js.map +1 -0
  61. package/dist/ai/usage.d.ts +18 -0
  62. package/dist/ai/usage.d.ts.map +1 -0
  63. package/dist/ai/usage.js +70 -0
  64. package/dist/ai/usage.js.map +1 -0
  65. package/dist/api/handlers.d.ts.map +1 -1
  66. package/dist/api/handlers.js +523 -0
  67. package/dist/api/handlers.js.map +1 -1
  68. package/dist/index.d.ts +3 -0
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +4 -0
  71. package/dist/index.js.map +1 -1
  72. package/package.json +1 -1
@@ -0,0 +1,340 @@
1
+ /**
2
+ * Curated model metadata + normalization.
3
+ *
4
+ * Live provider listing endpoints return model IDs and little else (rarely
5
+ * capabilities or pricing). We merge those IDs with a curated capability/pricing
6
+ * map so the catalog always knows what a model can do, what it costs, and
7
+ * whether it is current. When no key/live listing is available, the curated
8
+ * entries also serve as the fallback catalog.
9
+ *
10
+ * Matching is prefix-based (longest match wins) so dated provider IDs like
11
+ * `gpt-4o-2024-08-06` resolve to the `gpt-4o` entry, while `gpt-4o-mini-*`
12
+ * resolves to its own entry.
13
+ */
14
+ const NO_CAPABILITIES = {
15
+ text: false,
16
+ vision: false,
17
+ imageGeneration: false,
18
+ embeddings: false,
19
+ toolCalling: false,
20
+ structuredOutput: false,
21
+ streaming: false,
22
+ reasoning: false,
23
+ longContext: false,
24
+ };
25
+ const CURATED = {
26
+ openai: [
27
+ {
28
+ match: ['gpt-4o-mini'],
29
+ canonicalId: 'gpt-4o-mini',
30
+ displayName: 'GPT-4o mini (fast)',
31
+ description: 'Low-cost multimodal model for high-volume tasks.',
32
+ capabilities: {
33
+ text: true,
34
+ vision: true,
35
+ toolCalling: true,
36
+ structuredOutput: true,
37
+ streaming: true,
38
+ longContext: true,
39
+ },
40
+ contextWindow: 128000,
41
+ maxOutputTokens: 16384,
42
+ inputModalities: ['text', 'image'],
43
+ outputModalities: ['text'],
44
+ pricing: { inputPerMillion: 0.15, outputPerMillion: 0.6 },
45
+ },
46
+ {
47
+ match: ['gpt-4o', 'chatgpt-4o'],
48
+ canonicalId: 'gpt-4o',
49
+ displayName: 'GPT-4o (balanced)',
50
+ description: 'Flagship multimodal model balancing quality, speed, and cost.',
51
+ capabilities: {
52
+ text: true,
53
+ vision: true,
54
+ toolCalling: true,
55
+ structuredOutput: true,
56
+ streaming: true,
57
+ longContext: true,
58
+ },
59
+ contextWindow: 128000,
60
+ maxOutputTokens: 16384,
61
+ inputModalities: ['text', 'image'],
62
+ outputModalities: ['text'],
63
+ pricing: { inputPerMillion: 2.5, outputPerMillion: 10 },
64
+ },
65
+ {
66
+ match: ['o3-mini', 'o1-mini'],
67
+ canonicalId: 'o3-mini',
68
+ displayName: 'o3-mini (reasoning)',
69
+ description: 'Fast reasoning model for structured problem solving.',
70
+ capabilities: {
71
+ text: true,
72
+ toolCalling: true,
73
+ structuredOutput: true,
74
+ reasoning: true,
75
+ longContext: true,
76
+ },
77
+ contextWindow: 200000,
78
+ maxOutputTokens: 100000,
79
+ inputModalities: ['text'],
80
+ outputModalities: ['text'],
81
+ pricing: { inputPerMillion: 1.1, outputPerMillion: 4.4 },
82
+ },
83
+ {
84
+ match: ['o3', 'o1'],
85
+ canonicalId: 'o3',
86
+ displayName: 'o3 (reasoning)',
87
+ description: 'High-capability reasoning model.',
88
+ capabilities: {
89
+ text: true,
90
+ vision: true,
91
+ toolCalling: true,
92
+ structuredOutput: true,
93
+ reasoning: true,
94
+ longContext: true,
95
+ },
96
+ contextWindow: 200000,
97
+ maxOutputTokens: 100000,
98
+ inputModalities: ['text', 'image'],
99
+ outputModalities: ['text'],
100
+ pricing: { inputPerMillion: 10, outputPerMillion: 40 },
101
+ },
102
+ {
103
+ match: ['text-embedding-3-large'],
104
+ canonicalId: 'text-embedding-3-large',
105
+ displayName: 'Embedding 3 (large)',
106
+ capabilities: { embeddings: true },
107
+ inputModalities: ['text'],
108
+ outputModalities: ['embedding'],
109
+ pricing: { inputPerMillion: 0.13 },
110
+ },
111
+ {
112
+ match: ['text-embedding-3-small', 'text-embedding'],
113
+ canonicalId: 'text-embedding-3-small',
114
+ displayName: 'Embedding 3 (small)',
115
+ capabilities: { embeddings: true },
116
+ inputModalities: ['text'],
117
+ outputModalities: ['embedding'],
118
+ pricing: { inputPerMillion: 0.02 },
119
+ },
120
+ {
121
+ match: ['gpt-image-1', 'dall-e-3', 'dall-e'],
122
+ canonicalId: 'gpt-image-1',
123
+ displayName: 'GPT Image',
124
+ capabilities: { imageGeneration: true },
125
+ inputModalities: ['text'],
126
+ outputModalities: ['image'],
127
+ pricing: { image: 0.04 },
128
+ },
129
+ ],
130
+ anthropic: [
131
+ {
132
+ match: ['claude-3-5-haiku', 'claude-haiku', 'claude-3-haiku'],
133
+ canonicalId: 'claude-3-5-haiku-latest',
134
+ displayName: 'Claude Haiku (fast)',
135
+ description: 'Fast, low-cost model for high-throughput tasks.',
136
+ capabilities: {
137
+ text: true,
138
+ vision: true,
139
+ toolCalling: true,
140
+ structuredOutput: true,
141
+ streaming: true,
142
+ longContext: true,
143
+ },
144
+ contextWindow: 200000,
145
+ maxOutputTokens: 8192,
146
+ inputModalities: ['text', 'image'],
147
+ outputModalities: ['text'],
148
+ pricing: { inputPerMillion: 0.8, outputPerMillion: 4 },
149
+ },
150
+ {
151
+ match: ['claude-opus', 'claude-3-opus'],
152
+ canonicalId: 'claude-opus-4-latest',
153
+ displayName: 'Claude Opus (most capable)',
154
+ description: 'Highest-capability Claude model for complex reasoning.',
155
+ capabilities: {
156
+ text: true,
157
+ vision: true,
158
+ toolCalling: true,
159
+ structuredOutput: true,
160
+ streaming: true,
161
+ reasoning: true,
162
+ longContext: true,
163
+ },
164
+ contextWindow: 200000,
165
+ maxOutputTokens: 32000,
166
+ inputModalities: ['text', 'image'],
167
+ outputModalities: ['text'],
168
+ pricing: { inputPerMillion: 15, outputPerMillion: 75 },
169
+ },
170
+ {
171
+ match: ['claude-sonnet', 'claude-3-5-sonnet', 'claude-3-sonnet', 'claude-'],
172
+ canonicalId: 'claude-sonnet-4-latest',
173
+ displayName: 'Claude Sonnet (balanced)',
174
+ description: 'Balanced Claude model for everyday content and reasoning.',
175
+ capabilities: {
176
+ text: true,
177
+ vision: true,
178
+ toolCalling: true,
179
+ structuredOutput: true,
180
+ streaming: true,
181
+ longContext: true,
182
+ },
183
+ contextWindow: 200000,
184
+ maxOutputTokens: 16384,
185
+ inputModalities: ['text', 'image'],
186
+ outputModalities: ['text'],
187
+ pricing: { inputPerMillion: 3, outputPerMillion: 15 },
188
+ },
189
+ ],
190
+ xai: [
191
+ {
192
+ match: ['grok-4-fast', 'grok-3-mini', 'grok-mini'],
193
+ canonicalId: 'grok-4-fast',
194
+ displayName: 'Grok (fast)',
195
+ description: 'Low-latency Grok model for high-volume tasks.',
196
+ capabilities: {
197
+ text: true,
198
+ toolCalling: true,
199
+ structuredOutput: true,
200
+ streaming: true,
201
+ longContext: true,
202
+ },
203
+ contextWindow: 131072,
204
+ maxOutputTokens: 16384,
205
+ inputModalities: ['text'],
206
+ outputModalities: ['text'],
207
+ pricing: { inputPerMillion: 0.2, outputPerMillion: 0.5 },
208
+ },
209
+ {
210
+ match: ['grok-vision', 'grok-2-vision'],
211
+ canonicalId: 'grok-vision',
212
+ displayName: 'Grok Vision',
213
+ description: 'Multimodal Grok model with image understanding.',
214
+ capabilities: {
215
+ text: true,
216
+ vision: true,
217
+ toolCalling: true,
218
+ structuredOutput: true,
219
+ streaming: true,
220
+ longContext: true,
221
+ },
222
+ contextWindow: 131072,
223
+ maxOutputTokens: 16384,
224
+ inputModalities: ['text', 'image'],
225
+ outputModalities: ['text'],
226
+ pricing: { inputPerMillion: 2, outputPerMillion: 10 },
227
+ },
228
+ {
229
+ match: ['grok'],
230
+ canonicalId: 'grok-4',
231
+ displayName: 'Grok (balanced)',
232
+ description: 'Flagship Grok model.',
233
+ capabilities: {
234
+ text: true,
235
+ toolCalling: true,
236
+ structuredOutput: true,
237
+ streaming: true,
238
+ reasoning: true,
239
+ longContext: true,
240
+ },
241
+ contextWindow: 131072,
242
+ maxOutputTokens: 16384,
243
+ inputModalities: ['text'],
244
+ outputModalities: ['text'],
245
+ pricing: { inputPerMillion: 3, outputPerMillion: 15 },
246
+ },
247
+ ],
248
+ };
249
+ /** Find the curated entry whose longest `match` prefix applies to the id. */
250
+ function findCurated(provider, providerModelId) {
251
+ if (provider === 'custom')
252
+ return null;
253
+ const list = CURATED[provider];
254
+ if (!list)
255
+ return null;
256
+ const id = providerModelId.toLowerCase();
257
+ let best = null;
258
+ for (const meta of list) {
259
+ for (const m of meta.match) {
260
+ if (id.startsWith(m) && (!best || m.length > best.len)) {
261
+ best = { meta, len: m.length };
262
+ }
263
+ }
264
+ }
265
+ return best?.meta ?? null;
266
+ }
267
+ /**
268
+ * Heuristic capability inference for models with no curated entry (e.g. an
269
+ * unknown custom-provider model). Conservative: assumes text unless the id
270
+ * clearly indicates embeddings/image-only.
271
+ */
272
+ function inferCapabilities(providerModelId) {
273
+ const id = providerModelId.toLowerCase();
274
+ const caps = { ...NO_CAPABILITIES };
275
+ if (id.includes('embed')) {
276
+ caps.embeddings = true;
277
+ return caps;
278
+ }
279
+ if (id.includes('image') || id.includes('dall-e') || id.includes('imagen')) {
280
+ caps.imageGeneration = true;
281
+ return caps;
282
+ }
283
+ caps.text = true;
284
+ caps.streaming = true;
285
+ if (id.includes('vision') ||
286
+ id.includes('-o') ||
287
+ id.includes('4o') ||
288
+ id.includes('multimodal')) {
289
+ caps.vision = true;
290
+ }
291
+ if (id.includes('reason') || id.includes('think') || /\bo[13]\b/.test(id)) {
292
+ caps.reasoning = true;
293
+ }
294
+ return caps;
295
+ }
296
+ /** Merge a (live or fallback) model id with curated metadata into an AIModel. */
297
+ export function normalizeModel(input) {
298
+ const curated = findCurated(input.provider, input.providerModelId);
299
+ const capabilities = curated
300
+ ? { ...NO_CAPABILITIES, ...curated.capabilities }
301
+ : inferCapabilities(input.providerModelId);
302
+ return {
303
+ id: `${input.provider}:${input.providerModelId}`,
304
+ provider: input.provider,
305
+ providerModelId: input.providerModelId,
306
+ displayName: curated?.displayName ?? input.displayName ?? input.providerModelId,
307
+ description: curated?.description,
308
+ status: input.status ?? (curated?.deprecated ? 'deprecated' : 'available'),
309
+ capabilities,
310
+ contextWindow: curated?.contextWindow,
311
+ maxOutputTokens: curated?.maxOutputTokens,
312
+ inputModalities: curated?.inputModalities ?? (capabilities.embeddings ? ['text'] : ['text']),
313
+ outputModalities: curated?.outputModalities ??
314
+ (capabilities.embeddings
315
+ ? ['embedding']
316
+ : capabilities.imageGeneration
317
+ ? ['image']
318
+ : ['text']),
319
+ pricing: curated?.pricing,
320
+ lastSyncedAt: input.lastSyncedAt,
321
+ source: input.source,
322
+ metadata: curated ? { curated: true } : {},
323
+ };
324
+ }
325
+ /** Curated fallback catalog for a provider (used when no live listing). */
326
+ export function curatedFallbackCatalog(provider, lastSyncedAt) {
327
+ if (provider === 'custom')
328
+ return [];
329
+ const list = CURATED[provider] ?? [];
330
+ return list
331
+ .filter((m) => m.fallback !== false)
332
+ .map((m) => normalizeModel({
333
+ provider,
334
+ providerModelId: m.canonicalId,
335
+ source: 'config',
336
+ status: m.deprecated ? 'deprecated' : 'available',
337
+ lastSyncedAt,
338
+ }));
339
+ }
340
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/ai/catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,eAAe,GAAwB;IAC3C,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,KAAK;IACb,eAAe,EAAE,KAAK;IACtB,UAAU,EAAE,KAAK;IACjB,WAAW,EAAE,KAAK;IAClB,gBAAgB,EAAE,KAAK;IACvB,SAAS,EAAE,KAAK;IAChB,SAAS,EAAE,KAAK;IAChB,WAAW,EAAE,KAAK;CACnB,CAAA;AAqBD,MAAM,OAAO,GAA4D;IACvE,MAAM,EAAE;QACN;YACE,KAAK,EAAE,CAAC,aAAa,CAAC;YACtB,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,oBAAoB;YACjC,WAAW,EAAE,kDAAkD;YAC/D,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE;SAC1D;QACD;YACE,KAAK,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC;YAC/B,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,mBAAmB;YAChC,WAAW,EAAE,+DAA+D;YAC5E,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACxD;QACD;YACE,KAAK,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;YAC7B,WAAW,EAAE,SAAS;YACtB,WAAW,EAAE,qBAAqB;YAClC,WAAW,EAAE,sDAAsD;YACnE,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,MAAM;YACvB,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE;SACzD;QACD;YACE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC;YACnB,WAAW,EAAE,IAAI;YACjB,WAAW,EAAE,gBAAgB;YAC7B,WAAW,EAAE,kCAAkC;YAC/C,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,MAAM;YACvB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACvD;QACD;YACE,KAAK,EAAE,CAAC,wBAAwB,CAAC;YACjC,WAAW,EAAE,wBAAwB;YACrC,WAAW,EAAE,qBAAqB;YAClC,YAAY,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YAClC,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,WAAW,CAAC;YAC/B,OAAO,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;SACnC;QACD;YACE,KAAK,EAAE,CAAC,wBAAwB,EAAE,gBAAgB,CAAC;YACnD,WAAW,EAAE,wBAAwB;YACrC,WAAW,EAAE,qBAAqB;YAClC,YAAY,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE;YAClC,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,WAAW,CAAC;YAC/B,OAAO,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;SACnC;QACD;YACE,KAAK,EAAE,CAAC,aAAa,EAAE,UAAU,EAAE,QAAQ,CAAC;YAC5C,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,WAAW;YACxB,YAAY,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE;YACvC,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,OAAO,CAAC;YAC3B,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;SACzB;KACF;IACD,SAAS,EAAE;QACT;YACE,KAAK,EAAE,CAAC,kBAAkB,EAAE,cAAc,EAAE,gBAAgB,CAAC;YAC7D,WAAW,EAAE,yBAAyB;YACtC,WAAW,EAAE,qBAAqB;YAClC,WAAW,EAAE,iDAAiD;YAC9D,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,IAAI;YACrB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,EAAE,CAAC,EAAE;SACvD;QACD;YACE,KAAK,EAAE,CAAC,aAAa,EAAE,eAAe,CAAC;YACvC,WAAW,EAAE,sBAAsB;YACnC,WAAW,EAAE,4BAA4B;YACzC,WAAW,EAAE,wDAAwD;YACrE,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACvD;QACD;YACE,KAAK,EAAE,CAAC,eAAe,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,SAAS,CAAC;YAC3E,WAAW,EAAE,wBAAwB;YACrC,WAAW,EAAE,0BAA0B;YACvC,WAAW,EAAE,2DAA2D;YACxE,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACtD;KACF;IACD,GAAG,EAAE;QACH;YACE,KAAK,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,WAAW,CAAC;YAClD,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,+CAA+C;YAC5D,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,gBAAgB,EAAE,GAAG,EAAE;SACzD;QACD;YACE,KAAK,EAAE,CAAC,aAAa,EAAE,eAAe,CAAC;YACvC,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,aAAa;YAC1B,WAAW,EAAE,iDAAiD;YAC9D,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;gBACZ,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAClC,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACtD;QACD;YACE,KAAK,EAAE,CAAC,MAAM,CAAC;YACf,WAAW,EAAE,QAAQ;YACrB,WAAW,EAAE,iBAAiB;YAC9B,WAAW,EAAE,sBAAsB;YACnC,YAAY,EAAE;gBACZ,IAAI,EAAE,IAAI;gBACV,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,IAAI;gBACtB,SAAS,EAAE,IAAI;gBACf,SAAS,EAAE,IAAI;gBACf,WAAW,EAAE,IAAI;aAClB;YACD,aAAa,EAAE,MAAM;YACrB,eAAe,EAAE,KAAK;YACtB,eAAe,EAAE,CAAC,MAAM,CAAC;YACzB,gBAAgB,EAAE,CAAC,MAAM,CAAC;YAC1B,OAAO,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,gBAAgB,EAAE,EAAE,EAAE;SACtD;KACF;CACF,CAAA;AAED,6EAA6E;AAC7E,SAAS,WAAW,CAAC,QAAuB,EAAE,eAAuB;IACnE,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,MAAM,EAAE,GAAG,eAAe,CAAC,WAAW,EAAE,CAAA;IACxC,IAAI,IAAI,GAA8C,IAAI,CAAA;IAC1D,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACvD,IAAI,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAA;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,IAAI,EAAE,IAAI,IAAI,IAAI,CAAA;AAC3B,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,eAAuB;IAChD,MAAM,EAAE,GAAG,eAAe,CAAC,WAAW,EAAE,CAAA;IACxC,MAAM,IAAI,GAAG,EAAE,GAAG,eAAe,EAAE,CAAA;IACnC,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3E,IAAI,CAAC,eAAe,GAAG,IAAI,CAAA;QAC3B,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;IAChB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACrB,IACE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACrB,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;QACjB,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;QACjB,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,EACzB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;IACpB,CAAC;IACD,IAAI,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC1E,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACvB,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAaD,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,KAAqB;IAClD,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;IAClE,MAAM,YAAY,GAAG,OAAO;QAC1B,CAAC,CAAC,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE;QACjD,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,eAAe,CAAC,CAAA;IAC5C,OAAO;QACL,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,eAAe,EAAE;QAChD,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,eAAe;QAC/E,WAAW,EAAE,OAAO,EAAE,WAAW;QACjC,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;QAC1E,YAAY;QACZ,aAAa,EAAE,OAAO,EAAE,aAAa;QACrC,eAAe,EAAE,OAAO,EAAE,eAAe;QACzC,eAAe,EAAE,OAAO,EAAE,eAAe,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC5F,gBAAgB,EACd,OAAO,EAAE,gBAAgB;YACzB,CAAC,YAAY,CAAC,UAAU;gBACtB,CAAC,CAAC,CAAC,WAAW,CAAC;gBACf,CAAC,CAAC,YAAY,CAAC,eAAe;oBAC5B,CAAC,CAAC,CAAC,OAAO,CAAC;oBACX,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACjB,OAAO,EAAE,OAAO,EAAE,OAAO;QACzB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;KAC3C,CAAA;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,sBAAsB,CAAC,QAAuB,EAAE,YAAoB;IAClF,IAAI,QAAQ,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAA;IACpC,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;IACpC,OAAO,IAAI;SACR,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC;SACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACT,cAAc,CAAC;QACb,QAAQ;QACR,eAAe,EAAE,CAAC,CAAC,WAAW;QAC9B,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW;QACjD,YAAY;KACb,CAAC,CACH,CAAA;AACL,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * DB-backed AI configuration store.
3
+ *
4
+ * Storage: a single Document row with `collection = '__ai_config__'` holding
5
+ * `{ providers, settings, catalog }`, mirroring the SEO config-store pattern
6
+ * (`packages/cms-core/src/seo/config-store.ts`). This keeps Phases 1-2 free of a
7
+ * Prisma migration. The per-run usage table arrives in Phase 4.
8
+ *
9
+ * Security invariants:
10
+ * - Provider API keys are encrypted at rest via `encryptSecret`.
11
+ * - The encrypted key is NEVER returned to the client; handlers serialize
12
+ * providers through {@link toPublicProvider}.
13
+ * - When a provider's env var is set, that key wins and the UI shows it as
14
+ * env-managed/read-only.
15
+ */
16
+ import type { AIBudgetSettings, AIConfigDocument, AIKeySource, AIProviderConnection, AIProviderConnectionPublic, AIProviderKey, AISettings } from './types.js';
17
+ export declare const AI_CONFIG_COLLECTION = "__ai_config__";
18
+ export declare const DEFAULT_AI_BUDGET: AIBudgetSettings;
19
+ export declare function defaultAISettings(): AISettings;
20
+ /** Read the env-managed key for a provider, if present and non-empty. */
21
+ export declare function getEnvApiKey(provider: AIProviderKey): string | null;
22
+ /** Last 4 chars of a key for masked display (never the full value). */
23
+ export declare function last4(key: string | undefined | null): string | undefined;
24
+ /**
25
+ * Resolve the effective API key for a connection at call time: an env var (if
26
+ * configured for this provider) always wins, otherwise the decrypted stored key.
27
+ * Returns null when neither exists.
28
+ */
29
+ export declare function resolveProviderKey(conn: Pick<AIProviderConnection, 'provider' | 'apiKeyEncrypted'>): Promise<string | null>;
30
+ /** Determine where a connection's key comes from for UI display. */
31
+ export declare function resolveKeySource(conn: AIProviderConnection): AIKeySource;
32
+ /** Strip key material and add UI-facing source metadata. */
33
+ export declare function toPublicProvider(conn: AIProviderConnection): AIProviderConnectionPublic;
34
+ /** Encrypt a plaintext key for storage and compute its masked suffix. */
35
+ export declare function encryptProviderKey(plaintext: string): Promise<{
36
+ apiKeyEncrypted: string;
37
+ apiKeyLast4: string;
38
+ }>;
39
+ /**
40
+ * Read the AI config document. Returns a fully-populated object (defaults +
41
+ * merged feature flags) even when no row exists yet, so callers never special-
42
+ * case the empty state.
43
+ */
44
+ export declare function getAIConfig(db: unknown): Promise<AIConfigDocument>;
45
+ /**
46
+ * Upsert the AI config document. Caller is responsible for auth/role checks.
47
+ * Stamps `updatedAt`/`updatedBy`.
48
+ */
49
+ export declare function putAIConfig(db: unknown, next: AIConfigDocument, userId: string): Promise<AIConfigDocument>;
50
+ /** Serialize a full config doc for the client (providers masked). */
51
+ export declare function toPublicConfig(doc: AIConfigDocument): {
52
+ providers: AIProviderConnectionPublic[];
53
+ settings: AISettings;
54
+ };
55
+ //# sourceMappingURL=config-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.d.ts","sourceRoot":"","sources":["../../src/ai/config-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,oBAAoB,EACpB,0BAA0B,EAC1B,aAAa,EACb,UAAU,EACX,MAAM,YAAY,CAAA;AAEnB,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAUnD,eAAO,MAAM,iBAAiB,EAAE,gBAG/B,CAAA;AAED,wBAAgB,iBAAiB,IAAI,UAAU,CAM9C;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAKnE;AAED,uEAAuE;AACvE,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAIxE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,iBAAiB,CAAC,GAC/D,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAWxB;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,WAAW,CAIxE;AAED,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,GAAG,0BAA0B,CAcvF;AAED,yEAAyE;AACzE,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CAG3D;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4BxE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CA2B3B;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,GAAG,EAAE,gBAAgB,GAAG;IACrD,SAAS,EAAE,0BAA0B,EAAE,CAAA;IACvC,QAAQ,EAAE,UAAU,CAAA;CACrB,CAKA"}
@@ -0,0 +1,177 @@
1
+ /**
2
+ * DB-backed AI configuration store.
3
+ *
4
+ * Storage: a single Document row with `collection = '__ai_config__'` holding
5
+ * `{ providers, settings, catalog }`, mirroring the SEO config-store pattern
6
+ * (`packages/cms-core/src/seo/config-store.ts`). This keeps Phases 1-2 free of a
7
+ * Prisma migration. The per-run usage table arrives in Phase 4.
8
+ *
9
+ * Security invariants:
10
+ * - Provider API keys are encrypted at rest via `encryptSecret`.
11
+ * - The encrypted key is NEVER returned to the client; handlers serialize
12
+ * providers through {@link toPublicProvider}.
13
+ * - When a provider's env var is set, that key wins and the UI shows it as
14
+ * env-managed/read-only.
15
+ */
16
+ import { decryptSecret, encryptSecret } from '../security/secret-storage.js';
17
+ import { cloneFeatureDefaults, mergeFeatures } from './feature-defaults.js';
18
+ export const AI_CONFIG_COLLECTION = '__ai_config__';
19
+ /** Env var that supplies each built-in provider's key (custom has none). */
20
+ const ENV_KEY_BY_PROVIDER = {
21
+ openai: 'OPENAI_API_KEY',
22
+ anthropic: 'ANTHROPIC_API_KEY',
23
+ xai: 'XAI_API_KEY',
24
+ custom: null,
25
+ };
26
+ export const DEFAULT_AI_BUDGET = {
27
+ alertAtPercentages: [50, 80, 100],
28
+ disableWhenExceeded: false,
29
+ };
30
+ export function defaultAISettings() {
31
+ return {
32
+ features: cloneFeatureDefaults(),
33
+ budget: { ...DEFAULT_AI_BUDGET, alertAtPercentages: [...DEFAULT_AI_BUDGET.alertAtPercentages] },
34
+ logsRetentionDays: 30,
35
+ };
36
+ }
37
+ /** Read the env-managed key for a provider, if present and non-empty. */
38
+ export function getEnvApiKey(provider) {
39
+ const envVar = ENV_KEY_BY_PROVIDER[provider];
40
+ if (!envVar)
41
+ return null;
42
+ const val = process.env[envVar];
43
+ return val && val.trim() ? val.trim() : null;
44
+ }
45
+ /** Last 4 chars of a key for masked display (never the full value). */
46
+ export function last4(key) {
47
+ if (!key)
48
+ return undefined;
49
+ const trimmed = key.trim();
50
+ return trimmed.length >= 4 ? trimmed.slice(-4) : trimmed;
51
+ }
52
+ /**
53
+ * Resolve the effective API key for a connection at call time: an env var (if
54
+ * configured for this provider) always wins, otherwise the decrypted stored key.
55
+ * Returns null when neither exists.
56
+ */
57
+ export async function resolveProviderKey(conn) {
58
+ const envKey = getEnvApiKey(conn.provider);
59
+ if (envKey)
60
+ return envKey;
61
+ if (conn.apiKeyEncrypted) {
62
+ try {
63
+ return await decryptSecret(conn.apiKeyEncrypted);
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ return null;
70
+ }
71
+ /** Determine where a connection's key comes from for UI display. */
72
+ export function resolveKeySource(conn) {
73
+ if (getEnvApiKey(conn.provider))
74
+ return 'environment';
75
+ if (conn.apiKeyEncrypted)
76
+ return 'database';
77
+ return 'database';
78
+ }
79
+ /** Strip key material and add UI-facing source metadata. */
80
+ export function toPublicProvider(conn) {
81
+ const envKey = getEnvApiKey(conn.provider);
82
+ const source = resolveKeySource(conn);
83
+ const keyManagedExternally = source === 'environment';
84
+ // Reflect the env key's suffix when env-managed, else the stored suffix.
85
+ const maskLast4 = envKey ? last4(envKey) : conn.apiKeyLast4;
86
+ const { apiKeyEncrypted: _omit, ...rest } = conn;
87
+ return {
88
+ ...rest,
89
+ apiKeyLast4: maskLast4,
90
+ apiKeySource: source,
91
+ keyManagedExternally,
92
+ hasKey: Boolean(envKey || conn.apiKeyEncrypted),
93
+ };
94
+ }
95
+ /** Encrypt a plaintext key for storage and compute its masked suffix. */
96
+ export async function encryptProviderKey(plaintext) {
97
+ const apiKeyEncrypted = await encryptSecret(plaintext);
98
+ return { apiKeyEncrypted, apiKeyLast4: last4(plaintext) ?? '' };
99
+ }
100
+ /**
101
+ * Read the AI config document. Returns a fully-populated object (defaults +
102
+ * merged feature flags) even when no row exists yet, so callers never special-
103
+ * case the empty state.
104
+ */
105
+ export async function getAIConfig(db) {
106
+ const d = db;
107
+ const empty = { providers: [], settings: defaultAISettings() };
108
+ if (!d?.document?.findFirst)
109
+ return empty;
110
+ try {
111
+ const doc = await d.document.findFirst({
112
+ where: { collection: AI_CONFIG_COLLECTION, deletedAt: null },
113
+ select: { data: true },
114
+ });
115
+ if (!doc?.data || typeof doc.data !== 'object')
116
+ return empty;
117
+ const data = doc.data;
118
+ const settings = (data.settings ?? {});
119
+ return {
120
+ providers: Array.isArray(data.providers) ? data.providers : [],
121
+ settings: {
122
+ ...defaultAISettings(),
123
+ ...settings,
124
+ features: mergeFeatures(settings.features),
125
+ budget: { ...DEFAULT_AI_BUDGET, ...(settings.budget ?? {}) },
126
+ logsRetentionDays: settings.logsRetentionDays ?? 30,
127
+ },
128
+ catalog: data.catalog,
129
+ updatedAt: data.updatedAt,
130
+ updatedBy: data.updatedBy,
131
+ };
132
+ }
133
+ catch {
134
+ return empty;
135
+ }
136
+ }
137
+ /**
138
+ * Upsert the AI config document. Caller is responsible for auth/role checks.
139
+ * Stamps `updatedAt`/`updatedBy`.
140
+ */
141
+ export async function putAIConfig(db, next, userId) {
142
+ const d = db;
143
+ const data = {
144
+ ...next,
145
+ updatedAt: new Date().toISOString(),
146
+ updatedBy: userId,
147
+ };
148
+ const existing = await d.document.findFirst({
149
+ where: { collection: AI_CONFIG_COLLECTION, deletedAt: null },
150
+ select: { id: true },
151
+ });
152
+ if (existing) {
153
+ await d.document.update({ where: { id: existing.id }, data: { data, updatedById: userId } });
154
+ }
155
+ else {
156
+ await d.document.create({
157
+ data: {
158
+ collection: AI_CONFIG_COLLECTION,
159
+ data,
160
+ status: 'PUBLISHED',
161
+ title: 'AI settings',
162
+ slug: 'ai-config',
163
+ createdById: userId,
164
+ updatedById: userId,
165
+ },
166
+ });
167
+ }
168
+ return data;
169
+ }
170
+ /** Serialize a full config doc for the client (providers masked). */
171
+ export function toPublicConfig(doc) {
172
+ return {
173
+ providers: doc.providers.map(toPublicProvider),
174
+ settings: doc.settings,
175
+ };
176
+ }
177
+ //# sourceMappingURL=config-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.js","sourceRoot":"","sources":["../../src/ai/config-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Default AI feature catalog. These are the global feature flags surfaced in
3
+ * Settings > AI and (from Phase 3) enforced across CMS modules via
4
+ * `isAiFeatureEnabled(key)`. Seeded into the `__ai_config__` document on first
5
+ * write; admins toggle `enabled` per feature.
6
+ *
7
+ * Keys follow the `ai.features.*` convention so a single shared service governs
8
+ * every module instead of per-module flags.
9
+ */
10
+ import type { AIFeatureConfig } from './types.js';
11
+ export declare const AI_FEATURE_DEFAULTS: AIFeatureConfig[];
12
+ /** Deep clone so callers can't mutate the shared default array. */
13
+ export declare function cloneFeatureDefaults(): AIFeatureConfig[];
14
+ /**
15
+ * Merge stored feature toggles over the current defaults so newly-added features
16
+ * appear automatically and removed/renamed ones drop off, while preserving the
17
+ * admin's `enabled`/routing choices for features that still exist.
18
+ */
19
+ export declare function mergeFeatures(stored: AIFeatureConfig[] | undefined): AIFeatureConfig[];
20
+ //# sourceMappingURL=feature-defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-defaults.d.ts","sourceRoot":"","sources":["../../src/ai/feature-defaults.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,eAAO,MAAM,mBAAmB,EAAE,eAAe,EA6DhD,CAAA;AAED,mEAAmE;AACnE,wBAAgB,oBAAoB,IAAI,eAAe,EAAE,CAQxD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,SAAS,GAAG,eAAe,EAAE,CAetF"}