@actuate-media/cms-core 0.41.0 → 0.43.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 (101) 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__/seo/settings-defaults.test.d.ts +2 -0
  6. package/dist/__tests__/seo/settings-defaults.test.d.ts.map +1 -0
  7. package/dist/__tests__/seo/settings-defaults.test.js +189 -0
  8. package/dist/__tests__/seo/settings-defaults.test.js.map +1 -0
  9. package/dist/__tests__/seo/sitemap-notify.test.d.ts +2 -0
  10. package/dist/__tests__/seo/sitemap-notify.test.d.ts.map +1 -0
  11. package/dist/__tests__/seo/sitemap-notify.test.js +191 -0
  12. package/dist/__tests__/seo/sitemap-notify.test.js.map +1 -0
  13. package/dist/actions.d.ts.map +1 -1
  14. package/dist/actions.js +80 -3
  15. package/dist/actions.js.map +1 -1
  16. package/dist/ai/catalog.d.ts +29 -0
  17. package/dist/ai/catalog.d.ts.map +1 -0
  18. package/dist/ai/catalog.js +340 -0
  19. package/dist/ai/catalog.js.map +1 -0
  20. package/dist/ai/config-store.d.ts +55 -0
  21. package/dist/ai/config-store.d.ts.map +1 -0
  22. package/dist/ai/config-store.js +177 -0
  23. package/dist/ai/config-store.js.map +1 -0
  24. package/dist/ai/feature-defaults.d.ts +20 -0
  25. package/dist/ai/feature-defaults.d.ts.map +1 -0
  26. package/dist/ai/feature-defaults.js +104 -0
  27. package/dist/ai/feature-defaults.js.map +1 -0
  28. package/dist/ai/index.d.ts +13 -0
  29. package/dist/ai/index.d.ts.map +1 -0
  30. package/dist/ai/index.js +13 -0
  31. package/dist/ai/index.js.map +1 -0
  32. package/dist/ai/models.d.ts +37 -0
  33. package/dist/ai/models.d.ts.map +1 -0
  34. package/dist/ai/models.js +126 -0
  35. package/dist/ai/models.js.map +1 -0
  36. package/dist/ai/providers/anthropic.d.ts +9 -0
  37. package/dist/ai/providers/anthropic.d.ts.map +1 -0
  38. package/dist/ai/providers/anthropic.js +36 -0
  39. package/dist/ai/providers/anthropic.js.map +1 -0
  40. package/dist/ai/providers/custom.d.ts +11 -0
  41. package/dist/ai/providers/custom.d.ts.map +1 -0
  42. package/dist/ai/providers/custom.js +70 -0
  43. package/dist/ai/providers/custom.js.map +1 -0
  44. package/dist/ai/providers/http.d.ts +32 -0
  45. package/dist/ai/providers/http.d.ts.map +1 -0
  46. package/dist/ai/providers/http.js +90 -0
  47. package/dist/ai/providers/http.js.map +1 -0
  48. package/dist/ai/providers/index.d.ts +16 -0
  49. package/dist/ai/providers/index.d.ts.map +1 -0
  50. package/dist/ai/providers/index.js +25 -0
  51. package/dist/ai/providers/index.js.map +1 -0
  52. package/dist/ai/providers/openai.d.ts +10 -0
  53. package/dist/ai/providers/openai.d.ts.map +1 -0
  54. package/dist/ai/providers/openai.js +41 -0
  55. package/dist/ai/providers/openai.js.map +1 -0
  56. package/dist/ai/providers/xai.d.ts +9 -0
  57. package/dist/ai/providers/xai.d.ts.map +1 -0
  58. package/dist/ai/providers/xai.js +35 -0
  59. package/dist/ai/providers/xai.js.map +1 -0
  60. package/dist/ai/types.d.ts +161 -0
  61. package/dist/ai/types.d.ts.map +1 -0
  62. package/dist/ai/types.js +11 -0
  63. package/dist/ai/types.js.map +1 -0
  64. package/dist/ai/usage.d.ts +18 -0
  65. package/dist/ai/usage.d.ts.map +1 -0
  66. package/dist/ai/usage.js +70 -0
  67. package/dist/ai/usage.js.map +1 -0
  68. package/dist/api/handlers.d.ts.map +1 -1
  69. package/dist/api/handlers.js +539 -10
  70. package/dist/api/handlers.js.map +1 -1
  71. package/dist/config/types.d.ts +49 -0
  72. package/dist/config/types.d.ts.map +1 -1
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +4 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/page-builder/schema.d.ts +2 -2
  78. package/dist/seo/config-store.d.ts +2 -0
  79. package/dist/seo/config-store.d.ts.map +1 -1
  80. package/dist/seo/config-store.js +2 -0
  81. package/dist/seo/config-store.js.map +1 -1
  82. package/dist/seo/index.d.ts +6 -4
  83. package/dist/seo/index.d.ts.map +1 -1
  84. package/dist/seo/index.js +3 -2
  85. package/dist/seo/index.js.map +1 -1
  86. package/dist/seo/meta-tags.d.ts +36 -0
  87. package/dist/seo/meta-tags.d.ts.map +1 -1
  88. package/dist/seo/meta-tags.js +78 -0
  89. package/dist/seo/meta-tags.js.map +1 -1
  90. package/dist/seo/page-meta.d.ts.map +1 -1
  91. package/dist/seo/page-meta.js +47 -8
  92. package/dist/seo/page-meta.js.map +1 -1
  93. package/dist/seo/sitemap-notify.d.ts +85 -0
  94. package/dist/seo/sitemap-notify.d.ts.map +1 -0
  95. package/dist/seo/sitemap-notify.js +159 -0
  96. package/dist/seo/sitemap-notify.js.map +1 -0
  97. package/dist/seo/title-templates.d.ts +26 -0
  98. package/dist/seo/title-templates.d.ts.map +1 -1
  99. package/dist/seo/title-templates.js +83 -0
  100. package/dist/seo/title-templates.js.map +1 -1
  101. package/package.json +1 -1
