@actuate-media/cms-core 0.43.0 → 0.46.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 (63) hide show
  1. package/dist/__tests__/ai/brand-voice.test.d.ts +2 -0
  2. package/dist/__tests__/ai/brand-voice.test.d.ts.map +1 -0
  3. package/dist/__tests__/ai/brand-voice.test.js +82 -0
  4. package/dist/__tests__/ai/brand-voice.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/__tests__/ai/runtime.test.d.ts +2 -0
  10. package/dist/__tests__/ai/runtime.test.d.ts.map +1 -0
  11. package/dist/__tests__/ai/runtime.test.js +292 -0
  12. package/dist/__tests__/ai/runtime.test.js.map +1 -0
  13. package/dist/__tests__/api/ai-quality.test.js +178 -0
  14. package/dist/__tests__/api/ai-quality.test.js.map +1 -1
  15. package/dist/__tests__/api/health.test.js +9 -5
  16. package/dist/__tests__/api/health.test.js.map +1 -1
  17. package/dist/ai/brand-voice.d.ts +37 -0
  18. package/dist/ai/brand-voice.d.ts.map +1 -0
  19. package/dist/ai/brand-voice.js +85 -0
  20. package/dist/ai/brand-voice.js.map +1 -0
  21. package/dist/ai/index.d.ts +7 -4
  22. package/dist/ai/index.d.ts.map +1 -1
  23. package/dist/ai/index.js +7 -4
  24. package/dist/ai/index.js.map +1 -1
  25. package/dist/ai/providers/anthropic.d.ts +2 -1
  26. package/dist/ai/providers/anthropic.d.ts.map +1 -1
  27. package/dist/ai/providers/anthropic.js +28 -1
  28. package/dist/ai/providers/anthropic.js.map +1 -1
  29. package/dist/ai/providers/custom.d.ts +2 -1
  30. package/dist/ai/providers/custom.d.ts.map +1 -1
  31. package/dist/ai/providers/custom.js +7 -1
  32. package/dist/ai/providers/custom.js.map +1 -1
  33. package/dist/ai/providers/http.d.ts +30 -0
  34. package/dist/ai/providers/http.d.ts.map +1 -1
  35. package/dist/ai/providers/http.js +51 -0
  36. package/dist/ai/providers/http.js.map +1 -1
  37. package/dist/ai/providers/index.d.ts +4 -2
  38. package/dist/ai/providers/index.d.ts.map +1 -1
  39. package/dist/ai/providers/index.js +3 -0
  40. package/dist/ai/providers/index.js.map +1 -1
  41. package/dist/ai/providers/openai.d.ts +2 -1
  42. package/dist/ai/providers/openai.d.ts.map +1 -1
  43. package/dist/ai/providers/openai.js +4 -1
  44. package/dist/ai/providers/openai.js.map +1 -1
  45. package/dist/ai/providers/xai.d.ts +2 -1
  46. package/dist/ai/providers/xai.d.ts.map +1 -1
  47. package/dist/ai/providers/xai.js +4 -1
  48. package/dist/ai/providers/xai.js.map +1 -1
  49. package/dist/ai/resolve.d.ts +41 -0
  50. package/dist/ai/resolve.d.ts.map +1 -0
  51. package/dist/ai/resolve.js +142 -0
  52. package/dist/ai/resolve.js.map +1 -0
  53. package/dist/ai/runtime.d.ts +86 -0
  54. package/dist/ai/runtime.d.ts.map +1 -0
  55. package/dist/ai/runtime.js +177 -0
  56. package/dist/ai/runtime.js.map +1 -0
  57. package/dist/ai/types.d.ts +24 -0
  58. package/dist/ai/types.d.ts.map +1 -1
  59. package/dist/ai/usage.js +1 -1
  60. package/dist/api/handlers.d.ts.map +1 -1
  61. package/dist/api/handlers.js +359 -124
  62. package/dist/api/handlers.js.map +1 -1
  63. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=brand-voice.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-voice.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/ai/brand-voice.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { applyBrandVoice, buildBrandVoiceSystemPrompt, getActiveBrandVoice, normalizeBrandVoice, BRAND_VOICE_LIMITS, } from '../../ai/brand-voice.js';
