@constela/ai 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Constela Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,357 @@
1
+ declare const AI_PROVIDERS: readonly ["anthropic", "openai"];
2
+ type AiProviderType = (typeof AI_PROVIDERS)[number];
3
+ declare const AI_OUTPUT_TYPES: readonly ["component", "view", "suggestion"];
4
+ type AiOutputType = (typeof AI_OUTPUT_TYPES)[number];
5
+ interface GenerationContext {
6
+ existingComponents?: string[];
7
+ theme?: Record<string, unknown>;
8
+ schema?: Record<string, unknown>;
9
+ }
10
+ interface SecurityOptions$1 {
11
+ allowedTags?: string[];
12
+ allowedActions?: string[];
13
+ allowedUrlPatterns?: string[];
14
+ maxNestingDepth?: number;
15
+ }
16
+ interface GenerateOptions {
17
+ prompt: string;
18
+ output: AiOutputType;
19
+ context?: GenerationContext;
20
+ security?: SecurityOptions$1;
21
+ }
22
+ interface ProviderGenerateOptions {
23
+ model?: string;
24
+ maxTokens?: number;
25
+ temperature?: number;
26
+ systemPrompt?: string;
27
+ }
28
+ interface ProviderResponse {
29
+ content: string;
30
+ model: string;
31
+ usage?: {
32
+ inputTokens: number;
33
+ outputTokens: number;
34
+ };
35
+ }
36
+ interface AiProvider {
37
+ readonly name: AiProviderType;
38
+ generate(prompt: string, options?: ProviderGenerateOptions): Promise<ProviderResponse>;
39
+ isConfigured(): boolean;
40
+ }
41
+
42
+ type AiErrorCode = 'PROVIDER_NOT_CONFIGURED' | 'PROVIDER_NOT_FOUND' | 'API_ERROR' | 'VALIDATION_ERROR' | 'SECURITY_VIOLATION' | 'RATE_LIMIT_EXCEEDED';
43
+ declare class AiError extends Error {
44
+ readonly code: AiErrorCode;
45
+ constructor(message: string, code: AiErrorCode);
46
+ toJSON(): {
47
+ name: string;
48
+ message: string;
49
+ code: AiErrorCode;
50
+ };
51
+ }
52
+ declare class ValidationError$1 extends AiError {
53
+ readonly violations: string[];
54
+ constructor(message: string, violations: string[]);
55
+ toJSON(): {
56
+ name: string;
57
+ message: string;
58
+ code: AiErrorCode;
59
+ violations: string[];
60
+ };
61
+ }
62
+ declare class SecurityError extends AiError {
63
+ readonly violation: string;
64
+ constructor(message: string, violation: string);
65
+ toJSON(): {
66
+ name: string;
67
+ message: string;
68
+ code: AiErrorCode;
69
+ violation: string;
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Security whitelist definitions for DSL validation
75
+ *
76
+ * Defines forbidden tags and actions that should never appear in AI-generated DSL.
77
+ */
78
+ declare const FORBIDDEN_TAGS: readonly ["script", "iframe", "object", "embed", "form"];
79
+ declare const FORBIDDEN_ACTIONS: readonly ["import", "call", "dom"];
80
+ declare const RESTRICTED_ACTIONS: readonly ["fetch"];
81
+ type ForbiddenTag = (typeof FORBIDDEN_TAGS)[number];
82
+ type ForbiddenAction = (typeof FORBIDDEN_ACTIONS)[number];
83
+ type RestrictedAction = (typeof RESTRICTED_ACTIONS)[number];
84
+ /**
85
+ * Type guard to check if a tag is forbidden.
86
+ * Case-sensitive: only lowercase tags are forbidden.
87
+ */
88
+ declare function isForbiddenTag(tag: unknown): tag is ForbiddenTag;
89
+ /**
90
+ * Type guard to check if an action is forbidden.
91
+ * Case-sensitive: only lowercase actions are forbidden.
92
+ */
93
+ declare function isForbiddenAction(action: unknown): action is ForbiddenAction;
94
+ /**
95
+ * Type guard to check if an action is restricted (requires explicit whitelist).
96
+ * Case-sensitive: only lowercase actions are restricted.
97
+ */
98
+ declare function isRestrictedAction(action: unknown): action is RestrictedAction;
99
+
100
+ /**
101
+ * URL validation utilities for security enforcement
102
+ *
103
+ * Validates URLs to prevent XSS and other injection attacks.
104
+ */
105
+ declare const FORBIDDEN_URL_SCHEMES: readonly ["javascript:", "data:", "vbscript:"];
106
+ type ForbiddenUrlScheme = (typeof FORBIDDEN_URL_SCHEMES)[number];
107
+ interface UrlValidationOptions {
108
+ allowedDomains?: string[];
109
+ allowRelative?: boolean;
110
+ }
111
+ interface UrlValidationResult {
112
+ valid: boolean;
113
+ reason?: string;
114
+ }
115
+ /**
116
+ * Check if a URL uses a forbidden scheme.
117
+ * Case-insensitive for scheme matching.
118
+ */
119
+ declare function isForbiddenScheme(url: string): boolean;
120
+ /**
121
+ * Validate a URL against security rules.
122
+ */
123
+ declare function validateUrl(url: string, options?: UrlValidationOptions): UrlValidationResult;
124
+
125
+ /**
126
+ * DSL Validator
127
+ *
128
+ * Validates AI-generated DSL against security rules to prevent XSS,
129
+ * script injection, and other malicious content.
130
+ */
131
+ /**
132
+ * Security options for validation context
133
+ */
134
+ interface SecurityOptions {
135
+ allowedTags?: string[];
136
+ allowedActions?: string[];
137
+ allowedUrlPatterns?: string[];
138
+ maxNestingDepth?: number;
139
+ }
140
+ /**
141
+ * Validation context for DSL validation
142
+ */
143
+ interface ValidationContext {
144
+ security?: SecurityOptions;
145
+ path?: string;
146
+ }
147
+ /**
148
+ * Validation error with optional path and code
149
+ */
150
+ interface ValidationError {
151
+ message: string;
152
+ path?: string;
153
+ code?: string;
154
+ }
155
+ /**
156
+ * Result of DSL validation
157
+ */
158
+ interface DslValidationResult {
159
+ valid: boolean;
160
+ errors: ValidationError[];
161
+ }
162
+ /**
163
+ * Validate a complete DSL structure
164
+ */
165
+ declare function validateDsl(dsl: unknown, context?: ValidationContext): DslValidationResult;
166
+ /**
167
+ * Validate a single DSL node
168
+ */
169
+ declare function validateNode(node: unknown, context?: ValidationContext): ValidationError[];
170
+ /**
171
+ * Validate an array of actions
172
+ */
173
+ declare function validateActions(actions: unknown, context?: ValidationContext): ValidationError[];
174
+
175
+ declare abstract class BaseProvider implements AiProvider {
176
+ abstract readonly name: AiProviderType;
177
+ abstract generate(prompt: string, options?: ProviderGenerateOptions): Promise<ProviderResponse>;
178
+ abstract isConfigured(): boolean;
179
+ protected validatePrompt(prompt: string): void;
180
+ protected getEnvVar(name: string): string | undefined;
181
+ }
182
+
183
+ interface AnthropicProviderOptions {
184
+ apiKey?: string;
185
+ defaultModel?: string;
186
+ }
187
+ declare class AnthropicProvider extends BaseProvider {
188
+ readonly name: AiProviderType;
189
+ private readonly apiKey;
190
+ private readonly defaultModel;
191
+ constructor(options?: AnthropicProviderOptions);
192
+ isConfigured(): boolean;
193
+ generate(prompt: string, options?: ProviderGenerateOptions): Promise<ProviderResponse>;
194
+ }
195
+ declare function createAnthropicProvider(options?: AnthropicProviderOptions): AnthropicProvider;
196
+
197
+ interface OpenAIProviderOptions {
198
+ apiKey?: string;
199
+ defaultModel?: string;
200
+ }
201
+ declare class OpenAIProvider extends BaseProvider {
202
+ readonly name: AiProviderType;
203
+ private readonly apiKey;
204
+ private readonly defaultModel;
205
+ constructor(options?: OpenAIProviderOptions);
206
+ isConfigured(): boolean;
207
+ generate(prompt: string, options?: ProviderGenerateOptions): Promise<ProviderResponse>;
208
+ }
209
+ declare function createOpenAIProvider(options?: OpenAIProviderOptions): OpenAIProvider;
210
+
211
+ interface ProviderFactory {
212
+ create(type: AiProviderType): AiProvider;
213
+ getAvailable(): readonly AiProviderType[];
214
+ isAvailable(type: AiProviderType): boolean;
215
+ }
216
+ declare function createProviderFactory(): ProviderFactory;
217
+ declare function getProvider(type: AiProviderType): AiProvider;
218
+
219
+ /**
220
+ * Component prompt builder for Constela DSL generation
221
+ */
222
+
223
+ /**
224
+ * Options for building a component prompt
225
+ */
226
+ interface ComponentPromptOptions {
227
+ description: string;
228
+ context?: GenerationContext;
229
+ constraints?: string[];
230
+ }
231
+ /**
232
+ * System prompt for component generation
233
+ */
234
+ declare const COMPONENT_SYSTEM_PROMPT = "You are a Constela DSL component generator.\n\nYour task is to generate valid Constela DSL JSON for UI components based on user descriptions.\n\nOutput Requirements:\n- Return valid JSON representing a Constela DSL component\n- The JSON must have a \"type\" property specifying the component type\n- Include a \"props\" object for component properties\n- Include \"children\" array for nested elements if needed\n- Include \"actions\" array for event handlers if needed\n\nSecurity Rules:\n- Do NOT use forbidden tags: script, iframe, object, embed, form\n- Do NOT use forbidden actions: import, call, dom\n- Keep nesting depth reasonable\n\nAlways output raw JSON, optionally wrapped in a markdown code block.";
235
+ /**
236
+ * Build a user prompt for component generation
237
+ */
238
+ declare function buildComponentPrompt(options: ComponentPromptOptions): string;
239
+
240
+ /**
241
+ * View prompt builder for Constela DSL generation
242
+ */
243
+
244
+ /**
245
+ * Options for building a view prompt
246
+ */
247
+ interface ViewPromptOptions {
248
+ description: string;
249
+ context?: GenerationContext;
250
+ constraints?: string[];
251
+ }
252
+ /**
253
+ * System prompt for view generation
254
+ */
255
+ declare const VIEW_SYSTEM_PROMPT = "You are a Constela DSL view generator.\n\nYour task is to generate valid Constela DSL JSON for complete views, pages, screens, and layouts based on user descriptions.\n\nOutput Requirements:\n- Return valid JSON representing a Constela DSL view\n- The root element should typically be a \"view\" or layout component\n- Include a \"props\" object for view properties (title, layout options, etc.)\n- Include \"children\" array for the view's content structure\n- Include \"actions\" array for page-level event handlers if needed\n- Structure the view with proper layout containers and nested components\n\nSecurity Rules:\n- Do NOT use forbidden tags: script, iframe, object, embed, form\n- Do NOT use forbidden actions: import, call, dom\n- Keep nesting depth reasonable\n\nAlways output raw JSON, optionally wrapped in a markdown code block.";
256
+ /**
257
+ * Build a user prompt for view generation
258
+ */
259
+ declare function buildViewPrompt(options: ViewPromptOptions): string;
260
+
261
+ /**
262
+ * Suggest prompt builder for Constela DSL analysis
263
+ */
264
+ /**
265
+ * Aspects that can be analyzed for suggestions
266
+ */
267
+ type SuggestionAspect = 'accessibility' | 'performance' | 'security' | 'ux';
268
+ /**
269
+ * Severity levels for suggestions
270
+ */
271
+ type SuggestionSeverity = 'low' | 'medium' | 'high';
272
+ /**
273
+ * A suggestion for improving DSL
274
+ */
275
+ interface Suggestion {
276
+ aspect: SuggestionAspect;
277
+ issue: string;
278
+ recommendation: string;
279
+ location?: string;
280
+ severity: SuggestionSeverity;
281
+ }
282
+ /**
283
+ * Options for building a suggest prompt
284
+ */
285
+ interface SuggestPromptOptions {
286
+ dsl: unknown;
287
+ aspect: SuggestionAspect;
288
+ }
289
+ /**
290
+ * System prompt for suggestion generation
291
+ */
292
+ declare const SUGGEST_SYSTEM_PROMPT = "You are a Constela DSL code reviewer.\n\nYour task is to analyze Constela DSL and provide suggestions for improvements.\n\nOutput Requirements:\n- Return a JSON array of suggestion objects\n- Each suggestion must have: aspect, issue, recommendation, severity\n- Optionally include location (e.g., \"children[0].props\")\n- Severity levels: low, medium, high\n- Aspects: accessibility, performance, security, ux\n\nFocus Areas by Aspect:\n- accessibility: ARIA labels, keyboard navigation, screen reader support, color contrast\n- performance: unnecessary nesting, heavy components, render optimization\n- security: dangerous props, untrusted URLs, injection risks\n- ux: usability issues, confusing layouts, missing feedback, touch targets\n\nAlways output a JSON array, optionally wrapped in a markdown code block.";
293
+ /**
294
+ * Build a user prompt for suggestion generation
295
+ */
296
+ declare function buildSuggestPrompt(options: SuggestPromptOptions): string;
297
+ /**
298
+ * Parse AI response into suggestions array
299
+ * Returns empty array for invalid responses
300
+ */
301
+ declare function parseSuggestions(response: string): Suggestion[];
302
+
303
+ /**
304
+ * DSL Generator
305
+ *
306
+ * Main class for generating Constela DSL using AI providers
307
+ */
308
+
309
+ /**
310
+ * Options for creating a DslGenerator
311
+ */
312
+ interface DslGeneratorOptions {
313
+ provider: AiProviderType;
314
+ providerInstance?: AiProvider;
315
+ security?: SecurityOptions$1;
316
+ context?: GenerationContext;
317
+ }
318
+ /**
319
+ * Result of DSL generation
320
+ */
321
+ interface GenerateResult {
322
+ dsl: Record<string, unknown>;
323
+ raw: string;
324
+ validated: boolean;
325
+ errors?: string[];
326
+ }
327
+ /**
328
+ * Main class for generating DSL
329
+ */
330
+ declare class DslGenerator {
331
+ private readonly providerType;
332
+ private readonly provider;
333
+ private readonly security;
334
+ private readonly context;
335
+ constructor(options: DslGeneratorOptions);
336
+ /**
337
+ * Generate DSL from a prompt
338
+ */
339
+ generate(options: GenerateOptions): Promise<GenerateResult>;
340
+ /**
341
+ * Validate DSL against security rules
342
+ */
343
+ validate(dsl: unknown, securityOverrides?: SecurityOptions$1): {
344
+ valid: boolean;
345
+ errors: string[];
346
+ };
347
+ /**
348
+ * Parse AI response and extract JSON
349
+ */
350
+ parseResponse(response: string): Record<string, unknown>;
351
+ }
352
+ /**
353
+ * Factory function to create a DslGenerator
354
+ */
355
+ declare function createDslGenerator(options: DslGeneratorOptions): DslGenerator;
356
+
357
+ export { AI_OUTPUT_TYPES, AI_PROVIDERS, AiError, type AiErrorCode, type AiOutputType, type AiProvider, type AiProviderType, AnthropicProvider, type AnthropicProviderOptions, BaseProvider, COMPONENT_SYSTEM_PROMPT, type ComponentPromptOptions, DslGenerator, type DslGeneratorOptions, type ValidationError as DslValidationError, type DslValidationResult, FORBIDDEN_ACTIONS, FORBIDDEN_TAGS, FORBIDDEN_URL_SCHEMES, type ForbiddenAction, type ForbiddenTag, type ForbiddenUrlScheme, type GenerateOptions, type GenerateResult, type GenerationContext, OpenAIProvider, type OpenAIProviderOptions, type ProviderFactory, type ProviderGenerateOptions, type ProviderResponse, RESTRICTED_ACTIONS, type RestrictedAction, SUGGEST_SYSTEM_PROMPT, SecurityError, type SecurityOptions$1 as SecurityOptions, type SuggestPromptOptions, type Suggestion, type SuggestionAspect, type UrlValidationOptions, type UrlValidationResult, VIEW_SYSTEM_PROMPT, type ValidationContext, ValidationError$1 as ValidationError, type ViewPromptOptions, buildComponentPrompt, buildSuggestPrompt, buildViewPrompt, createAnthropicProvider, createDslGenerator, createOpenAIProvider, createProviderFactory, getProvider, isForbiddenAction, isForbiddenScheme, isForbiddenTag, isRestrictedAction, parseSuggestions, validateActions, validateDsl, validateNode, validateUrl };
package/dist/index.js ADDED
@@ -0,0 +1,840 @@
1
+ // src/types.ts
2
+ var AI_PROVIDERS = Object.freeze(["anthropic", "openai"]);
3
+ var AI_OUTPUT_TYPES = Object.freeze(["component", "view", "suggestion"]);
4
+
5
+ // src/errors.ts
6
+ var AiError = class _AiError extends Error {
7
+ constructor(message, code) {
8
+ super(message);
9
+ this.code = code;
10
+ this.name = "AiError";
11
+ Object.setPrototypeOf(this, _AiError.prototype);
12
+ }
13
+ toJSON() {
14
+ return {
15
+ name: this.name,
16
+ message: this.message,
17
+ code: this.code
18
+ };
19
+ }
20
+ };
21
+ var ValidationError = class _ValidationError extends AiError {
22
+ constructor(message, violations) {
23
+ super(message, "VALIDATION_ERROR");
24
+ this.violations = violations;
25
+ this.name = "ValidationError";
26
+ Object.setPrototypeOf(this, _ValidationError.prototype);
27
+ }
28
+ toJSON() {
29
+ return {
30
+ name: this.name,
31
+ message: this.message,
32
+ code: this.code,
33
+ violations: this.violations
34
+ };
35
+ }
36
+ };
37
+ var SecurityError = class _SecurityError extends AiError {
38
+ constructor(message, violation) {
39
+ super(message, "SECURITY_VIOLATION");
40
+ this.violation = violation;
41
+ this.name = "SecurityError";
42
+ Object.setPrototypeOf(this, _SecurityError.prototype);
43
+ }
44
+ toJSON() {
45
+ return {
46
+ name: this.name,
47
+ message: this.message,
48
+ code: this.code,
49
+ violation: this.violation
50
+ };
51
+ }
52
+ };
53
+
54
+ // src/security/whitelist.ts
55
+ var FORBIDDEN_TAGS = Object.freeze([
56
+ "script",
57
+ "iframe",
58
+ "object",
59
+ "embed",
60
+ "form"
61
+ ]);
62
+ var FORBIDDEN_ACTIONS = Object.freeze([
63
+ "import",
64
+ "call",
65
+ "dom"
66
+ ]);
67
+ var RESTRICTED_ACTIONS = Object.freeze([
68
+ "fetch"
69
+ ]);
70
+ function isForbiddenTag(tag) {
71
+ if (typeof tag !== "string") {
72
+ return false;
73
+ }
74
+ return FORBIDDEN_TAGS.includes(tag);
75
+ }
76
+ function isForbiddenAction(action) {
77
+ if (typeof action !== "string") {
78
+ return false;
79
+ }
80
+ return FORBIDDEN_ACTIONS.includes(action);
81
+ }
82
+ function isRestrictedAction(action) {
83
+ if (typeof action !== "string") {
84
+ return false;
85
+ }
86
+ return RESTRICTED_ACTIONS.includes(action);
87
+ }
88
+
89
+ // src/security/url-validator.ts
90
+ var FORBIDDEN_URL_SCHEMES = Object.freeze([
91
+ "javascript:",
92
+ "data:",
93
+ "vbscript:"
94
+ ]);
95
+ function isForbiddenScheme(url) {
96
+ const trimmedUrl = url.trim().toLowerCase();
97
+ const decodedUrl = tryDecodeUri(trimmedUrl);
98
+ return FORBIDDEN_URL_SCHEMES.some(
99
+ (scheme) => trimmedUrl.startsWith(scheme) || decodedUrl.startsWith(scheme)
100
+ );
101
+ }
102
+ function tryDecodeUri(url) {
103
+ try {
104
+ return decodeURIComponent(url);
105
+ } catch {
106
+ return url;
107
+ }
108
+ }
109
+ function validateUrl(url, options) {
110
+ const trimmedUrl = url.trim();
111
+ if (trimmedUrl === "") {
112
+ return { valid: false, reason: "Empty URL is not allowed" };
113
+ }
114
+ if (isForbiddenScheme(trimmedUrl)) {
115
+ const lowerUrl = trimmedUrl.toLowerCase();
116
+ let scheme = "unknown";
117
+ for (const forbiddenScheme of FORBIDDEN_URL_SCHEMES) {
118
+ if (lowerUrl.startsWith(forbiddenScheme) || tryDecodeUri(lowerUrl).startsWith(forbiddenScheme)) {
119
+ scheme = forbiddenScheme.replace(":", "");
120
+ break;
121
+ }
122
+ }
123
+ return { valid: false, reason: `Forbidden URL scheme: ${scheme}` };
124
+ }
125
+ const isRelative = !trimmedUrl.includes("://") && !trimmedUrl.startsWith("//");
126
+ const allowRelative = options?.allowRelative ?? true;
127
+ if (isRelative) {
128
+ return allowRelative ? { valid: true } : { valid: false, reason: "Relative URLs are not allowed" };
129
+ }
130
+ if (options?.allowedDomains && options.allowedDomains.length > 0) {
131
+ try {
132
+ const urlObj = new URL(trimmedUrl);
133
+ const hostname = urlObj.hostname.toLowerCase();
134
+ const isAllowed = options.allowedDomains.some((domain) => {
135
+ const lowerDomain = domain.toLowerCase();
136
+ return hostname === lowerDomain;
137
+ });
138
+ if (!isAllowed) {
139
+ return { valid: false, reason: `Domain not in allowed list: ${hostname}` };
140
+ }
141
+ } catch {
142
+ return { valid: false, reason: "Invalid URL format" };
143
+ }
144
+ }
145
+ return { valid: true };
146
+ }
147
+
148
+ // src/validator.ts
149
+ var DEFAULT_MAX_NESTING_DEPTH = 32;
150
+ function validateDsl(dsl, context) {
151
+ const errors = [];
152
+ const basePath = context?.path ?? "root";
153
+ if (!isPlainObject(dsl)) {
154
+ errors.push({
155
+ message: "DSL must be a non-null object",
156
+ path: basePath,
157
+ code: "INVALID_DSL"
158
+ });
159
+ return { valid: false, errors };
160
+ }
161
+ const nodeErrors = validateNodeRecursive(
162
+ dsl,
163
+ { ...context, path: basePath },
164
+ 0
165
+ );
166
+ errors.push(...nodeErrors);
167
+ return {
168
+ valid: errors.length === 0,
169
+ errors
170
+ };
171
+ }
172
+ function validateNode(node, context) {
173
+ if (!isPlainObject(node)) {
174
+ return [];
175
+ }
176
+ const errors = [];
177
+ const path = context?.path ?? "node";
178
+ const dslNode = node;
179
+ if (dslNode.type && isForbiddenTag(dslNode.type)) {
180
+ errors.push({
181
+ message: "Forbidden tag: " + dslNode.type,
182
+ path,
183
+ code: "FORBIDDEN_TAG"
184
+ });
185
+ }
186
+ if (context?.security?.allowedTags && context.security.allowedTags.length > 0 && dslNode.type) {
187
+ if (!context.security.allowedTags.includes(dslNode.type)) {
188
+ errors.push({
189
+ message: "Tag not in allowed list: " + dslNode.type,
190
+ path,
191
+ code: "TAG_NOT_ALLOWED"
192
+ });
193
+ }
194
+ }
195
+ if (dslNode.actions) {
196
+ const actionErrors = validateActions(dslNode.actions, {
197
+ ...context,
198
+ path: path + ".actions"
199
+ });
200
+ errors.push(...actionErrors);
201
+ }
202
+ return errors;
203
+ }
204
+ function validateActions(actions, context) {
205
+ if (actions === null || actions === void 0) {
206
+ return [];
207
+ }
208
+ if (!Array.isArray(actions)) {
209
+ return [];
210
+ }
211
+ const errors = [];
212
+ const basePath = context?.path ?? "actions";
213
+ const allowedActions = context?.security?.allowedActions ?? [];
214
+ const allowedUrlPatterns = context?.security?.allowedUrlPatterns ?? [];
215
+ actions.forEach((action, index) => {
216
+ if (!isPlainObject(action)) {
217
+ return;
218
+ }
219
+ const actionPath = basePath + "[" + index + "]";
220
+ const dslAction = action;
221
+ const actionType = dslAction.type;
222
+ if (!actionType) {
223
+ return;
224
+ }
225
+ if (isForbiddenAction(actionType)) {
226
+ errors.push({
227
+ message: "Forbidden action: " + actionType,
228
+ path: actionPath,
229
+ code: "FORBIDDEN_ACTION"
230
+ });
231
+ return;
232
+ }
233
+ if (isRestrictedAction(actionType)) {
234
+ if (!allowedActions.includes(actionType)) {
235
+ errors.push({
236
+ message: "Restricted action requires explicit whitelist: " + actionType,
237
+ path: actionPath,
238
+ code: "RESTRICTED_ACTION"
239
+ });
240
+ return;
241
+ }
242
+ if (actionType === "fetch" && dslAction.payload?.["url"]) {
243
+ const url = String(dslAction.payload["url"]);
244
+ const urlValidation = validateActionUrl(url, allowedUrlPatterns);
245
+ if (!urlValidation.valid) {
246
+ errors.push({
247
+ message: urlValidation.reason ?? "Invalid URL",
248
+ path: actionPath + ".payload.url",
249
+ code: "INVALID_URL"
250
+ });
251
+ }
252
+ }
253
+ }
254
+ if (context?.security?.allowedActions && !isStandardAction(actionType) && !allowedActions.includes(actionType)) {
255
+ errors.push({
256
+ message: "Action not in allowed list: " + actionType,
257
+ path: actionPath,
258
+ code: "ACTION_NOT_ALLOWED"
259
+ });
260
+ }
261
+ if (actionType === "navigate" && dslAction.payload) {
262
+ const url = dslAction.payload["url"] ?? dslAction.payload["path"];
263
+ if (url && typeof url === "string") {
264
+ const urlResult = validateUrl(url);
265
+ if (!urlResult.valid) {
266
+ errors.push({
267
+ message: urlResult.reason ?? "Invalid navigation URL",
268
+ path: actionPath + ".payload.url",
269
+ code: "INVALID_URL"
270
+ });
271
+ }
272
+ }
273
+ }
274
+ });
275
+ return errors;
276
+ }
277
+ var STANDARD_ACTIONS = ["navigate", "setState", "emit", "submit"];
278
+ function isStandardAction(action) {
279
+ return STANDARD_ACTIONS.includes(action);
280
+ }
281
+ function validateNodeRecursive(node, context, depth) {
282
+ const errors = [];
283
+ const path = context.path ?? "root";
284
+ const maxDepth = context.security?.maxNestingDepth ?? DEFAULT_MAX_NESTING_DEPTH;
285
+ if (depth > maxDepth) {
286
+ errors.push({
287
+ message: "Maximum nesting depth exceeded: " + depth + " > " + maxDepth,
288
+ path,
289
+ code: "MAX_DEPTH_EXCEEDED"
290
+ });
291
+ return errors;
292
+ }
293
+ const nodeErrors = validateNode(node, context);
294
+ errors.push(...nodeErrors);
295
+ if (Array.isArray(node.children)) {
296
+ node.children.forEach((child, index) => {
297
+ if (isPlainObject(child)) {
298
+ const childErrors = validateNodeRecursive(
299
+ child,
300
+ { ...context, path: path + ".children[" + index + "]" },
301
+ depth + 1
302
+ );
303
+ errors.push(...childErrors);
304
+ }
305
+ });
306
+ }
307
+ return errors;
308
+ }
309
+ function validateActionUrl(url, allowedPatterns) {
310
+ if (isForbiddenScheme(url)) {
311
+ const scheme = url.toLowerCase().split(":")[0];
312
+ return { valid: false, reason: "Forbidden URL scheme: " + scheme };
313
+ }
314
+ if (allowedPatterns.length === 0) {
315
+ return { valid: true };
316
+ }
317
+ for (const pattern of allowedPatterns) {
318
+ if (matchUrlPattern(url, pattern)) {
319
+ return { valid: true };
320
+ }
321
+ }
322
+ return { valid: false, reason: "URL does not match allowed patterns: " + url };
323
+ }
324
+ function matchUrlPattern(url, pattern) {
325
+ let regexPattern = "";
326
+ for (let i = 0; i < pattern.length; i++) {
327
+ const char = pattern[i];
328
+ if (char === "*") {
329
+ regexPattern += ".*";
330
+ } else if (".+?^${}()|[]\\".includes(char)) {
331
+ regexPattern += "\\" + char;
332
+ } else {
333
+ regexPattern += char;
334
+ }
335
+ }
336
+ const regex = new RegExp("^" + regexPattern + "$");
337
+ return regex.test(url);
338
+ }
339
+ function isPlainObject(value) {
340
+ return typeof value === "object" && value !== null && !Array.isArray(value);
341
+ }
342
+
343
+ // src/providers/anthropic.ts
344
+ import Anthropic from "@anthropic-ai/sdk";
345
+
346
+ // src/providers/base.ts
347
+ var BaseProvider = class {
348
+ validatePrompt(prompt) {
349
+ if (!prompt || typeof prompt !== "string" || prompt.trim().length === 0) {
350
+ throw new AiError("Prompt cannot be empty", "VALIDATION_ERROR");
351
+ }
352
+ }
353
+ getEnvVar(name) {
354
+ return process.env[name];
355
+ }
356
+ };
357
+
358
+ // src/providers/anthropic.ts
359
+ var DEFAULT_MODEL = "claude-sonnet-4-20250514";
360
+ var DEFAULT_MAX_TOKENS = 4096;
361
+ var AnthropicProvider = class extends BaseProvider {
362
+ name = "anthropic";
363
+ apiKey;
364
+ defaultModel;
365
+ constructor(options) {
366
+ super();
367
+ this.apiKey = options?.apiKey ?? this.getEnvVar("ANTHROPIC_API_KEY");
368
+ this.defaultModel = options?.defaultModel ?? DEFAULT_MODEL;
369
+ }
370
+ isConfigured() {
371
+ return Boolean(this.apiKey && this.apiKey.length > 0);
372
+ }
373
+ async generate(prompt, options) {
374
+ if (!this.isConfigured()) {
375
+ throw new AiError(
376
+ "Anthropic provider is not configured. Please set ANTHROPIC_API_KEY environment variable or provide apiKey in options.",
377
+ "PROVIDER_NOT_CONFIGURED"
378
+ );
379
+ }
380
+ this.validatePrompt(prompt);
381
+ try {
382
+ const client = new Anthropic({ apiKey: this.apiKey });
383
+ const params = {
384
+ model: options?.model ?? this.defaultModel,
385
+ max_tokens: options?.maxTokens ?? DEFAULT_MAX_TOKENS,
386
+ messages: [{ role: "user", content: prompt }]
387
+ };
388
+ if (options?.temperature !== void 0) {
389
+ params.temperature = options.temperature;
390
+ }
391
+ if (options?.systemPrompt !== void 0) {
392
+ params.system = options.systemPrompt;
393
+ }
394
+ const response = await client.messages.create(params);
395
+ const textContent = response.content.find((block) => block.type === "text");
396
+ const content = textContent && "text" in textContent ? textContent.text : "";
397
+ const result = {
398
+ content,
399
+ model: response.model
400
+ };
401
+ if (response.usage) {
402
+ result.usage = {
403
+ inputTokens: response.usage.input_tokens,
404
+ outputTokens: response.usage.output_tokens
405
+ };
406
+ }
407
+ return result;
408
+ } catch (error) {
409
+ if (error instanceof AiError) {
410
+ throw error;
411
+ }
412
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
413
+ throw new AiError(`Anthropic API error: ${message}`, "API_ERROR");
414
+ }
415
+ }
416
+ };
417
+ function createAnthropicProvider(options) {
418
+ return new AnthropicProvider(options);
419
+ }
420
+
421
+ // src/providers/openai.ts
422
+ import OpenAI from "openai";
423
+ var DEFAULT_MODEL2 = "gpt-4o";
424
+ var DEFAULT_MAX_TOKENS2 = 4096;
425
+ var OpenAIProvider = class extends BaseProvider {
426
+ name = "openai";
427
+ apiKey;
428
+ defaultModel;
429
+ constructor(options) {
430
+ super();
431
+ this.apiKey = options?.apiKey ?? this.getEnvVar("OPENAI_API_KEY");
432
+ this.defaultModel = options?.defaultModel ?? DEFAULT_MODEL2;
433
+ }
434
+ isConfigured() {
435
+ return Boolean(this.apiKey && this.apiKey.length > 0);
436
+ }
437
+ async generate(prompt, options) {
438
+ if (!this.isConfigured()) {
439
+ throw new AiError(
440
+ "OpenAI provider is not configured. Please set OPENAI_API_KEY environment variable or provide apiKey in options.",
441
+ "PROVIDER_NOT_CONFIGURED"
442
+ );
443
+ }
444
+ this.validatePrompt(prompt);
445
+ try {
446
+ const client = new OpenAI({ apiKey: this.apiKey });
447
+ const messages = [];
448
+ if (options?.systemPrompt) {
449
+ messages.push({ role: "system", content: options.systemPrompt });
450
+ }
451
+ messages.push({ role: "user", content: prompt });
452
+ const params = {
453
+ model: options?.model ?? this.defaultModel,
454
+ max_tokens: options?.maxTokens ?? DEFAULT_MAX_TOKENS2,
455
+ messages
456
+ };
457
+ if (options?.temperature !== void 0) {
458
+ params.temperature = options.temperature;
459
+ }
460
+ const response = await client.chat.completions.create(params);
461
+ const content = response.choices[0]?.message?.content;
462
+ if (content === null || content === void 0) {
463
+ throw new AiError("OpenAI returned empty response", "API_ERROR");
464
+ }
465
+ const result = {
466
+ content,
467
+ model: response.model
468
+ };
469
+ if (response.usage) {
470
+ result.usage = {
471
+ inputTokens: response.usage.prompt_tokens,
472
+ outputTokens: response.usage.completion_tokens
473
+ };
474
+ }
475
+ return result;
476
+ } catch (error) {
477
+ if (error instanceof AiError) {
478
+ throw error;
479
+ }
480
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
481
+ throw new AiError(`OpenAI API error: ${message}`, "API_ERROR");
482
+ }
483
+ }
484
+ };
485
+ function createOpenAIProvider(options) {
486
+ return new OpenAIProvider(options);
487
+ }
488
+
489
+ // src/providers/index.ts
490
+ function createProviderFactory() {
491
+ return {
492
+ create(type) {
493
+ switch (type) {
494
+ case "anthropic":
495
+ return createAnthropicProvider();
496
+ case "openai":
497
+ return createOpenAIProvider();
498
+ default:
499
+ throw new AiError(
500
+ `Unknown provider type: ${type}. Available providers: ${AI_PROVIDERS.join(", ")}`,
501
+ "PROVIDER_NOT_FOUND"
502
+ );
503
+ }
504
+ },
505
+ getAvailable() {
506
+ return AI_PROVIDERS;
507
+ },
508
+ isAvailable(type) {
509
+ try {
510
+ const provider = this.create(type);
511
+ return provider.isConfigured();
512
+ } catch {
513
+ return false;
514
+ }
515
+ }
516
+ };
517
+ }
518
+ function getProvider(type) {
519
+ const factory = createProviderFactory();
520
+ return factory.create(type);
521
+ }
522
+
523
+ // src/prompts/component.ts
524
+ var COMPONENT_SYSTEM_PROMPT = `You are a Constela DSL component generator.
525
+
526
+ Your task is to generate valid Constela DSL JSON for UI components based on user descriptions.
527
+
528
+ Output Requirements:
529
+ - Return valid JSON representing a Constela DSL component
530
+ - The JSON must have a "type" property specifying the component type
531
+ - Include a "props" object for component properties
532
+ - Include "children" array for nested elements if needed
533
+ - Include "actions" array for event handlers if needed
534
+
535
+ Security Rules:
536
+ - Do NOT use forbidden tags: script, iframe, object, embed, form
537
+ - Do NOT use forbidden actions: import, call, dom
538
+ - Keep nesting depth reasonable
539
+
540
+ Always output raw JSON, optionally wrapped in a markdown code block.`;
541
+ function buildComponentPrompt(options) {
542
+ const { description, context, constraints } = options;
543
+ const parts = [];
544
+ parts.push("Create a Constela DSL component based on the following description:\n\n" + description);
545
+ if (context) {
546
+ const contextParts = [];
547
+ if (context.existingComponents && context.existingComponents.length > 0) {
548
+ contextParts.push("Available components: " + context.existingComponents.join(", "));
549
+ }
550
+ if (context.theme && Object.keys(context.theme).length > 0) {
551
+ contextParts.push("Theme: " + JSON.stringify(context.theme));
552
+ }
553
+ if (context.schema && Object.keys(context.schema).length > 0) {
554
+ contextParts.push("Schema: " + JSON.stringify(context.schema));
555
+ }
556
+ if (contextParts.length > 0) {
557
+ parts.push("\nContext:\n" + contextParts.join("\n"));
558
+ }
559
+ }
560
+ if (constraints && constraints.length > 0) {
561
+ parts.push("\nConstraints:\n" + constraints.map((c) => "- " + c).join("\n"));
562
+ }
563
+ return parts.join("\n");
564
+ }
565
+
566
+ // src/prompts/view.ts
567
+ var VIEW_SYSTEM_PROMPT = `You are a Constela DSL view generator.
568
+
569
+ Your task is to generate valid Constela DSL JSON for complete views, pages, screens, and layouts based on user descriptions.
570
+
571
+ Output Requirements:
572
+ - Return valid JSON representing a Constela DSL view
573
+ - The root element should typically be a "view" or layout component
574
+ - Include a "props" object for view properties (title, layout options, etc.)
575
+ - Include "children" array for the view's content structure
576
+ - Include "actions" array for page-level event handlers if needed
577
+ - Structure the view with proper layout containers and nested components
578
+
579
+ Security Rules:
580
+ - Do NOT use forbidden tags: script, iframe, object, embed, form
581
+ - Do NOT use forbidden actions: import, call, dom
582
+ - Keep nesting depth reasonable
583
+
584
+ Always output raw JSON, optionally wrapped in a markdown code block.`;
585
+ function buildViewPrompt(options) {
586
+ const { description, context, constraints } = options;
587
+ const parts = [];
588
+ parts.push("Create a Constela DSL view based on the following description:\n\n" + description);
589
+ if (context) {
590
+ const contextParts = [];
591
+ if (context.existingComponents && context.existingComponents.length > 0) {
592
+ contextParts.push("Available components: " + context.existingComponents.join(", "));
593
+ }
594
+ if (context.theme && Object.keys(context.theme).length > 0) {
595
+ contextParts.push("Theme: " + JSON.stringify(context.theme));
596
+ }
597
+ if (context.schema && Object.keys(context.schema).length > 0) {
598
+ contextParts.push("Schema: " + JSON.stringify(context.schema));
599
+ }
600
+ if (contextParts.length > 0) {
601
+ parts.push("\nContext:\n" + contextParts.join("\n"));
602
+ }
603
+ }
604
+ if (constraints && constraints.length > 0) {
605
+ parts.push("\nConstraints:\n" + constraints.map((c) => "- " + c).join("\n"));
606
+ }
607
+ return parts.join("\n");
608
+ }
609
+
610
+ // src/prompts/suggest.ts
611
+ var VALID_ASPECTS = ["accessibility", "performance", "security", "ux"];
612
+ var VALID_SEVERITIES = ["low", "medium", "high"];
613
+ var SUGGEST_SYSTEM_PROMPT = `You are a Constela DSL code reviewer.
614
+
615
+ Your task is to analyze Constela DSL and provide suggestions for improvements.
616
+
617
+ Output Requirements:
618
+ - Return a JSON array of suggestion objects
619
+ - Each suggestion must have: aspect, issue, recommendation, severity
620
+ - Optionally include location (e.g., "children[0].props")
621
+ - Severity levels: low, medium, high
622
+ - Aspects: accessibility, performance, security, ux
623
+
624
+ Focus Areas by Aspect:
625
+ - accessibility: ARIA labels, keyboard navigation, screen reader support, color contrast
626
+ - performance: unnecessary nesting, heavy components, render optimization
627
+ - security: dangerous props, untrusted URLs, injection risks
628
+ - ux: usability issues, confusing layouts, missing feedback, touch targets
629
+
630
+ Always output a JSON array, optionally wrapped in a markdown code block.`;
631
+ function buildSuggestPrompt(options) {
632
+ const { dsl, aspect } = options;
633
+ const dslString = typeof dsl === "string" ? dsl : JSON.stringify(dsl, null, 2);
634
+ return `Analyze the following Constela DSL for ${aspect} issues and provide suggestions:
635
+
636
+ \`\`\`json
637
+ ${dslString}
638
+ \`\`\`
639
+
640
+ Focus specifically on ${aspect} concerns. Return a JSON array of suggestions.`;
641
+ }
642
+ function extractJson(response) {
643
+ const trimmed = response.trim();
644
+ if (trimmed === "") {
645
+ return null;
646
+ }
647
+ const codeBlockMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
648
+ if (codeBlockMatch?.[1]) {
649
+ return codeBlockMatch[1].trim();
650
+ }
651
+ return trimmed;
652
+ }
653
+ function isValidSuggestion(obj) {
654
+ if (typeof obj !== "object" || obj === null) {
655
+ return false;
656
+ }
657
+ const suggestion = obj;
658
+ if (typeof suggestion["aspect"] !== "string" || typeof suggestion["issue"] !== "string" || typeof suggestion["recommendation"] !== "string" || typeof suggestion["severity"] !== "string") {
659
+ return false;
660
+ }
661
+ if (!VALID_ASPECTS.includes(suggestion["aspect"])) {
662
+ return false;
663
+ }
664
+ if (!VALID_SEVERITIES.includes(suggestion["severity"])) {
665
+ return false;
666
+ }
667
+ if (suggestion["location"] !== void 0 && typeof suggestion["location"] !== "string") {
668
+ return false;
669
+ }
670
+ return true;
671
+ }
672
+ function parseSuggestions(response) {
673
+ const jsonString = extractJson(response);
674
+ if (!jsonString) {
675
+ return [];
676
+ }
677
+ try {
678
+ const parsed = JSON.parse(jsonString);
679
+ if (!Array.isArray(parsed)) {
680
+ return [];
681
+ }
682
+ const validSuggestions = parsed.filter(isValidSuggestion);
683
+ if (parsed.length > 0 && validSuggestions.length === 0) {
684
+ return [];
685
+ }
686
+ return validSuggestions;
687
+ } catch {
688
+ return [];
689
+ }
690
+ }
691
+
692
+ // src/generator.ts
693
+ var DslGenerator = class {
694
+ providerType;
695
+ provider;
696
+ security;
697
+ context;
698
+ constructor(options) {
699
+ this.providerType = options.provider;
700
+ this.provider = options.providerInstance ?? getProvider(options.provider);
701
+ this.security = options.security ?? {};
702
+ this.context = options.context ?? {};
703
+ }
704
+ /**
705
+ * Generate DSL from a prompt
706
+ */
707
+ async generate(options) {
708
+ const { prompt, output, context, security } = options;
709
+ const mergedContext = {
710
+ ...this.context,
711
+ ...context
712
+ };
713
+ const mergedSecurity = {
714
+ ...this.security,
715
+ ...security
716
+ };
717
+ let userPrompt;
718
+ let systemPrompt;
719
+ switch (output) {
720
+ case "component":
721
+ userPrompt = buildComponentPrompt({
722
+ description: prompt,
723
+ context: mergedContext
724
+ });
725
+ systemPrompt = COMPONENT_SYSTEM_PROMPT;
726
+ break;
727
+ case "view":
728
+ userPrompt = buildViewPrompt({
729
+ description: prompt,
730
+ context: mergedContext
731
+ });
732
+ systemPrompt = VIEW_SYSTEM_PROMPT;
733
+ break;
734
+ case "suggestion":
735
+ userPrompt = buildSuggestPrompt({
736
+ dsl: {},
737
+ aspect: "accessibility"
738
+ });
739
+ systemPrompt = SUGGEST_SYSTEM_PROMPT;
740
+ break;
741
+ default:
742
+ userPrompt = prompt;
743
+ systemPrompt = COMPONENT_SYSTEM_PROMPT;
744
+ }
745
+ const response = await this.provider.generate(userPrompt, {
746
+ systemPrompt
747
+ });
748
+ const raw = response.content;
749
+ const dsl = this.parseResponse(raw);
750
+ const validationResult = this.validate(dsl, mergedSecurity);
751
+ if (validationResult.valid) {
752
+ return {
753
+ dsl,
754
+ raw,
755
+ validated: true
756
+ };
757
+ }
758
+ return {
759
+ dsl,
760
+ raw,
761
+ validated: false,
762
+ errors: validationResult.errors
763
+ };
764
+ }
765
+ /**
766
+ * Validate DSL against security rules
767
+ */
768
+ validate(dsl, securityOverrides) {
769
+ const mergedSecurity = {
770
+ ...this.security,
771
+ ...securityOverrides
772
+ };
773
+ const result = validateDsl(dsl, {
774
+ security: mergedSecurity
775
+ });
776
+ return {
777
+ valid: result.valid,
778
+ errors: result.errors.map((e) => e.message)
779
+ };
780
+ }
781
+ /**
782
+ * Parse AI response and extract JSON
783
+ */
784
+ parseResponse(response) {
785
+ const trimmed = response.trim();
786
+ if (trimmed === "") {
787
+ throw new Error("Empty response");
788
+ }
789
+ const codeBlockMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
790
+ const jsonString = codeBlockMatch?.[1]?.trim() ?? trimmed;
791
+ let parsed;
792
+ try {
793
+ parsed = JSON.parse(jsonString);
794
+ } catch {
795
+ throw new Error("Invalid JSON in response");
796
+ }
797
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
798
+ throw new Error("Response must be a JSON object");
799
+ }
800
+ return parsed;
801
+ }
802
+ };
803
+ function createDslGenerator(options) {
804
+ return new DslGenerator(options);
805
+ }
806
+ export {
807
+ AI_OUTPUT_TYPES,
808
+ AI_PROVIDERS,
809
+ AiError,
810
+ AnthropicProvider,
811
+ BaseProvider,
812
+ COMPONENT_SYSTEM_PROMPT,
813
+ DslGenerator,
814
+ FORBIDDEN_ACTIONS,
815
+ FORBIDDEN_TAGS,
816
+ FORBIDDEN_URL_SCHEMES,
817
+ OpenAIProvider,
818
+ RESTRICTED_ACTIONS,
819
+ SUGGEST_SYSTEM_PROMPT,
820
+ SecurityError,
821
+ VIEW_SYSTEM_PROMPT,
822
+ ValidationError,
823
+ buildComponentPrompt,
824
+ buildSuggestPrompt,
825
+ buildViewPrompt,
826
+ createAnthropicProvider,
827
+ createDslGenerator,
828
+ createOpenAIProvider,
829
+ createProviderFactory,
830
+ getProvider,
831
+ isForbiddenAction,
832
+ isForbiddenScheme,
833
+ isForbiddenTag,
834
+ isRestrictedAction,
835
+ parseSuggestions,
836
+ validateActions,
837
+ validateDsl,
838
+ validateNode,
839
+ validateUrl
840
+ };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@constela/ai",
3
+ "version": "1.0.0",
4
+ "description": "AI provider abstraction layer for Constela DSL",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "dependencies": {
18
+ "@anthropic-ai/sdk": "^0.39.0",
19
+ "openai": "^4.96.0"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.10.0",
23
+ "tsup": "^8.0.0",
24
+ "typescript": "^5.3.0",
25
+ "vitest": "^2.0.0"
26
+ },
27
+ "peerDependencies": {
28
+ "@constela/core": "0.16.0"
29
+ },
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ },
33
+ "license": "MIT",
34
+ "scripts": {
35
+ "build": "tsup src/index.ts --format esm --dts --clean",
36
+ "type-check": "tsc --noEmit",
37
+ "test": "vitest run",
38
+ "test:watch": "vitest",
39
+ "clean": "rm -rf dist"
40
+ }
41
+ }