@mondaydotcomorg/atp-protocol 0.17.14

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/src/types.ts ADDED
@@ -0,0 +1,547 @@
1
+ import type { ProvenanceMode, SecurityPolicy } from '@mondaydotcomorg/atp-provenance';
2
+ export { ProvenanceMode, type SecurityPolicy } from '@mondaydotcomorg/atp-provenance';
3
+
4
+ /**
5
+ * Callback types that can pause execution
6
+ */
7
+ export enum CallbackType {
8
+ LLM = 'llm',
9
+ APPROVAL = 'approval',
10
+ EMBEDDING = 'embedding',
11
+ TOOL = 'tool',
12
+ }
13
+
14
+ /**
15
+ * Tool callback operations
16
+ */
17
+ export enum ToolOperation {
18
+ CALL = 'call',
19
+ }
20
+
21
+ export interface AgentToolProtocolRequest {
22
+ jsonrpc: '2.0';
23
+ id: string | number;
24
+ method: string;
25
+ params: Record<string, unknown>;
26
+ }
27
+
28
+ export interface AgentToolProtocolResponse {
29
+ jsonrpc: '2.0';
30
+ id: string | number;
31
+ result?: unknown;
32
+ error?: {
33
+ code: number;
34
+ message: string;
35
+ data?: unknown;
36
+ };
37
+ }
38
+
39
+ export interface AgentToolProtocolNotification {
40
+ jsonrpc: '2.0';
41
+ method: string;
42
+ params: Record<string, unknown>;
43
+ }
44
+
45
+ /**
46
+ * Client-provided service availability
47
+ */
48
+ export interface ClientServices {
49
+ /** Whether client provides LLM implementation */
50
+ hasLLM: boolean;
51
+ /** Whether client provides approval handler */
52
+ hasApproval: boolean;
53
+ /** Whether client provides embedding model */
54
+ hasEmbedding: boolean;
55
+ /** Whether client provides custom tools */
56
+ hasTools: boolean;
57
+ /** Names of client-provided tools (for discovery) */
58
+ toolNames?: string[];
59
+ }
60
+
61
+ /**
62
+ * Client-provided LLM handler
63
+ */
64
+ export interface ClientLLMHandler {
65
+ call: (
66
+ prompt: string,
67
+ options?: {
68
+ context?: Record<string, unknown>;
69
+ model?: string;
70
+ temperature?: number;
71
+ systemPrompt?: string;
72
+ }
73
+ ) => Promise<string>;
74
+ extract?: <T>(
75
+ prompt: string,
76
+ schema: Record<string, unknown>,
77
+ options?: {
78
+ context?: Record<string, unknown>;
79
+ }
80
+ ) => Promise<T>;
81
+ classify?: (
82
+ text: string,
83
+ categories: string[],
84
+ options?: {
85
+ context?: Record<string, unknown>;
86
+ }
87
+ ) => Promise<string>;
88
+ }
89
+
90
+ /**
91
+ * Client-provided approval handler
92
+ */
93
+ export interface ClientApprovalHandler {
94
+ request: (
95
+ message: string,
96
+ context?: Record<string, unknown>
97
+ ) => Promise<{
98
+ approved: boolean;
99
+ response?: unknown;
100
+ timestamp: number;
101
+ }>;
102
+ }
103
+
104
+ /**
105
+ * Client-provided embedding handler
106
+ */
107
+ export interface ClientEmbeddingHandler {
108
+ embed: (text: string) => Promise<number[]>;
109
+ similarity?: (text1: string, text2: string) => Promise<number>;
110
+ }
111
+
112
+ /**
113
+ * Client-provided tool handler
114
+ * Function that executes on the client side when a client tool is invoked
115
+ */
116
+ export interface ClientToolHandler {
117
+ (input: unknown): Promise<unknown>;
118
+ }
119
+
120
+ /**
121
+ * Client tool definition (metadata sent to server)
122
+ * The actual handler function remains on the client side
123
+ */
124
+ export interface ClientToolDefinition {
125
+ /** Tool name (unique per client session) */
126
+ name: string;
127
+ /** API namespace (e.g., 'playwright', 'browser'). Defaults to 'client' if not specified */
128
+ namespace?: string;
129
+ /** Human-readable description of what the tool does */
130
+ description: string;
131
+ /** JSON Schema for tool input validation */
132
+ inputSchema: JSONSchema;
133
+ /** JSON Schema for tool output (optional, for documentation) */
134
+ outputSchema?: JSONSchema;
135
+ /** Tool metadata for security and risk management */
136
+ metadata?: ToolMetadata;
137
+ /** Whether this tool supports parallel execution with other tools */
138
+ supportsConcurrency?: boolean;
139
+ /** Keywords for search/discovery (optional) */
140
+ keywords?: string[];
141
+ }
142
+
143
+ /**
144
+ * Client tool with handler (used client-side only)
145
+ * Extends ClientToolDefinition to include the actual handler function
146
+ */
147
+ export interface ClientTool extends ClientToolDefinition {
148
+ /** Handler function that executes on client side */
149
+ handler: ClientToolHandler;
150
+ }
151
+
152
+ /**
153
+ * Tool operation type classification
154
+ */
155
+ export enum ToolOperationType {
156
+ /** Safe read-only operations */
157
+ READ = 'read',
158
+ /** Operations that modify data */
159
+ WRITE = 'write',
160
+ /** Operations that delete or destroy data */
161
+ DESTRUCTIVE = 'destructive',
162
+ }
163
+
164
+ /**
165
+ * Tool sensitivity level
166
+ */
167
+ export enum ToolSensitivityLevel {
168
+ /** Public data, no sensitivity concerns */
169
+ PUBLIC = 'public',
170
+ /** Internal data, requires authentication */
171
+ INTERNAL = 'internal',
172
+ /** Sensitive data (PII, financial data, etc.) */
173
+ SENSITIVE = 'sensitive',
174
+ }
175
+
176
+ /**
177
+ * Client-side tool execution rules
178
+ * Allows clients to control which tools can be executed and under what conditions
179
+ */
180
+ export interface ClientToolRules {
181
+ /** Block all tools of specific operation types */
182
+ blockOperationTypes?: ToolOperationType[];
183
+ /** Block all tools with specific sensitivity levels */
184
+ blockSensitivityLevels?: ToolSensitivityLevel[];
185
+ /** Require approval for specific operation types */
186
+ requireApprovalForOperationTypes?: ToolOperationType[];
187
+ /** Require approval for specific sensitivity levels */
188
+ requireApprovalForSensitivityLevels?: ToolSensitivityLevel[];
189
+ /** Block specific tools by name (e.g., ['deleteDatabase', 'dropTable']) */
190
+ blockTools?: string[];
191
+ /** Allow only specific tools by name (whitelist mode) */
192
+ allowOnlyTools?: string[];
193
+ /** Block entire API groups (e.g., ['admin', 'system']) */
194
+ blockApiGroups?: string[];
195
+ /** Allow only specific API groups (whitelist mode) */
196
+ allowOnlyApiGroups?: string[];
197
+ }
198
+
199
+ /**
200
+ * Tool/API metadata for security and risk management
201
+ *
202
+ * What can clients do with these annotations?
203
+ *
204
+ * 1. **Block Execution**:
205
+ * - Block all WRITE operations: blockOperationTypes: [ToolOperationType.WRITE]
206
+ * - Block all DESTRUCTIVE operations: blockOperationTypes: [ToolOperationType.DESTRUCTIVE]
207
+ * - Block SENSITIVE data access: blockSensitivityLevels: [ToolSensitivityLevel.SENSITIVE]
208
+ *
209
+ * 2. **Require Approval**:
210
+ * - Require approval for WRITE: requireApprovalForOperationTypes: [ToolOperationType.WRITE]
211
+ * - Require approval for DESTRUCTIVE: requireApprovalForOperationTypes: [ToolOperationType.DESTRUCTIVE]
212
+ * - Require approval for SENSITIVE: requireApprovalForSensitivityLevels: [ToolSensitivityLevel.SENSITIVE]
213
+ *
214
+ * 3. **Whitelist/Blacklist**:
215
+ * - Block specific tools: blockTools: ['deleteDatabase', 'dropTable']
216
+ * - Allow only safe tools: allowOnlyTools: ['getUser', 'listItems']
217
+ * - Block admin APIs: blockApiGroups: ['admin', 'system']
218
+ *
219
+ * 4. **Audit & Logging**:
220
+ * - Log all DESTRUCTIVE operations
221
+ * - Track access to SENSITIVE data
222
+ * - Monitor WRITE operations
223
+ *
224
+ * Granularity levels:
225
+ * - Operation Type: READ, WRITE, DESTRUCTIVE (coarse-grained)
226
+ * - Sensitivity Level: PUBLIC, INTERNAL, SENSITIVE (data classification)
227
+ * - Tool Name: Specific function names (fine-grained)
228
+ * - API Group: Entire namespaces (medium-grained)
229
+ */
230
+ export interface ToolMetadata {
231
+ /** Operation type classification */
232
+ operationType?: ToolOperationType;
233
+ /** Sensitivity level of data handled */
234
+ sensitivityLevel?: ToolSensitivityLevel;
235
+ /** Require explicit approval before execution (server-side enforcement) */
236
+ requiresApproval?: boolean;
237
+ /** Category for grouping/filtering (e.g., 'database', 'user-management') */
238
+ category?: string;
239
+ /** Additional tags for classification */
240
+ tags?: string[];
241
+ /** Human-readable description of potential impact */
242
+ impactDescription?: string;
243
+ /**
244
+ * Required OAuth scopes to use this tool
245
+ * Used for scope-based filtering when user credentials have limited permissions
246
+ * @example ['repo', 'read:user'] for GitHub
247
+ * @example ['https://www.googleapis.com/auth/calendar'] for Google
248
+ */
249
+ requiredScopes?: string[];
250
+ /**
251
+ * Generic permissions required (for non-OAuth providers)
252
+ * @example ['admin', 'write:users']
253
+ */
254
+ requiredPermissions?: string[];
255
+ }
256
+
257
+ /**
258
+ * Client service providers
259
+ */
260
+ export interface ClientServiceProviders {
261
+ llm?: ClientLLMHandler;
262
+ approval?: ClientApprovalHandler;
263
+ embedding?: ClientEmbeddingHandler;
264
+ /** Client-provided tools that execute locally */
265
+ tools?: ClientTool[];
266
+ }
267
+
268
+ export interface ExecutionConfig {
269
+ timeout: number;
270
+ maxMemory: number;
271
+ maxLLMCalls: number;
272
+ allowedAPIs: string[];
273
+ allowLLMCalls: boolean;
274
+ progressCallback?: (message: string, fraction: number) => void;
275
+ customLLMHandler?: (prompt: string, options?: any) => Promise<string>;
276
+ clientServices?: ClientServices;
277
+ provenanceMode?: ProvenanceMode;
278
+ securityPolicies?: SecurityPolicy[];
279
+ provenanceHints?: string[];
280
+ }
281
+
282
+ /**
283
+ * Execution status codes for fine-grained error reporting
284
+ */
285
+ export enum ExecutionStatus {
286
+ COMPLETED = 'completed',
287
+ FAILED = 'failed',
288
+ TIMEOUT = 'timeout',
289
+ CANCELLED = 'cancelled',
290
+ PAUSED = 'paused',
291
+ MEMORY_EXCEEDED = 'memory_exceeded',
292
+ LLM_CALLS_EXCEEDED = 'llm_calls_exceeded',
293
+ SECURITY_VIOLATION = 'security_violation',
294
+ VALIDATION_FAILED = 'validation_failed',
295
+ LOOP_DETECTED = 'loop_detected',
296
+ RATE_LIMITED = 'rate_limited',
297
+ NETWORK_ERROR = 'network_error',
298
+ PARSE_ERROR = 'parse_error',
299
+ }
300
+
301
+ /**
302
+ * Execution error codes for categorizing failures
303
+ */
304
+ export enum ExecutionErrorCode {
305
+ UNKNOWN_ERROR = 'UNKNOWN_ERROR',
306
+ EXECUTION_FAILED = 'EXECUTION_FAILED',
307
+ TIMEOUT_ERROR = 'TIMEOUT_ERROR',
308
+
309
+ MEMORY_LIMIT_EXCEEDED = 'MEMORY_LIMIT_EXCEEDED',
310
+ LLM_CALL_LIMIT_EXCEEDED = 'LLM_CALL_LIMIT_EXCEEDED',
311
+ HTTP_CALL_LIMIT_EXCEEDED = 'HTTP_CALL_LIMIT_EXCEEDED',
312
+
313
+ SECURITY_VIOLATION = 'SECURITY_VIOLATION',
314
+ VALIDATION_FAILED = 'VALIDATION_FAILED',
315
+ FORBIDDEN_OPERATION = 'FORBIDDEN_OPERATION',
316
+
317
+ PARSE_ERROR = 'PARSE_ERROR',
318
+ SYNTAX_ERROR = 'SYNTAX_ERROR',
319
+ TYPE_ERROR = 'TYPE_ERROR',
320
+ REFERENCE_ERROR = 'REFERENCE_ERROR',
321
+
322
+ INFINITE_LOOP_DETECTED = 'INFINITE_LOOP_DETECTED',
323
+ LOOP_TIMEOUT = 'LOOP_TIMEOUT',
324
+
325
+ NETWORK_ERROR = 'NETWORK_ERROR',
326
+ HTTP_ERROR = 'HTTP_ERROR',
327
+ DNS_ERROR = 'DNS_ERROR',
328
+
329
+ RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
330
+ CONCURRENT_LIMIT_EXCEEDED = 'CONCURRENT_LIMIT_EXCEEDED',
331
+ }
332
+
333
+ export interface ExecutionResult {
334
+ executionId: string;
335
+ status: ExecutionStatus;
336
+ result?: unknown;
337
+ error?: {
338
+ message: string;
339
+ code?: ExecutionErrorCode;
340
+ stack?: string;
341
+ line?: number;
342
+ context?: Record<string, unknown>;
343
+ retryable?: boolean;
344
+ suggestion?: string;
345
+ };
346
+ stats: {
347
+ duration: number;
348
+ memoryUsed: number;
349
+ llmCallsCount: number;
350
+ approvalCallsCount: number;
351
+ statementsExecuted?: number;
352
+ statementsCached?: number;
353
+ };
354
+ needsCallback?: {
355
+ type: CallbackType;
356
+ operation: string;
357
+ payload: Record<string, unknown>;
358
+ };
359
+ needsCallbacks?: BatchCallbackRequest[];
360
+ callbackHistory?: Array<{
361
+ type: CallbackType;
362
+ operation: string;
363
+ payload: Record<string, unknown>;
364
+ result?: unknown;
365
+ timestamp: number;
366
+ sequenceNumber: number;
367
+ }>;
368
+ transformedCode?: string;
369
+ provenanceSnapshot?: unknown;
370
+ provenanceTokens?: Array<{
371
+ path: string;
372
+ token: string;
373
+ }>;
374
+ }
375
+
376
+ /**
377
+ * Batch callback request for parallel execution
378
+ */
379
+ export interface BatchCallbackRequest {
380
+ /** Unique callback ID */
381
+ id: string;
382
+ /** Callback type */
383
+ type: CallbackType;
384
+ /** Operation name */
385
+ operation: string;
386
+ /** Operation payload */
387
+ payload: Record<string, unknown>;
388
+ }
389
+
390
+ /**
391
+ * Batch callback result from client
392
+ */
393
+ export interface BatchCallbackResult {
394
+ /** Callback ID (matches BatchCallbackRequest.id) */
395
+ id: string;
396
+ /** Callback result */
397
+ result: unknown;
398
+ }
399
+
400
+ export interface SearchOptions {
401
+ query: string;
402
+ apiGroups?: string[];
403
+ maxResults?: number;
404
+ useEmbeddings?: boolean;
405
+ embeddingModel?: string;
406
+ }
407
+
408
+ export interface SearchResult {
409
+ apiGroup: string;
410
+ functionName: string;
411
+ description: string;
412
+ signature: string;
413
+ example?: string;
414
+ relevanceScore: number;
415
+ }
416
+
417
+ export interface ExploreRequest {
418
+ path: string;
419
+ }
420
+
421
+ export interface ExploreDirectoryResult {
422
+ type: 'directory';
423
+ path: string;
424
+ items: Array<{ name: string; type: 'directory' | 'function' }>;
425
+ }
426
+
427
+ export interface ExploreFunctionResult {
428
+ type: 'function';
429
+ path: string;
430
+ name: string;
431
+ description: string;
432
+ definition: string;
433
+ group: string;
434
+ }
435
+
436
+ export type ExploreResult = ExploreDirectoryResult | ExploreFunctionResult;
437
+
438
+ export interface ValidationResult {
439
+ valid: boolean;
440
+ errors?: ValidationError[];
441
+ warnings?: ValidationError[];
442
+ securityIssues?: SecurityIssue[];
443
+ }
444
+
445
+ export interface ValidationError {
446
+ line: number;
447
+ message: string;
448
+ severity: 'error' | 'warning';
449
+ }
450
+
451
+ export interface SecurityIssue {
452
+ line: number;
453
+ issue: string;
454
+ risk: 'low' | 'medium' | 'high';
455
+ }
456
+
457
+ export interface APISource {
458
+ type: 'mcp' | 'openapi' | 'custom';
459
+ name: string;
460
+ url?: string;
461
+ spec?: unknown;
462
+ }
463
+
464
+ export interface ServerConfig {
465
+ apiGroups: APIGroupConfig[];
466
+ security: SecurityConfig;
467
+ execution: ExecutionLimits;
468
+ search: SearchConfig;
469
+ logging: LoggingConfig;
470
+ }
471
+
472
+ export interface APIGroupConfig {
473
+ name: string;
474
+ type: 'mcp' | 'openapi' | 'custom';
475
+ url?: string;
476
+ spec?: unknown;
477
+ functions?: CustomFunctionDef[];
478
+ /** Authentication configuration for this API group */
479
+ auth?: import('./auth.js').AuthConfig;
480
+ }
481
+
482
+ export interface SecurityConfig {
483
+ allowedOrigins: string[];
484
+ apiKeyRequired: boolean;
485
+ rateLimits: {
486
+ requestsPerMinute: number;
487
+ executionsPerHour: number;
488
+ };
489
+ }
490
+
491
+ export interface ExecutionLimits {
492
+ defaultTimeout: number;
493
+ maxTimeout: number;
494
+ defaultMemoryLimit: number;
495
+ maxMemoryLimit: number;
496
+ defaultLLMCallLimit: number;
497
+ maxLLMCallLimit: number;
498
+ }
499
+
500
+ export interface SearchConfig {
501
+ enableEmbeddings: boolean;
502
+ embeddingProvider?: 'openai' | 'cohere' | 'custom';
503
+ customSearcher?: (query: string) => Promise<SearchResult[]>;
504
+ }
505
+
506
+ export interface LoggingConfig {
507
+ level: 'debug' | 'info' | 'warn' | 'error';
508
+ destination: 'console' | 'file' | 'remote';
509
+ auditEnabled: boolean;
510
+ }
511
+
512
+ export interface CustomFunctionDef {
513
+ name: string;
514
+ description: string;
515
+ inputSchema: JSONSchema;
516
+ outputSchema?: JSONSchema;
517
+ handler: (params: unknown) => Promise<unknown>;
518
+ keywords?: string[];
519
+ metadata?: ToolMetadata; // NEW: Tool metadata for security
520
+ requiredScopes?: string[]; // OAuth scopes required to use this function
521
+ auth?: {
522
+ source?: 'server' | 'user';
523
+ oauthProvider?: string;
524
+ };
525
+ }
526
+
527
+ export interface JSONSchema {
528
+ type: string;
529
+ properties?: Record<string, unknown>;
530
+ required?: string[];
531
+ [key: string]: unknown;
532
+ }
533
+
534
+ export interface ClientConfig {
535
+ serverUrl: string;
536
+ apiKey: string;
537
+ timeout?: number;
538
+ llmProvider: 'anthropic' | 'openai' | 'custom';
539
+ llmModel?: string;
540
+ temperature?: number;
541
+ defaultExecutionConfig?: Partial<ExecutionConfig>;
542
+ searchPreferences?: {
543
+ useEmbeddings?: boolean;
544
+ embeddingModel?: string;
545
+ maxResults?: number;
546
+ };
547
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Input validation utilities for ExecutionConfig and other types
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import type { ExecutionConfig } from './types.js';
7
+
8
+ /**
9
+ * Maximum allowed code size (1MB)
10
+ */
11
+ export const MAX_CODE_SIZE = 1000000;
12
+
13
+ export class ConfigValidationError extends Error {
14
+ constructor(
15
+ message: string,
16
+ public readonly field: string,
17
+ public readonly value: unknown
18
+ ) {
19
+ super(message);
20
+ this.name = 'ConfigValidationError';
21
+ }
22
+ }
23
+
24
+ export class SecurityViolationError extends Error {
25
+ constructor(
26
+ message: string,
27
+ public readonly violations: string[]
28
+ ) {
29
+ super(message);
30
+ this.name = 'SecurityViolationError';
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Sanitizes input string by removing control characters and normalizing whitespace
36
+ */
37
+ export function sanitizeInput(input: string, maxLength = MAX_CODE_SIZE): string {
38
+ if (typeof input !== 'string') {
39
+ return '';
40
+ }
41
+
42
+ let sanitized = input.replace(/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, '');
43
+
44
+ sanitized = sanitized.replace(/[\u200B-\u200D\uFEFF]/g, '');
45
+
46
+ sanitized = sanitized.replace(/\n{10,}/g, '\n\n\n');
47
+
48
+ if (sanitized.length > maxLength) {
49
+ sanitized = sanitized.substring(0, maxLength);
50
+ }
51
+
52
+ return sanitized;
53
+ }
54
+
55
+ /**
56
+ * Frames user code in a secure execution context to prevent injection attacks
57
+ * Similar to SQL parameterized queries - treats user code as data within a safe boundary
58
+ */
59
+ export function frameCodeExecution(userCode: string): string {
60
+ const cleaned = sanitizeInput(userCode);
61
+
62
+ return `
63
+ (async function __user_code_context__() {
64
+ "use strict";
65
+ ${cleaned}
66
+ })();
67
+ `.trim();
68
+ }
69
+
70
+ /**
71
+ * Zod schema for ExecutionConfig validation
72
+ */
73
+ export const executionConfigSchema = z.object({
74
+ timeout: z
75
+ .number({
76
+ invalid_type_error: 'timeout must be a number',
77
+ })
78
+ .positive('timeout must be positive')
79
+ .max(300000, 'timeout cannot exceed 300000ms (5 minutes)')
80
+ .optional(),
81
+
82
+ maxMemory: z
83
+ .number({
84
+ invalid_type_error: 'maxMemory must be a number',
85
+ })
86
+ .positive('maxMemory must be positive')
87
+ .max(512 * 1024 * 1024, 'maxMemory cannot exceed 512MB')
88
+ .optional(),
89
+
90
+ maxLLMCalls: z
91
+ .number({
92
+ invalid_type_error: 'maxLLMCalls must be a number',
93
+ })
94
+ .nonnegative('maxLLMCalls cannot be negative')
95
+ .max(1000, 'maxLLMCalls cannot exceed 1000')
96
+ .optional(),
97
+
98
+ allowedAPIs: z
99
+ .array(
100
+ z.string().refine((val) => val.trim().length > 0, {
101
+ message: 'allowedAPIs must contain non-empty strings',
102
+ })
103
+ )
104
+ .optional(),
105
+
106
+ allowLLMCalls: z
107
+ .boolean({
108
+ invalid_type_error: 'allowLLMCalls must be a boolean',
109
+ })
110
+ .optional(),
111
+
112
+ progressCallback: z.function().optional(),
113
+ customLLMHandler: z.function().optional(),
114
+ clientServices: z.any().optional(),
115
+ provenanceMode: z.any().optional(),
116
+ securityPolicies: z.array(z.any()).optional(),
117
+ provenanceHints: z.array(z.string()).optional(),
118
+ });
119
+
120
+ /**
121
+ * Validates ExecutionConfig parameters using Zod
122
+ */
123
+ export function validateExecutionConfig(config: Partial<ExecutionConfig>): void {
124
+ try {
125
+ executionConfigSchema.parse(config);
126
+ } catch (error) {
127
+ if (error instanceof z.ZodError) {
128
+ const errors = error.errors.map((err) => err.message);
129
+ throw new ConfigValidationError(
130
+ `Invalid ExecutionConfig: ${errors.join(', ')}`,
131
+ 'ExecutionConfig',
132
+ config
133
+ );
134
+ }
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Validates client ID format
141
+ */
142
+ export function validateClientId(clientId: string): void {
143
+ if (typeof clientId !== 'string') {
144
+ throw new ConfigValidationError('clientId must be a string', 'clientId', clientId);
145
+ }
146
+
147
+ if (clientId.trim().length === 0) {
148
+ throw new ConfigValidationError('clientId cannot be empty', 'clientId', clientId);
149
+ }
150
+
151
+ if (clientId.length > 256) {
152
+ throw new ConfigValidationError('clientId cannot exceed 256 characters', 'clientId', clientId);
153
+ }
154
+
155
+ if (!/^[a-zA-Z0-9_-]+$/.test(clientId)) {
156
+ throw new ConfigValidationError(
157
+ 'clientId can only contain alphanumeric characters, dashes, and underscores',
158
+ 'clientId',
159
+ clientId
160
+ );
161
+ }
162
+ }