3
+ function profile(over = {}) {
4
+ return { enabled: true, name: 'Acme', tone: 'confident, warm', ...over };
5
+ }
6
+ function config(brandVoice) {
7
+ return {
8
+ providers: [],
9
+ settings: {
10
+ features: [],
11
+ budget: { alertAtPercentages: [], disableWhenExceeded: false },
12
+ logsRetentionDays: 30,
13
+ brandVoice,
14
+ },
15
+ };
16
+ }
17
+ describe('buildBrandVoiceSystemPrompt', () => {
18
+ it('returns null when disabled', () => {
19
+ expect(buildBrandVoiceSystemPrompt(profile({ enabled: false }))).toBeNull();
20
+ });
21
+ it('returns null when there is no tone or guidelines', () => {
22
+ expect(buildBrandVoiceSystemPrompt(profile({ tone: '', guidelines: '' }))).toBeNull();
23
+ });
24
+ it('includes brand, tone, audience, guidelines, and a fenced sample', () => {
25
+ const out = buildBrandVoiceSystemPrompt(profile({
26
+ audience: 'developers',
27
+ guidelines: 'No jargon. Use active voice.',
28
+ sampleText: 'Ship faster, sleep better.',
29
+ }));
30
+ expect(out).toContain('Brand: Acme');
31
+ expect(out).toContain('Tone: confident, warm');
32
+ expect(out).toContain('Audience: developers');
33
+ expect(out).toContain('No jargon');
34
+ expect(out).toContain('<<<BRAND_SAMPLE');
35
+ expect(out).toContain('Ship faster, sleep better.');
36
+ });
37
+ });
38
+ describe('applyBrandVoice', () => {
39
+ it('prepends the voice before the task system prompt', () => {
40
+ const out = applyBrandVoice('Return only JSON.', profile());
41
+ expect(out).toMatch(/^Write in the following brand voice:/);
42
+ expect(out).toContain('Return only JSON.');
43
+ // Voice comes first; task instruction last so it can override format.
44
+ expect(out.indexOf('brand voice')).toBeLessThan(out.indexOf('Return only JSON.'));
45
+ });
46
+ it('returns the original system prompt untouched when no voice applies', () => {
47
+ expect(applyBrandVoice('sys', profile({ enabled: false }))).toBe('sys');
48
+ expect(applyBrandVoice(undefined, undefined)).toBeUndefined();
49
+ });
50
+ });
51
+ describe('getActiveBrandVoice', () => {
52
+ it('returns null when disabled or empty, the profile when usable', () => {
53
+ expect(getActiveBrandVoice(config(undefined))).toBeNull();
54
+ expect(getActiveBrandVoice(config(profile({ enabled: false })))).toBeNull();
55
+ expect(getActiveBrandVoice(config(profile({ tone: '', guidelines: '' })))).toBeNull();
56
+ expect(getActiveBrandVoice(config(profile()))?.name).toBe('Acme');
57
+ });
58
+ });
59
+ describe('normalizeBrandVoice', () => {
60
+ it('clamps long fields and coerces enabled', () => {
61
+ const long = 'x'.repeat(5000);
62
+ const out = normalizeBrandVoice({
63
+ enabled: 1,
64
+ name: long,
65
+ tone: long,
66
+ guidelines: long,
67
+ sampleText: long,
68
+ });
69
+ expect(out.enabled).toBe(true);
70
+ expect(out.name.length).toBe(BRAND_VOICE_LIMITS.name);
71
+ expect(out.tone.length).toBe(BRAND_VOICE_LIMITS.tone);
72
+ expect(out.guidelines.length).toBe(BRAND_VOICE_LIMITS.guidelines);
73
+ expect(out.sampleText.length).toBe(BRAND_VOICE_LIMITS.sampleText);
74
+ });
75
+ it('drops empty optional fields to undefined', () => {
76
+ const out = normalizeBrandVoice({ enabled: false, name: 'A', tone: 'b', audience: ' ' });
77
+ expect(out.audience).toBeUndefined();
78
+ expect(out.guidelines).toBeUndefined();
79
+ expect(out.sampleText).toBeUndefined();
80
+ });
81
+ });
82
+ //# sourceMappingURL=brand-voice.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brand-voice.test.js","sourceRoot":"","sources":["../../../src/__tests__/ai/brand-voice.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolve.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/ai/resolve.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,164 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { resolveAllFeatureModels, resolveFeatureModel } from '../../ai/resolve.js';
3
+ const NO_CAPS = {
4
+ text: false,
5
+ vision: false,
6
+ imageGeneration: false,
7
+ embeddings: false,
8
+ toolCalling: false,
9
+ structuredOutput: false,
10
+ streaming: false,
11
+ reasoning: false,
12
+ longContext: false,
13
+ };
14
+ function model(id, caps, over = {}) {
15
+ const [provider = 'anthropic', providerModelId = id] = id.split(':');
16
+ return {
17
+ id,
18
+ provider: provider,
19
+ providerModelId,
20
+ displayName: id,
21
+ status: 'available',
22
+ capabilities: { ...NO_CAPS, ...caps },
23
+ inputModalities: ['text'],
24
+ outputModalities: ['text'],
25
+ lastSyncedAt: '2026-01-01T00:00:00.000Z',
26
+ source: 'provider',
27
+ metadata: {},
28
+ ...over,
29
+ };
30
+ }
31
+ function conn(over = {}) {
32
+ return {
33
+ id: 'p-anthropic',
34
+ provider: 'anthropic',
35
+ displayName: 'Anthropic',
36
+ enabled: true,
37
+ status: 'connected',
38
+ createdAt: '2026-01-01T00:00:00.000Z',
39
+ updatedAt: '2026-01-01T00:00:00.000Z',
40
+ updatedBy: 'u1',
41
+ ...over,
42
+ };
43
+ }
44
+ function config(over) {
45
+ return {
46
+ providers: over.providers ?? [conn()],
47
+ settings: {
48
+ defaultModelId: over.defaultModelId,
49
+ defaultVisionModelId: over.defaultVisionModelId,
50
+ features: over.features ?? [],
51
+ budget: { alertAtPercentages: [], disableWhenExceeded: false },
52
+ logsRetentionDays: 30,
53
+ },
54
+ };
55
+ }
56
+ const textFeature = {
57
+ key: 'ai.features.contentWriting',
58
+ label: 'Content writing',
59
+ description: '',
60
+ enabled: true,
61
+ category: 'content',
62
+ requiredCapabilities: ['text'],
63
+ approvalRequired: false,
64
+ allowBulkApply: false,
65
+ rolesAllowed: ['ADMIN'],
66
+ permissionsRequired: ['ai.use'],
67
+ };
68
+ describe('resolveFeatureModel', () => {
69
+ it('uses the global default when the feature has no override', () => {
70
+ const catalog = [model('anthropic:claude-x', { text: true })];
71
+ const cfg = config({ defaultModelId: 'anthropic:claude-x', features: [textFeature] });
72
+ const r = resolveFeatureModel(cfg, textFeature.key, catalog);
73
+ expect(r.modelId).toBe('anthropic:claude-x');
74
+ expect(r.source).toBe('global-default');
75
+ expect(r.providerId).toBe('p-anthropic');
76
+ expect(r.satisfiesCapabilities).toBe(true);
77
+ });
78
+ it('prefers the feature-level model override over the default', () => {
79
+ const catalog = [
80
+ model('anthropic:claude-x', { text: true }),
81
+ model('openai:gpt-y', { text: true }, { provider: 'openai' }),
82
+ ];
83
+ const cfg = config({
84
+ providers: [conn(), conn({ id: 'p-openai', provider: 'openai' })],
85
+ defaultModelId: 'anthropic:claude-x',
86
+ features: [{ ...textFeature, modelId: 'openai:gpt-y' }],
87
+ });
88
+ const r = resolveFeatureModel(cfg, textFeature.key, catalog);
89
+ expect(r.modelId).toBe('openai:gpt-y');
90
+ expect(r.source).toBe('feature-override');
91
+ expect(r.providerId).toBe('p-openai');
92
+ });
93
+ it('falls back to the capability-typed default for a vision feature', () => {
94
+ const visionFeature = {
95
+ ...textFeature,
96
+ key: 'ai.features.mediaAltText',
97
+ requiredCapabilities: ['vision'],
98
+ };
99
+ const catalog = [
100
+ model('anthropic:claude-text', { text: true }),
101
+ model('openai:gpt-vision', { text: true, vision: true }, { provider: 'openai' }),
102
+ ];
103
+ const cfg = config({
104
+ providers: [conn(), conn({ id: 'p-openai', provider: 'openai' })],
105
+ defaultModelId: 'anthropic:claude-text', // text-only, can't satisfy vision
106
+ defaultVisionModelId: 'openai:gpt-vision',
107
+ features: [visionFeature],
108
+ });
109
+ const r = resolveFeatureModel(cfg, visionFeature.key, catalog);
110
+ expect(r.modelId).toBe('openai:gpt-vision');
111
+ expect(r.source).toBe('capability-default');
112
+ expect(r.satisfiesCapabilities).toBe(true);
113
+ });
114
+ it('reports missing capabilities when no candidate satisfies them', () => {
115
+ const visionFeature = {
116
+ ...textFeature,
117
+ key: 'ai.features.mediaAltText',
118
+ requiredCapabilities: ['vision'],
119
+ };
120
+ const catalog = [model('anthropic:claude-text', { text: true })];
121
+ const cfg = config({ defaultModelId: 'anthropic:claude-text', features: [visionFeature] });
122
+ const r = resolveFeatureModel(cfg, visionFeature.key, catalog);
123
+ expect(r.modelId).toBe('anthropic:claude-text');
124
+ expect(r.satisfiesCapabilities).toBe(false);
125
+ expect(r.missingCapabilities).toContain('vision');
126
+ });
127
+ it('keeps an uncatalogued override and flags it as not satisfying capabilities', () => {
128
+ const cfg = config({
129
+ features: [{ ...textFeature, modelId: 'anthropic:ghost-model' }],
130
+ });
131
+ const r = resolveFeatureModel(cfg, textFeature.key, []);
132
+ expect(r.modelId).toBe('anthropic:ghost-model');
133
+ expect(r.source).toBe('feature-override');
134
+ expect(r.satisfiesCapabilities).toBe(false);
135
+ });
136
+ it('returns source "none" with no model configured', () => {
137
+ const cfg = config({ features: [textFeature] });
138
+ const r = resolveFeatureModel(cfg, textFeature.key, []);
139
+ expect(r.source).toBe('none');
140
+ expect(r.modelId).toBeUndefined();
141
+ expect(r.missingCapabilities).toContain('text');
142
+ });
143
+ it('does not select an unavailable model when an available one exists', () => {
144
+ const catalog = [
145
+ model('anthropic:dead', { text: true }, { status: 'unavailable' }),
146
+ model('anthropic:live', { text: true }),
147
+ ];
148
+ const cfg = config({
149
+ defaultModelId: 'anthropic:dead',
150
+ features: [{ ...textFeature, fallbackModelIds: ['anthropic:live'] }],
151
+ });
152
+ const r = resolveFeatureModel(cfg, textFeature.key, catalog);
153
+ expect(r.modelId).toBe('anthropic:live');
154
+ expect(r.source).toBe('fallback');
155
+ });
156
+ it('resolveAllFeatureModels keys by feature', () => {
157
+ const catalog = [model('anthropic:claude-x', { text: true })];
158
+ const cfg = config({ defaultModelId: 'anthropic:claude-x', features: [textFeature] });
159
+ const all = resolveAllFeatureModels(cfg, catalog);
160
+ expect(Object.keys(all)).toEqual([textFeature.key]);
161
+ expect(all[textFeature.key]?.modelId).toBe('anthropic:claude-x');
162
+ });
163
+ });
164
+ //# sourceMappingURL=resolve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.test.js","sourceRoot":"","sources":["../../../src/__tests__/ai/resolve.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runtime.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/ai/runtime.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,292 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { generateTextForFeature, isAiFeatureEnabled, resolveFeatureRuntime, } from '../../ai/runtime.js';
3
+ const NO_CAPS = {
4
+ text: false,
5
+ vision: false,
6
+ imageGeneration: false,
7
+ embeddings: false,
8
+ toolCalling: false,
9
+ structuredOutput: false,
10
+ streaming: false,
11
+ reasoning: false,
12
+ longContext: false,
13
+ };
14
+ function model(id, caps, over = {}) {
15
+ const [provider = 'openai', providerModelId = id] = id.split(':');
16
+ return {
17
+ id,
18
+ provider: provider,
19
+ providerModelId,
20
+ displayName: id,
21
+ status: 'available',
22
+ capabilities: { ...NO_CAPS, ...caps },
23
+ inputModalities: ['text'],
24
+ outputModalities: ['text'],
25
+ lastSyncedAt: '2026-01-01T00:00:00.000Z',
26
+ source: 'provider',
27
+ metadata: {},
28
+ ...over,
29
+ };
30
+ }
31
+ function conn(over = {}) {
32
+ return {
33
+ id: 'p-openai',
34
+ provider: 'openai',
35
+ displayName: 'OpenAI',
36
+ enabled: true,
37
+ status: 'connected',
38
+ createdAt: '2026-01-01T00:00:00.000Z',
39
+ updatedAt: '2026-01-01T00:00:00.000Z',
40
+ updatedBy: 'u1',
41
+ ...over,
42
+ };
43
+ }
44
+ const CONTENT_FEATURE = {
45
+ key: 'ai.features.contentWriting',
46
+ label: 'Content writing',
47
+ description: '',
48
+ enabled: true,
49
+ category: 'content',
50
+ requiredCapabilities: ['text'],
51
+ approvalRequired: false,
52
+ allowBulkApply: false,
53
+ rolesAllowed: ['ADMIN'],
54
+ permissionsRequired: ['ai.use'],
55
+ };
56
+ function makeDb(opts) {
57
+ const created = [];
58
+ const data = {
59
+ providers: opts.providers ?? [],
60
+ settings: opts.settings ?? {},
61
+ catalog: opts.catalog,
62
+ };
63
+ const db = {
64
+ document: {
65
+ findFirst: vi.fn(async () => ({ data })),
66
+ },
67
+ auditLog: {
68
+ findMany: vi.fn(async () => opts.auditRows ?? []),
69
+ create: vi.fn(async (args) => {
70
+ created.push(args.data);
71
+ return args.data;
72
+ }),
73
+ },
74
+ };
75
+ return { db, created };
76
+ }
77
+ const KEY = CONTENT_FEATURE.key;
78
+ afterEach(() => {
79
+ delete process.env.OPENAI_API_KEY;
80
+ delete process.env.ANTHROPIC_API_KEY;
81
+ });
82
+ describe('isAiFeatureEnabled', () => {
83
+ it('reflects the stored toggle and defaults unknown keys to enabled', async () => {
84
+ const { db } = makeDb({
85
+ providers: [conn()],
86
+ settings: { features: [{ ...CONTENT_FEATURE, enabled: false }] },
87
+ });
88
+ expect(await isAiFeatureEnabled(db, KEY)).toBe(false);
89
+ expect(await isAiFeatureEnabled(db, 'ai.features.unknown')).toBe(true);
90
+ });
91
+ });
92
+ describe('resolveFeatureRuntime', () => {
93
+ it('blocks a disabled feature', async () => {
94
+ const { db } = makeDb({
95
+ providers: [conn()],
96
+ settings: { features: [{ ...CONTENT_FEATURE, enabled: false }] },
97
+ });
98
+ const r = await resolveFeatureRuntime(db, KEY);
99
+ expect(r.ok).toBe(false);
100
+ if (!r.ok)
101
+ expect(r.code).toBe('feature_disabled');
102
+ });
103
+ it('returns no_model when nothing is configured', async () => {
104
+ const { db } = makeDb({ providers: [], settings: { features: [CONTENT_FEATURE] } });
105
+ const r = await resolveFeatureRuntime(db, KEY);
106
+ expect(r.ok).toBe(false);
107
+ if (!r.ok)
108
+ expect(r.code).toBe('no_model');
109
+ });
110
+ it('returns no_provider when the only matching connection is disabled', async () => {
111
+ process.env.OPENAI_API_KEY = 'sk-env-test';
112
+ const { db } = makeDb({
113
+ providers: [conn({ enabled: false })],
114
+ settings: { defaultModelId: 'openai:gpt-x', features: [CONTENT_FEATURE] },
115
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
116
+ });
117
+ const r = await resolveFeatureRuntime(db, KEY);
118
+ expect(r.ok).toBe(false);
119
+ if (!r.ok)
120
+ expect(r.code).toBe('no_provider');
121
+ });
122
+ it('returns no_key when neither env nor a stored key exists', async () => {
123
+ const { db } = makeDb({
124
+ providers: [conn({ provider: 'anthropic', id: 'p-anthropic' })],
125
+ settings: { defaultModelId: 'anthropic:claude-x', features: [CONTENT_FEATURE] },
126
+ catalog: { 'p-anthropic': { models: [model('anthropic:claude-x', { text: true })] } },
127
+ });
128
+ const r = await resolveFeatureRuntime(db, KEY);
129
+ expect(r.ok).toBe(false);
130
+ if (!r.ok)
131
+ expect(r.code).toBe('no_key');
132
+ });
133
+ it('resolves a ready runtime, stripping the provider prefix from the model id', async () => {
134
+ process.env.OPENAI_API_KEY = 'sk-env-test';
135
+ const { db } = makeDb({
136
+ providers: [conn()],
137
+ settings: { defaultModelId: 'openai:gpt-x', features: [CONTENT_FEATURE] },
138
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
139
+ });
140
+ const r = await resolveFeatureRuntime(db, KEY);
141
+ expect(r.ok).toBe(true);
142
+ if (r.ok) {
143
+ expect(r.providerKey).toBe('openai');
144
+ expect(r.apiKey).toBe('sk-env-test');
145
+ expect(r.modelId).toBe('openai:gpt-x');
146
+ expect(r.providerModelId).toBe('gpt-x');
147
+ }
148
+ });
149
+ it('blocks when the monthly budget is exceeded and enforcement is on', async () => {
150
+ process.env.OPENAI_API_KEY = 'sk-env-test';
151
+ const { db } = makeDb({
152
+ providers: [conn()],
153
+ settings: {
154
+ defaultModelId: 'openai:gpt-x',
155
+ features: [CONTENT_FEATURE],
156
+ budget: { alertAtPercentages: [], disableWhenExceeded: true, monthlyTokenLimit: 100 },
157
+ },
158
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
159
+ auditRows: [{ details: { totalTokensUsed: 250 } }],
160
+ });
161
+ const r = await resolveFeatureRuntime(db, KEY);
162
+ expect(r.ok).toBe(false);
163
+ if (!r.ok)
164
+ expect(r.code).toBe('budget_exceeded');
165
+ });
166
+ });
167
+ describe('generateTextForFeature', () => {
168
+ function okFetch(body) {
169
+ return vi.fn(async () => new Response(JSON.stringify(body), {
170
+ status: 200,
171
+ headers: { 'content-type': 'application/json' },
172
+ }));
173
+ }
174
+ it('generates text and records real token usage', async () => {
175
+ process.env.OPENAI_API_KEY = 'sk-env-test';
176
+ const { db, created } = makeDb({
177
+ providers: [conn()],
178
+ settings: { defaultModelId: 'openai:gpt-x', features: [CONTENT_FEATURE] },
179
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
180
+ });
181
+ const fetchImpl = okFetch({
182
+ choices: [{ message: { content: 'rewritten copy' } }],
183
+ usage: { prompt_tokens: 12, completion_tokens: 34, total_tokens: 46 },
184
+ });
185
+ const r = await generateTextForFeature(db, KEY, {
186
+ prompt: 'hello',
187
+ system: 'be terse',
188
+ userId: 'u1',
189
+ fetchImpl,
190
+ });
191
+ expect(r.ok).toBe(true);
192
+ if (r.ok) {
193
+ expect(r.text).toBe('rewritten copy');
194
+ expect(r.usage.totalTokens).toBe(46);
195
+ expect(r.modelId).toBe('openai:gpt-x');
196
+ }
197
+ expect(fetchImpl).toHaveBeenCalledTimes(1);
198
+ // A usage audit row is recorded with fields the usage meter reads.
199
+ expect(created).toHaveLength(1);
200
+ expect(created[0].event).toBe('ai_usage');
201
+ expect(created[0].details.totalTokensUsed).toBe(46);
202
+ expect(created[0].details.feature).toBe(KEY);
203
+ });
204
+ it('surfaces a provider HTTP failure as provider_error without recording usage', async () => {
205
+ process.env.OPENAI_API_KEY = 'sk-env-test';
206
+ const { db, created } = makeDb({
207
+ providers: [conn()],
208
+ settings: { defaultModelId: 'openai:gpt-x', features: [CONTENT_FEATURE] },
209
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
210
+ });
211
+ const fetchImpl = vi.fn(async () => new Response('nope', { status: 401 }));
212
+ const r = await generateTextForFeature(db, KEY, { prompt: 'hello', fetchImpl });
213
+ expect(r.ok).toBe(false);
214
+ if (!r.ok)
215
+ expect(r.code).toBe('provider_error');
216
+ expect(created).toHaveLength(0);
217
+ });
218
+ it('injects the active brand voice into the system prompt when opted in', async () => {
219
+ process.env.OPENAI_API_KEY = 'sk-env-test';
220
+ const { db } = makeDb({
221
+ providers: [conn()],
222
+ settings: {
223
+ defaultModelId: 'openai:gpt-x',
224
+ features: [CONTENT_FEATURE],
225
+ brandVoice: {
226
+ enabled: true,
227
+ name: 'Acme',
228
+ tone: 'confident, warm',
229
+ guidelines: 'No jargon.',
230
+ },
231
+ },
232
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
233
+ });
234
+ let sentBody;
235
+ const fetchImpl = vi.fn(async (_url, init) => {
236
+ sentBody = JSON.parse(init.body);
237
+ return new Response(JSON.stringify({
238
+ choices: [{ message: { content: 'ok' } }],
239
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
240
+ }), { status: 200, headers: { 'content-type': 'application/json' } });
241
+ });
242
+ const r = await generateTextForFeature(db, KEY, {
243
+ prompt: 'write a headline',
244
+ system: 'Be concise.',
245
+ useBrandVoice: true,
246
+ fetchImpl,
247
+ });
248
+ expect(r.ok).toBe(true);
249
+ const systemMsg = sentBody?.messages.find((m) => m.role === 'system')?.content ?? '';
250
+ expect(systemMsg).toContain('brand voice');
251
+ expect(systemMsg).toContain('confident, warm');
252
+ // Task instruction still present, after the voice.
253
+ expect(systemMsg).toContain('Be concise.');
254
+ });
255
+ it('does NOT inject brand voice unless the caller opts in', async () => {
256
+ process.env.OPENAI_API_KEY = 'sk-env-test';
257
+ const { db } = makeDb({
258
+ providers: [conn()],
259
+ settings: {
260
+ defaultModelId: 'openai:gpt-x',
261
+ features: [CONTENT_FEATURE],
262
+ brandVoice: { enabled: true, name: 'Acme', tone: 'confident', guidelines: 'No jargon.' },
263
+ },
264
+ catalog: { 'p-openai': { models: [model('openai:gpt-x', { text: true })] } },
265
+ });
266
+ let sentBody;
267
+ const fetchImpl = vi.fn(async (_url, init) => {
268
+ sentBody = JSON.parse(init.body);
269
+ return new Response(JSON.stringify({
270
+ choices: [{ message: { content: 'ok' } }],
271
+ usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
272
+ }), { status: 200, headers: { 'content-type': 'application/json' } });
273
+ });
274
+ await generateTextForFeature(db, KEY, { prompt: 'x', system: 'Be concise.', fetchImpl });
275
+ const systemMsg = sentBody?.messages.find((m) => m.role === 'system')?.content ?? '';
276
+ expect(systemMsg).toBe('Be concise.');
277
+ expect(systemMsg).not.toContain('brand voice');
278
+ });
279
+ it('short-circuits a disabled feature before any network call', async () => {
280
+ const { db } = makeDb({
281
+ providers: [conn()],
282
+ settings: { features: [{ ...CONTENT_FEATURE, enabled: false }] },
283
+ });
284
+ const fetchImpl = vi.fn();
285
+ const r = await generateTextForFeature(db, KEY, { prompt: 'hi', fetchImpl });
286
+ expect(r.ok).toBe(false);
287
+ if (!r.ok)
288
+ expect(r.code).toBe('feature_disabled');
289
+ expect(fetchImpl).not.toHaveBeenCalled();
290
+ });
291
+ });
292
+ //# sourceMappingURL=runtime.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.test.js","sourceRoot":"","sources":["../../../src/__tests__/ai/runtime.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAA;AAE5D,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,qBAAqB,CAAA;AAQ5B,MAAM,OAAO,GAAwB;IACnC,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;AAED,SAAS,KAAK,CACZ,EAAU,EACV,IAAkC,EAClC,OAAyB,EAAE;IAE3B,MAAM,CAAC,QAAQ,GAAG,QAAQ,EAAE,eAAe,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACjE,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,QAA+B;QACzC,eAAe;QACf,WAAW,EAAE,EAAE;QACf,MAAM,EAAE,WAAW;QACnB,YAAY,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,IAAI,EAAE;QACrC,eAAe,EAAE,CAAC,MAAM,CAAC;QACzB,gBAAgB,EAAE,CAAC,MAAM,CAAC;QAC1B,YAAY,EAAE,0BAA0B;QACxC,MAAM,EAAE,UAAU;QAClB,QAAQ,EAAE,EAAE;QACZ,GAAG,IAAI;KACR,CAAA;AACH,CAAC;AAED,SAAS,IAAI,CAAC,OAAsC,EAAE;IACpD,OAAO;QACL,EAAE,EAAE,UAAU;QACd,QAAQ,EAAE,QAAQ;QAClB,WAAW,EAAE,QAAQ;QACrB,OAAO,EAAE,IAAI;QACb,MAAM,EAAE,WAAW;QACnB,SAAS,EAAE,0BAA0B;QACrC,SAAS,EAAE,0BAA0B;QACrC,SAAS,EAAE,IAAI;QACf,GAAG,IAAI;KACR,CAAA;AACH,CAAC;AAED,MAAM,eAAe,GAAG;IACtB,GAAG,EAAE,4BAA4B;IACjC,KAAK,EAAE,iBAAiB;IACxB,WAAW,EAAE,EAAE;IACf,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,SAAkB;IAC5B,oBAAoB,EAAE,CAAC,MAAM,CAAC;IAC9B,gBAAgB,EAAE,KAAK;IACvB,cAAc,EAAE,KAAK;IACrB,YAAY,EAAE,CAAC,OAAO,CAAC;IACvB,mBAAmB,EAAE,CAAC,QAAQ,CAAC;CAChC,CAAA;AASD,SAAS,MAAM,CAAC,IAAY;IAC1B,MAAM,OAAO,GAA+E,EAAE,CAAA;IAC9F,MAAM,IAAI,GAAG;QACX,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE;QAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,EAAE;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAA;IACD,MAAM,EAAE,GAAG;QACT,QAAQ,EAAE;YACR,SAAS,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;SACzC;QACD,QAAQ,EAAE;YACR,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;YACjD,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAwC,EAAE,EAAE;gBAC/D,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACvB,OAAO,IAAI,CAAC,IAAI,CAAA;YAClB,CAAC,CAAC;SACH;KACF,CAAA;IACD,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,CAAA;AACxB,CAAC;AAED,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAA;AAE/B,SAAS,CAAC,GAAG,EAAE;IACb,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAA;AACtC,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;SACjE,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,kBAAkB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACrD,MAAM,CAAC,MAAM,kBAAkB,CAAC,EAAE,EAAE,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACxE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,2BAA2B,EAAE,KAAK,IAAI,EAAE;QACzC,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;SACjE,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE,EAAE,CAAC,CAAA;QACnF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;YACrC,QAAQ,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE;YACzE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,yDAAyD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,EAAE,aAAa,EAAE,CAAC,CAAC;YAC/D,QAAQ,EAAE,EAAE,cAAc,EAAE,oBAAoB,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE;YAC/E,OAAO,EAAE,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SACtF,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC1C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2EAA2E,EAAE,KAAK,IAAI,EAAE;QACzF,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE;YACzE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;YACT,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACpC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;YACpC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;YACtC,MAAM,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACzC,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,KAAK,IAAI,EAAE;QAChF,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE;gBACR,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,CAAC,eAAe,CAAC;gBAC3B,MAAM,EAAE,EAAE,kBAAkB,EAAE,EAAE,EAAE,mBAAmB,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE;aACtF;YACD,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;YAC5E,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,GAAG,EAAE,EAAE,CAAC;SACnD,CAAC,CAAA;QACF,MAAM,CAAC,GAAG,MAAM,qBAAqB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAA;QAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,SAAS,OAAO,CAAC,IAAa;QAC5B,OAAO,EAAE,CAAC,EAAE,CACV,KAAK,IAAI,EAAE,CACT,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE;YACjC,MAAM,EAAE,GAAG;YACX,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;SAChD,CAAC,CACsB,CAAA;IAC9B,CAAC;IAED,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;YAC7B,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE;YACzE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,MAAM,SAAS,GAAG,OAAO,CAAC;YACxB,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,gBAAgB,EAAE,EAAE,CAAC;YACrD,KAAK,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE;SACtE,CAAC,CAAA;QAEF,MAAM,CAAC,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE;YAC9C,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,UAAU;YAClB,MAAM,EAAE,IAAI;YACZ,SAAS;SACV,CAAC,CAAA;QAEF,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC;YACT,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;YACrC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACpC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QACxC,CAAC;QACD,MAAM,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAA;QAC1C,mEAAmE;QACnE,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;QAC/B,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACpD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;YAC7B,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,eAAe,CAAC,EAAE;YACzE,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CACrB,KAAK,IAAI,EAAE,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CACvB,CAAA;QAE5B,MAAM,CAAC,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QAC/E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAA;QAChD,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;QACnF,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE;gBACR,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,CAAC,eAAe,CAAC;gBAC3B,UAAU,EAAE;oBACV,OAAO,EAAE,IAAI;oBACb,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,iBAAiB;oBACvB,UAAU,EAAE,YAAY;iBACzB;aACF;YACD,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,IAAI,QAA4E,CAAA;QAChF,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,IAAsB,EAAE,EAAE;YACrE,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChC,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;gBACzC,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;aACnE,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAA;QACH,CAAC,CAA4B,CAAA;QAE7B,MAAM,CAAC,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE;YAC9C,MAAM,EAAE,kBAAkB;YAC1B,MAAM,EAAE,aAAa;YACrB,aAAa,EAAE,IAAI;YACnB,SAAS;SACV,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,MAAM,SAAS,GAAG,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,OAAO,IAAI,EAAE,CAAA;QACpF,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;QAC1C,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;QAC9C,mDAAmD;QACnD,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,aAAa,CAAA;QAC1C,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE;gBACR,cAAc,EAAE,cAAc;gBAC9B,QAAQ,EAAE,CAAC,eAAe,CAAC;gBAC3B,UAAU,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE;aACzF;YACD,OAAO,EAAE,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;SAC7E,CAAC,CAAA;QACF,IAAI,QAA4E,CAAA;QAChF,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,IAAsB,EAAE,EAAE;YACrE,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;YAChC,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;gBACzC,KAAK,EAAE,EAAE,aAAa,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE;aACnE,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAA;QACH,CAAC,CAA4B,CAAA;QAE7B,MAAM,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAA;QACxF,MAAM,SAAS,GAAG,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,OAAO,IAAI,EAAE,CAAA;QACpF,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAA;QACrC,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,CAAA;IAChD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,CAAC;YACpB,SAAS,EAAE,CAAC,IAAI,EAAE,CAAC;YACnB,QAAQ,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,GAAG,eAAe,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE;SACjE,CAAC,CAAA;QACF,MAAM,SAAS,GAAG,EAAE,CAAC,EAAE,EAA6B,CAAA;QACpD,MAAM,CAAC,GAAG,MAAM,sBAAsB,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;QAC5E,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACxB,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAA;QAClD,MAAM,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;IAC1C,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}