@@ -0,0 +1,104 @@
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
+ export const AI_FEATURE_DEFAULTS = [
11
+ {
12
+ key: 'ai.features.seo404Recovery',
13
+ label: '404 Recovery suggestions',
14
+ description: 'Recommend redirect targets for broken URLs that still receive traffic.',
15
+ enabled: true,
16
+ category: 'seo',
17
+ requiredCapabilities: ['text', 'reasoning'],
18
+ approvalRequired: true,
19
+ allowBulkApply: false,
20
+ rolesAllowed: ['ADMIN', 'EDITOR'],
21
+ permissionsRequired: ['ai.use'],
22
+ },
23
+ {
24
+ key: 'ai.features.contentWriting',
25
+ label: 'Content writing suggestions',
26
+ description: 'Draft, rewrite, summarize, and improve content in the editor.',
27
+ enabled: true,
28
+ category: 'content',
29
+ requiredCapabilities: ['text'],
30
+ approvalRequired: true,
31
+ allowBulkApply: false,
32
+ rolesAllowed: ['ADMIN', 'EDITOR'],
33
+ permissionsRequired: ['ai.use'],
34
+ },
35
+ {
36
+ key: 'ai.features.mediaAltText',
37
+ label: 'Auto alt-text for media',
38
+ description: 'Generate descriptive alt text for images on upload or in the SEO drawer.',
39
+ enabled: true,
40
+ category: 'media',
41
+ requiredCapabilities: ['vision'],
42
+ approvalRequired: true,
43
+ allowBulkApply: false,
44
+ rolesAllowed: ['ADMIN', 'EDITOR'],
45
+ permissionsRequired: ['ai.use'],
46
+ },
47
+ {
48
+ key: 'ai.features.seoMetaGeneration',
49
+ label: 'SEO meta generation',
50
+ description: 'Generate SEO titles, meta descriptions, and focus keywords from content.',
51
+ enabled: true,
52
+ category: 'seo',
53
+ requiredCapabilities: ['text'],
54
+ approvalRequired: true,
55
+ allowBulkApply: false,
56
+ rolesAllowed: ['ADMIN', 'EDITOR'],
57
+ permissionsRequired: ['ai.use'],
58
+ },
59
+ {
60
+ key: 'ai.features.redirectChainFlattening',
61
+ label: 'Redirect chain flattening',
62
+ description: 'Suggest direct single-hop redirects to collapse redirect chains.',
63
+ enabled: false,
64
+ category: 'seo',
65
+ requiredCapabilities: ['text', 'reasoning'],
66
+ approvalRequired: true,
67
+ allowBulkApply: false,
68
+ rolesAllowed: ['ADMIN'],
69
+ permissionsRequired: ['ai.use'],
70
+ },
71
+ ];
72
+ /** Deep clone so callers can't mutate the shared default array. */
73
+ export function cloneFeatureDefaults() {
74
+ return AI_FEATURE_DEFAULTS.map((f) => ({
75
+ ...f,
76
+ requiredCapabilities: [...f.requiredCapabilities],
77
+ rolesAllowed: [...f.rolesAllowed],
78
+ permissionsRequired: [...f.permissionsRequired],
79
+ fallbackModelIds: f.fallbackModelIds ? [...f.fallbackModelIds] : undefined,
80
+ }));
81
+ }
82
+ /**
83
+ * Merge stored feature toggles over the current defaults so newly-added features
84
+ * appear automatically and removed/renamed ones drop off, while preserving the
85
+ * admin's `enabled`/routing choices for features that still exist.
86
+ */
87
+ export function mergeFeatures(stored) {
88
+ const byKey = new Map((stored ?? []).map((f) => [f.key, f]));
89
+ return cloneFeatureDefaults().map((def) => {
90
+ const prev = byKey.get(def.key);
91
+ if (!prev)
92
+ return def;
93
+ return {
94
+ ...def,
95
+ enabled: typeof prev.enabled === 'boolean' ? prev.enabled : def.enabled,
96
+ providerId: prev.providerId ?? def.providerId,
97
+ modelId: prev.modelId ?? def.modelId,
98
+ fallbackModelIds: prev.fallbackModelIds ?? def.fallbackModelIds,
99
+ maxMonthlyTokens: prev.maxMonthlyTokens ?? def.maxMonthlyTokens,
100
+ maxRequestsPerDay: prev.maxRequestsPerDay ?? def.maxRequestsPerDay,
101
+ };
102
+ });
103
+ }
104
+ //# sourceMappingURL=feature-defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-defaults.js","sourceRoot":"","sources":["../../src/ai/feature-defaults.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Barrel for the Settings > AI governance layer (Phases 1-2): provider
3
+ * connections + dynamic model catalog. Feature enforcement and per-run metering
4
+ * arrive in later phases.
5
+ */
6
+ export * from './types.js';
7
+ export { AI_CONFIG_COLLECTION, DEFAULT_AI_BUDGET, defaultAISettings, encryptProviderKey, getAIConfig, getEnvApiKey, last4, putAIConfig, resolveKeySource, resolveProviderKey, toPublicConfig, toPublicProvider, } from './config-store.js';
8
+ export { AI_FEATURE_DEFAULTS, cloneFeatureDefaults, mergeFeatures } from './feature-defaults.js';
9
+ export { curatedFallbackCatalog, normalizeModel } from './catalog.js';
10
+ export { getModels, resolveModelOrUnavailable, syncModels, type SyncModelsResult, } from './models.js';
11
+ export { getUsageSummary } from './usage.js';
12
+ export { getAdapter, listModels as adapterListModels, testConnection as adapterTestConnection, type AdapterContext, } from './providers/index.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAChG,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,UAAU,EACV,KAAK,gBAAgB,GACtB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,UAAU,EACV,UAAU,IAAI,iBAAiB,EAC/B,cAAc,IAAI,qBAAqB,EACvC,KAAK,cAAc,GACpB,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Barrel for the Settings > AI governance layer (Phases 1-2): provider
3
+ * connections + dynamic model catalog. Feature enforcement and per-run metering
4
+ * arrive in later phases.
5
+ */
6
+ export * from './types.js';
7
+ export { AI_CONFIG_COLLECTION, DEFAULT_AI_BUDGET, defaultAISettings, encryptProviderKey, getAIConfig, getEnvApiKey, last4, putAIConfig, resolveKeySource, resolveProviderKey, toPublicConfig, toPublicProvider, } from './config-store.js';
8
+ export { AI_FEATURE_DEFAULTS, cloneFeatureDefaults, mergeFeatures } from './feature-defaults.js';
9
+ export { curatedFallbackCatalog, normalizeModel } from './catalog.js';
10
+ export { getModels, resolveModelOrUnavailable, syncModels, } from './models.js';
11
+ export { getUsageSummary } from './usage.js';
12
+ export { getAdapter, listModels as adapterListModels, testConnection as adapterTestConnection, } from './providers/index.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ai/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,YAAY,CAAA;AAC1B,OAAO,EACL,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,WAAW,EACX,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAChG,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,UAAU,GAEX,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,EACL,UAAU,EACV,UAAU,IAAI,iBAAiB,EAC/B,cAAc,IAAI,qBAAqB,GAExC,MAAM,sBAAsB,CAAA"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Model catalog orchestration: live sync (with curated normalization), curated
3
+ * fallback, and resolution of a configured-but-missing model to an `unavailable`
4
+ * entry so the UI can warn without silently dropping the admin's choice.
5
+ */
6
+ import { AI_CONFIG_COLLECTION } from './config-store.js';
7
+ import type { AIModel } from './types.js';
8
+ export { AI_CONFIG_COLLECTION };
9
+ export interface SyncModelsResult {
10
+ models: AIModel[];
11
+ fromFallback: boolean;
12
+ /** Present when the live listing failed and we fell back to curated data. */
13
+ warning?: string;
14
+ }
15
+ /**
16
+ * Sync the model catalog for a provider connection. Attempts a live listing;
17
+ * on any failure falls back to the curated catalog so the selector is never
18
+ * empty. Persists the result on the `__ai_config__` document under
19
+ * `catalog[connectionId]` and stamps `lastModelSyncAt`.
20
+ */
21
+ export declare function syncModels(db: unknown, connectionId: string): Promise<SyncModelsResult>;
22
+ /**
23
+ * Read the cached catalog for a connection (or all connections). Falls back to
24
+ * the curated catalog for connections that have never been synced so the UI
25
+ * always has something to show.
26
+ */
27
+ export declare function getModels(db: unknown, params?: {
28
+ connectionId?: string;
29
+ provider?: string;
30
+ }): Promise<AIModel[]>;
31
+ /**
32
+ * Resolve a configured model id to a catalog entry. When the id is no longer in
33
+ * the catalog (provider removed/renamed it) returns a synthesized `unavailable`
34
+ * model so the caller can keep the setting and warn the user.
35
+ */
36
+ export declare function resolveModelOrUnavailable(modelId: string | undefined, catalog: AIModel[]): AIModel | null;
37
+ //# sourceMappingURL=models.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.d.ts","sourceRoot":"","sources":["../../src/ai/models.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EAIrB,MAAM,mBAAmB,CAAA;AAI1B,OAAO,KAAK,EAAkB,OAAO,EAAwB,MAAM,YAAY,CAAA;AAE/E,OAAO,EAAE,oBAAoB,EAAE,CAAA;AAW/B,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,EAAE,CAAA;IACjB,YAAY,EAAE,OAAO,CAAA;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA2D7F;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,OAAO,EACX,MAAM,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD,OAAO,CAAC,OAAO,EAAE,CAAC,CAiBpB;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,OAAO,EAAE,GACjB,OAAO,GAAG,IAAI,CAgBhB"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Model catalog orchestration: live sync (with curated normalization), curated
3
+ * fallback, and resolution of a configured-but-missing model to an `unavailable`
4
+ * entry so the UI can warn without silently dropping the admin's choice.
5
+ */
6
+ import { AI_CONFIG_COLLECTION, getAIConfig, putAIConfig, resolveProviderKey, } from './config-store.js';
7
+ import { curatedFallbackCatalog, normalizeModel } from './catalog.js';
8
+ import { listModels as adapterListModels } from './providers/index.js';
9
+ export { AI_CONFIG_COLLECTION };
10
+ function adapterContextFor(conn, apiKey) {
11
+ return {
12
+ apiKey,
13
+ baseUrl: conn.baseUrl,
14
+ organizationId: conn.organizationId,
15
+ projectId: conn.projectId,
16
+ };
17
+ }
18
+ /**
19
+ * Sync the model catalog for a provider connection. Attempts a live listing;
20
+ * on any failure falls back to the curated catalog so the selector is never
21
+ * empty. Persists the result on the `__ai_config__` document under
22
+ * `catalog[connectionId]` and stamps `lastModelSyncAt`.
23
+ */
24
+ export async function syncModels(db, connectionId) {
25
+ const config = await getAIConfig(db);
26
+ const conn = config.providers.find((p) => p.id === connectionId);
27
+ if (!conn)
28
+ throw new Error('Provider connection not found');
29
+ const now = new Date().toISOString();
30
+ const apiKey = await resolveProviderKey(conn);
31
+ let models;
32
+ let fromFallback = false;
33
+ let warning;
34
+ if (!apiKey) {
35
+ models = curatedFallbackCatalog(conn.provider, now);
36
+ fromFallback = true;
37
+ warning = 'No API key configured; showing the curated model list.';
38
+ }
39
+ else {
40
+ try {
41
+ const ids = await adapterListModels(conn.provider, adapterContextFor(conn, apiKey));
42
+ if (ids.length === 0) {
43
+ models = curatedFallbackCatalog(conn.provider, now);
44
+ fromFallback = true;
45
+ warning = 'The provider returned no models; showing the curated list.';
46
+ }
47
+ else {
48
+ models = ids.map((providerModelId) => normalizeModel({
49
+ provider: conn.provider,
50
+ providerModelId,
51
+ source: 'provider',
52
+ status: 'available',
53
+ lastSyncedAt: now,
54
+ }));
55
+ }
56
+ }
57
+ catch (err) {
58
+ models = curatedFallbackCatalog(conn.provider, now);
59
+ fromFallback = true;
60
+ warning =
61
+ err instanceof Error
62
+ ? `Live model listing failed (${err.message}); showing the curated list.`
63
+ : 'Live model listing failed; showing the curated list.';
64
+ }
65
+ }
66
+ const entry = { models, lastSyncedAt: now, fromFallback };
67
+ const nextProviders = config.providers.map((p) => p.id === connectionId ? { ...p, lastModelSyncAt: now } : p);
68
+ await putAIConfig(db, {
69
+ ...config,
70
+ providers: nextProviders,
71
+ catalog: { ...(config.catalog ?? {}), [connectionId]: entry },
72
+ }, conn.updatedBy || 'system');
73
+ return { models, fromFallback, warning };
74
+ }
75
+ /**
76
+ * Read the cached catalog for a connection (or all connections). Falls back to
77
+ * the curated catalog for connections that have never been synced so the UI
78
+ * always has something to show.
79
+ */
80
+ export async function getModels(db, params) {
81
+ const config = await getAIConfig(db);
82
+ const now = new Date().toISOString();
83
+ const out = [];
84
+ const conns = config.providers.filter((p) => {
85
+ if (params?.connectionId)
86
+ return p.id === params.connectionId;
87
+ if (params?.provider)
88
+ return p.provider === params.provider;
89
+ return true;
90
+ });
91
+ for (const conn of conns) {
92
+ const cached = config.catalog?.[conn.id];
93
+ if (cached?.models?.length)
94
+ out.push(...cached.models);
95
+ else
96
+ out.push(...curatedFallbackCatalog(conn.provider, now));
97
+ }
98
+ // De-dupe by normalized id, preferring the first (cached/live) occurrence.
99
+ const seen = new Set();
100
+ return out.filter((m) => (seen.has(m.id) ? false : (seen.add(m.id), true)));
101
+ }
102
+ /**
103
+ * Resolve a configured model id to a catalog entry. When the id is no longer in
104
+ * the catalog (provider removed/renamed it) returns a synthesized `unavailable`
105
+ * model so the caller can keep the setting and warn the user.
106
+ */
107
+ export function resolveModelOrUnavailable(modelId, catalog) {
108
+ if (!modelId)
109
+ return null;
110
+ const found = catalog.find((m) => m.id === modelId || m.providerModelId === modelId);
111
+ if (found)
112
+ return found;
113
+ const parts = modelId.split(':');
114
+ const provider = parts[0] ?? '';
115
+ const providerModelId = parts.slice(1).join(':') || modelId;
116
+ return normalizeModel({
117
+ provider: (['openai', 'anthropic', 'xai', 'custom'].includes(provider)
118
+ ? provider
119
+ : 'custom'),
120
+ providerModelId,
121
+ source: 'config',
122
+ status: 'unavailable',
123
+ lastSyncedAt: new Date().toISOString(),
124
+ });
125
+ }
126
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/ai/models.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Anthropic adapter. Uses the `/v1/models` listing endpoint with `x-api-key`
3
+ * and the required `anthropic-version` header. Hostname is fixed.
4
+ */
5
+ import type { AIConnectionTestResult } from '../types.js';
6
+ import { type AdapterContext } from './http.js';
7
+ export declare function testConnection(ctx: AdapterContext): Promise<AIConnectionTestResult>;
8
+ export declare function listModels(ctx: AdapterContext): Promise<string[]>;
9
+ //# sourceMappingURL=anthropic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.d.ts","sourceRoot":"","sources":["../../../src/ai/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EACL,KAAK,cAAc,EAKpB,MAAM,WAAW,CAAA;AASlB,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAqBzF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQvE"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Anthropic adapter. Uses the `/v1/models` listing endpoint with `x-api-key`
3
+ * and the required `anthropic-version` header. Hostname is fixed.
4
+ */
5
+ import { extractModelIds, messageFromStatus, statusFromHttp, timedFetch, } from './http.js';
6
+ const ANTHROPIC_BASE = 'https://api.anthropic.com/v1';
7
+ const ANTHROPIC_VERSION = '2023-06-01';
8
+ function headers(ctx) {
9
+ return { 'x-api-key': ctx.apiKey, 'anthropic-version': ANTHROPIC_VERSION };
10
+ }
11
+ export async function testConnection(ctx) {
12
+ const testedAt = new Date().toISOString();
13
+ if (!ctx.apiKey) {
14
+ return { ok: false, status: 'needs_setup', message: messageFromStatus('needs_setup'), testedAt };
15
+ }
16
+ try {
17
+ const res = await timedFetch(`${ANTHROPIC_BASE}/models`, { method: 'GET', headers: headers(ctx) }, ctx);
18
+ const status = statusFromHttp(res.status);
19
+ return { ok: status === 'connected', status, message: messageFromStatus(status), testedAt };
20
+ }
21
+ catch {
22
+ return {
23
+ ok: false,
24
+ status: 'provider_down',
25
+ message: messageFromStatus('provider_down'),
26
+ testedAt,
27
+ };
28
+ }
29
+ }
30
+ export async function listModels(ctx) {
31
+ const res = await timedFetch(`${ANTHROPIC_BASE}/models`, { method: 'GET', headers: headers(ctx) }, ctx);
32
+ if (!res.ok)
33
+ throw new Error(`Anthropic model listing failed (${res.status})`);
34
+ return extractModelIds(await res.json());
35
+ }
36
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src/ai/providers/anthropic.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAEL,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,UAAU,GACX,MAAM,WAAW,CAAA;AAElB,MAAM,cAAc,GAAG,8BAA8B,CAAA;AACrD,MAAM,iBAAiB,GAAG,YAAY,CAAA;AAEtC,SAAS,OAAO,CAAC,GAAmB;IAClC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,CAAA;AAC5E,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAmB;IACtD,MAAM,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACzC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,iBAAiB,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAA;IAClG,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,UAAU,CAC1B,GAAG,cAAc,SAAS,EAC1B,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EACxC,GAAG,CACJ,CAAA;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACzC,OAAO,EAAE,EAAE,EAAE,MAAM,KAAK,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAA;IAC7F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,eAAe;YACvB,OAAO,EAAE,iBAAiB,CAAC,eAAe,CAAC;YAC3C,QAAQ;SACT,CAAA;IACH,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAmB;IAClD,MAAM,GAAG,GAAG,MAAM,UAAU,CAC1B,GAAG,cAAc,SAAS,EAC1B,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EACxC,GAAG,CACJ,CAAA;IACD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA;IAC9E,OAAO,eAAe,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;AAC1C,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Custom OpenAI-compatible provider adapter. The base URL is admin-controlled,
3
+ * so it is SSRF-validated and DNS-resolved before any request. Falls back to an
4
+ * empty model list (manual entry in the UI) when the endpoint doesn't support
5
+ * `/models`.
6
+ */
7
+ import type { AIConnectionTestResult } from '../types.js';
8
+ import { type AdapterContext } from './http.js';
9
+ export declare function testConnection(ctx: AdapterContext): Promise<AIConnectionTestResult>;
10
+ export declare function listModels(ctx: AdapterContext): Promise<string[]>;
11
+ //# sourceMappingURL=custom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../../src/ai/providers/custom.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EACL,KAAK,cAAc,EAOpB,MAAM,WAAW,CAAA;AAMlB,wBAAsB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAiDzF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAWvE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Custom OpenAI-compatible provider adapter. The base URL is admin-controlled,
3
+ * so it is SSRF-validated and DNS-resolved before any request. Falls back to an
4
+ * empty model list (manual entry in the UI) when the endpoint doesn't support
5
+ * `/models`.
6
+ */
7
+ import { assertSafeBaseUrl, extractModelIds, messageFromStatus, statusFromHttp, timedFetch, trimTrailingSlash, } from './http.js';
8
+ function headers(ctx) {
9
+ return { authorization: `Bearer ${ctx.apiKey}` };
10
+ }
11
+ export async function testConnection(ctx) {
12
+ const testedAt = new Date().toISOString();
13
+ if (!ctx.baseUrl) {
14
+ return {
15
+ ok: false,
16
+ status: 'needs_setup',
17
+ message: 'A base URL is required for a custom provider.',
18
+ testedAt,
19
+ };
20
+ }
21
+ try {
22
+ await assertSafeBaseUrl(ctx.baseUrl);
23
+ }
24
+ catch (err) {
25
+ return {
26
+ ok: false,
27
+ status: 'unknown',
28
+ message: err instanceof Error ? err.message : 'Invalid base URL.',
29
+ testedAt,
30
+ };
31
+ }
32
+ if (!ctx.apiKey) {
33
+ return { ok: false, status: 'needs_setup', message: messageFromStatus('needs_setup'), testedAt };
34
+ }
35
+ try {
36
+ const res = await timedFetch(`${trimTrailingSlash(ctx.baseUrl)}/models`, { method: 'GET', headers: headers(ctx) }, ctx);
37
+ // A 404 likely means the endpoint doesn't implement /models; the connection
38
+ // itself may still be usable with a manually-entered model id.
39
+ if (res.status === 404) {
40
+ return {
41
+ ok: true,
42
+ status: 'connected',
43
+ message: 'Connected. This endpoint does not list models; enter a model id manually.',
44
+ testedAt,
45
+ };
46
+ }
47
+ const status = statusFromHttp(res.status);
48
+ return { ok: status === 'connected', status, message: messageFromStatus(status), testedAt };
49
+ }
50
+ catch {
51
+ return {
52
+ ok: false,
53
+ status: 'provider_down',
54
+ message: messageFromStatus('provider_down'),
55
+ testedAt,
56
+ };
57
+ }
58
+ }
59
+ export async function listModels(ctx) {
60
+ if (!ctx.baseUrl)
61
+ return [];
62
+ await assertSafeBaseUrl(ctx.baseUrl);
63
+ const res = await timedFetch(`${trimTrailingSlash(ctx.baseUrl)}/models`, { method: 'GET', headers: headers(ctx) }, ctx);
64
+ if (res.status === 404)
65
+ return [];
66
+ if (!res.ok)
67
+ throw new Error(`Custom provider model listing failed (${res.status})`);
68
+ return extractModelIds(await res.json());
69
+ }
70
+ //# sourceMappingURL=custom.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom.js","sourceRoot":"","sources":["../../../src/ai/providers/custom.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared HTTP helpers for AI provider adapters: a timeout-bounded fetch, a
3
+ * status->AIConnectionStatus mapper, and an SSRF guard for custom base URLs.
4
+ * Built-in providers use fixed hostnames; only custom endpoints are
5
+ * admin-controlled and therefore validated/resolved.
6
+ */
7
+ import type { AIConnectionStatus } from '../types.js';
8
+ export interface AdapterContext {
9
+ apiKey: string;
10
+ baseUrl?: string;
11
+ organizationId?: string;
12
+ projectId?: string;
13
+ /** Injectable fetch for tests; defaults to global `fetch`. */
14
+ fetchImpl?: typeof fetch;
15
+ timeoutMs?: number;
16
+ }
17
+ /** Map an HTTP response status to a connection-health value. */
18
+ export declare function statusFromHttp(status: number): AIConnectionStatus;
19
+ /** A short, safe message for a status (never contains the key). */
20
+ export declare function messageFromStatus(status: AIConnectionStatus): string;
21
+ export declare function timedFetch(url: string, init: RequestInit, ctx: AdapterContext): Promise<Response>;
22
+ /**
23
+ * Validate a custom provider base URL against SSRF (no private/internal hosts,
24
+ * https/http only) AND resolve its DNS to defend against rebinding. Throws on
25
+ * rejection with a safe message.
26
+ */
27
+ export declare function assertSafeBaseUrl(baseUrl: string): Promise<void>;
28
+ /** Strip a trailing slash so we can append `/models` etc. cleanly. */
29
+ export declare function trimTrailingSlash(url: string): string;
30
+ /** Best-effort extraction of model ids from an OpenAI-compatible list payload. */
31
+ export declare function extractModelIds(payload: unknown): string[];
32
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/ai/providers/http.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAMjE;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAepE;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,QAAQ,CAAC,CAYnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAetE;AAED,sEAAsE;AACtE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAS1D"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Shared HTTP helpers for AI provider adapters: a timeout-bounded fetch, a
3
+ * status->AIConnectionStatus mapper, and an SSRF guard for custom base URLs.
4
+ * Built-in providers use fixed hostnames; only custom endpoints are
5
+ * admin-controlled and therefore validated/resolved.
6
+ */
7
+ import { resolveAndCheck, validateWebhookUrl } from '../../security/webhook.js';
8
+ /** Map an HTTP response status to a connection-health value. */
9
+ export function statusFromHttp(status) {
10
+ if (status >= 200 && status < 300)
11
+ return 'connected';
12
+ if (status === 401 || status === 403)
13
+ return 'invalid_key';
14
+ if (status === 429)
15
+ return 'rate_limited';
16
+ if (status >= 500)
17
+ return 'provider_down';
18
+ return 'unknown';
19
+ }
20
+ /** A short, safe message for a status (never contains the key). */
21
+ export function messageFromStatus(status) {
22
+ switch (status) {
23
+ case 'connected':
24
+ return 'Connection successful.';
25
+ case 'invalid_key':
26
+ return 'The API key was rejected (invalid or unauthorized).';
27
+ case 'rate_limited':
28
+ return 'The provider rate-limited the request. Try again shortly.';
29
+ case 'provider_down':
30
+ return 'The provider returned a server error. It may be temporarily unavailable.';
31
+ case 'needs_setup':
32
+ return 'No API key is configured for this provider.';
33
+ default:
34
+ return 'Could not determine connection status.';
35
+ }
36
+ }
37
+ export async function timedFetch(url, init, ctx) {
38
+ const doFetch = ctx.fetchImpl ?? globalThis.fetch;
39
+ if (typeof doFetch !== 'function') {
40
+ throw new Error('fetch is not available in this runtime');
41
+ }
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), ctx.timeoutMs ?? 10_000);
44
+ try {
45
+ return await doFetch(url, { ...init, redirect: 'error', signal: controller.signal });
46
+ }
47
+ finally {
48
+ clearTimeout(timer);
49
+ }
50
+ }
51
+ /**
52
+ * Validate a custom provider base URL against SSRF (no private/internal hosts,
53
+ * https/http only) AND resolve its DNS to defend against rebinding. Throws on
54
+ * rejection with a safe message.
55
+ */
56
+ export async function assertSafeBaseUrl(baseUrl) {
57
+ const check = validateWebhookUrl(baseUrl);
58
+ if (!check.valid) {
59
+ throw new Error(`Unsafe base URL: ${check.error ?? 'invalid'}`);
60
+ }
61
+ let host;
62
+ try {
63
+ host = new URL(baseUrl).hostname;
64
+ }
65
+ catch {
66
+ throw new Error('Unsafe base URL: invalid URL');
67
+ }
68
+ const resolved = await resolveAndCheck(host);
69
+ if (!resolved.safe) {
70
+ throw new Error(`Unsafe base URL: ${resolved.error ?? 'resolves to a private address'}`);
71
+ }
72
+ }
73
+ /** Strip a trailing slash so we can append `/models` etc. cleanly. */
74
+ export function trimTrailingSlash(url) {
75
+ return url.replace(/\/+$/, '');
76
+ }
77
+ /** Best-effort extraction of model ids from an OpenAI-compatible list payload. */
78
+ export function extractModelIds(payload) {
79
+ const data = payload?.data;
80
+ if (!Array.isArray(data))
81
+ return [];
82
+ const ids = [];
83
+ for (const entry of data) {
84
+ const id = entry?.id;
85
+ if (typeof id === 'string' && id.trim())
86
+ ids.push(id.trim());
87
+ }
88
+ return ids;
89
+ }
90
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../../src/ai/providers/http.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Provider adapter dispatcher. Selects the right adapter by provider key and
3
+ * exposes a uniform `testConnection` / `listModels` surface to the catalog and
4
+ * route layers.
5
+ */
6
+ import type { AIConnectionTestResult, AIProviderKey } from '../types.js';
7
+ import type { AdapterContext } from './http.js';
8
+ export type { AdapterContext } from './http.js';
9
+ interface ProviderAdapter {
10
+ testConnection(ctx: AdapterContext): Promise<AIConnectionTestResult>;
11
+ listModels(ctx: AdapterContext): Promise<string[]>;
12
+ }
13
+ export declare function getAdapter(provider: AIProviderKey): ProviderAdapter;
14
+ export declare function testConnection(provider: AIProviderKey, ctx: AdapterContext): Promise<AIConnectionTestResult>;
15
+ export declare function listModels(provider: AIProviderKey, ctx: AdapterContext): Promise<string[]>;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ai/providers/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAGxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAI/C,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE/C,UAAU,eAAe;IACvB,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACpE,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;CACnD;AASD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,eAAe,CAEnE;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,cAAc,GAClB,OAAO,CAAC,sBAAsB,CAAC,CAEjC;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAE1F"}