@artemiskit/sdk 0.3.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.
@@ -0,0 +1,497 @@
1
+ /**
2
+ * Guardian - Main runtime protection class
3
+ *
4
+ * Provides a unified interface for all guardian features:
5
+ * - Interceptor for LLM client wrapping
6
+ * - Action validation for tool/function calls
7
+ * - Intent classification
8
+ * - Input/output guardrails
9
+ * - Policy-based configuration
10
+ * - Circuit breaker protection
11
+ * - Metrics collection
12
+ */
13
+
14
+ import type { ModelClient } from '@artemiskit/core';
15
+ import { nanoid } from 'nanoid';
16
+ import { ActionValidator, createDefaultActionValidator } from './action-validator';
17
+ import { CircuitBreaker, MetricsCollector, RateLimiter } from './circuit-breaker';
18
+ import { type GuardrailsConfig, createGuardrails } from './guardrails';
19
+ import { type IntentClassifier, createIntentClassifier } from './intent-classifier';
20
+ import {
21
+ GuardianBlockedError,
22
+ GuardianInterceptor,
23
+ type GuardrailFn,
24
+ type InterceptorConfig,
25
+ } from './interceptor';
26
+ import { createDefaultPolicy, loadPolicy, parsePolicy } from './policy';
27
+ import type {
28
+ ActionDefinition,
29
+ GuardianEvent,
30
+ GuardianEventHandler,
31
+ GuardianMetrics,
32
+ GuardianMode,
33
+ GuardianPolicy,
34
+ InterceptedToolCall,
35
+ Violation,
36
+ } from './types';
37
+
38
+ /**
39
+ * Guardian configuration options
40
+ */
41
+ export interface GuardianConfig {
42
+ /** Operating mode */
43
+ mode?: GuardianMode;
44
+ /** Policy file path or policy object */
45
+ policy?: string | GuardianPolicy;
46
+ /** LLM client for intent classification */
47
+ llmClient?: ModelClient;
48
+ /** Enable input validation */
49
+ validateInput?: boolean;
50
+ /** Enable output validation */
51
+ validateOutput?: boolean;
52
+ /** Block on validation failure */
53
+ blockOnFailure?: boolean;
54
+ /** Custom guardrails configuration */
55
+ guardrails?: GuardrailsConfig;
56
+ /** Custom action definitions */
57
+ allowedActions?: ActionDefinition[];
58
+ /** Event handler */
59
+ onEvent?: GuardianEventHandler;
60
+ /** Enable metrics collection */
61
+ collectMetrics?: boolean;
62
+ /** Enable logging */
63
+ enableLogging?: boolean;
64
+ }
65
+
66
+ /**
67
+ * Guardian class - main runtime protection API
68
+ */
69
+ export class Guardian {
70
+ private config: GuardianConfig;
71
+ private policy: GuardianPolicy;
72
+ private circuitBreaker: CircuitBreaker;
73
+ private rateLimiter?: RateLimiter;
74
+ private metricsCollector: MetricsCollector;
75
+ private actionValidator: ActionValidator;
76
+ private intentClassifier: IntentClassifier;
77
+ private inputGuardrails: GuardrailFn[];
78
+ private outputGuardrails: GuardrailFn[];
79
+ private eventHandlers: GuardianEventHandler[] = [];
80
+
81
+ constructor(config: GuardianConfig = {}) {
82
+ this.config = {
83
+ mode: 'guardian',
84
+ validateInput: true,
85
+ validateOutput: true,
86
+ blockOnFailure: true,
87
+ collectMetrics: true,
88
+ enableLogging: true,
89
+ ...config,
90
+ };
91
+
92
+ // Load or create policy
93
+ this.policy = this.loadOrCreatePolicy(config.policy);
94
+
95
+ // Initialize circuit breaker
96
+ this.circuitBreaker = new CircuitBreaker(
97
+ this.policy.circuitBreaker ?? {
98
+ enabled: false,
99
+ threshold: 5,
100
+ windowMs: 60000,
101
+ cooldownMs: 300000,
102
+ }
103
+ );
104
+
105
+ // Initialize rate limiter
106
+ if (this.policy.rateLimits?.enabled) {
107
+ this.rateLimiter = new RateLimiter(this.policy.rateLimits);
108
+ }
109
+
110
+ // Initialize metrics collector
111
+ this.metricsCollector = new MetricsCollector();
112
+
113
+ // Initialize action validator
114
+ this.actionValidator = config.allowedActions
115
+ ? new ActionValidator({ allowedActions: config.allowedActions })
116
+ : createDefaultActionValidator();
117
+
118
+ // Initialize intent classifier
119
+ this.intentClassifier = createIntentClassifier({
120
+ useLLM: !!config.llmClient,
121
+ llmClient: config.llmClient,
122
+ blockHighRisk: true,
123
+ });
124
+
125
+ // Initialize guardrails
126
+ const guardrailConfig = config.guardrails ?? {};
127
+ this.inputGuardrails = createGuardrails(guardrailConfig);
128
+ this.outputGuardrails = createGuardrails(guardrailConfig);
129
+
130
+ // Add intent classifier as guardrail
131
+ this.inputGuardrails.push(this.intentClassifier.asGuardrail());
132
+
133
+ // Register event handler
134
+ if (config.onEvent) {
135
+ this.eventHandlers.push(config.onEvent);
136
+ }
137
+
138
+ // Set up circuit breaker event forwarding
139
+ this.circuitBreaker.onEvent((event, data) => {
140
+ if (event === 'open') {
141
+ this.emit({
142
+ type: 'circuit_breaker_open',
143
+ timestamp: new Date(),
144
+ data: data ?? {},
145
+ });
146
+ } else if (event === 'close') {
147
+ this.emit({
148
+ type: 'circuit_breaker_close',
149
+ timestamp: new Date(),
150
+ data: data ?? {},
151
+ });
152
+ }
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Wrap a model client with guardian protection
158
+ */
159
+ protect(client: ModelClient): GuardianInterceptor {
160
+ const interceptorConfig: InterceptorConfig = {
161
+ validateInput: this.config.validateInput,
162
+ validateOutput: this.config.validateOutput,
163
+ inputGuardrails: this.inputGuardrails,
164
+ outputGuardrails: this.outputGuardrails,
165
+ blockOnFailure: this.config.blockOnFailure,
166
+ logViolations: this.config.enableLogging,
167
+ onEvent: (event) => {
168
+ // Record metrics
169
+ if (event.type === 'request_complete') {
170
+ const latencyMs = event.data.latencyMs as number;
171
+ this.metricsCollector.recordRequest({
172
+ blocked: false,
173
+ warned: false,
174
+ latencyMs,
175
+ violations: [],
176
+ });
177
+ this.circuitBreaker.recordSuccess();
178
+ } else if (event.type === 'violation_detected') {
179
+ const violation = event.data.violation as Violation;
180
+ this.circuitBreaker.recordViolation(violation);
181
+ } else if (event.type === 'request_blocked') {
182
+ const violations = event.data.violations as Violation[];
183
+ const latencyMs = (event.data.latencyMs as number) ?? 0;
184
+ this.metricsCollector.recordRequest({
185
+ blocked: true,
186
+ warned: false,
187
+ latencyMs,
188
+ violations,
189
+ });
190
+ }
191
+
192
+ // Forward to guardian event handlers
193
+ this.emit(event);
194
+ },
195
+ };
196
+
197
+ return new GuardianInterceptor(client, interceptorConfig);
198
+ }
199
+
200
+ /**
201
+ * Validate a tool/function call
202
+ */
203
+ async validateAction(
204
+ toolName: string,
205
+ args: Record<string, unknown>,
206
+ agentId?: string
207
+ ): Promise<{
208
+ valid: boolean;
209
+ violations: Violation[];
210
+ sanitizedArguments?: Record<string, unknown>;
211
+ requiresApproval?: boolean;
212
+ }> {
213
+ // Check circuit breaker
214
+ if (this.circuitBreaker.isOpen()) {
215
+ return {
216
+ valid: false,
217
+ violations: [
218
+ {
219
+ id: nanoid(),
220
+ type: 'rate_limit',
221
+ severity: 'high',
222
+ message: 'Circuit breaker is open - too many violations',
223
+ timestamp: new Date(),
224
+ action: 'block',
225
+ blocked: true,
226
+ },
227
+ ],
228
+ };
229
+ }
230
+
231
+ // Check rate limiter
232
+ if (this.rateLimiter) {
233
+ const rateResult = this.rateLimiter.allowRequest();
234
+ if (!rateResult.allowed) {
235
+ return {
236
+ valid: false,
237
+ violations: [
238
+ {
239
+ id: nanoid(),
240
+ type: 'rate_limit',
241
+ severity: 'medium',
242
+ message: rateResult.reason ?? 'Rate limit exceeded',
243
+ details: { retryAfterMs: rateResult.retryAfterMs },
244
+ timestamp: new Date(),
245
+ action: 'block',
246
+ blocked: true,
247
+ },
248
+ ],
249
+ };
250
+ }
251
+ }
252
+
253
+ const toolCall: InterceptedToolCall = {
254
+ id: nanoid(),
255
+ toolName,
256
+ arguments: args,
257
+ agentId,
258
+ timestamp: new Date(),
259
+ };
260
+
261
+ const result = await this.actionValidator.validate(toolCall);
262
+
263
+ // Record violations
264
+ for (const violation of result.violations) {
265
+ this.circuitBreaker.recordViolation(violation);
266
+ }
267
+
268
+ if (result.valid) {
269
+ this.circuitBreaker.recordSuccess();
270
+ }
271
+
272
+ return result;
273
+ }
274
+
275
+ /**
276
+ * Classify intent of a message
277
+ */
278
+ async classifyIntent(text: string) {
279
+ return this.intentClassifier.classify(text);
280
+ }
281
+
282
+ /**
283
+ * Validate input content
284
+ */
285
+ async validateInput(content: string): Promise<{
286
+ valid: boolean;
287
+ violations: Violation[];
288
+ transformedContent?: string;
289
+ }> {
290
+ const violations: Violation[] = [];
291
+ let transformedContent: string | undefined;
292
+ let currentContent = content;
293
+
294
+ for (const guardrail of this.inputGuardrails) {
295
+ const result = await guardrail(currentContent, {});
296
+
297
+ if (!result.passed) {
298
+ violations.push(...result.violations);
299
+ }
300
+
301
+ if (result.transformedContent) {
302
+ transformedContent = result.transformedContent;
303
+ currentContent = result.transformedContent;
304
+ }
305
+ }
306
+
307
+ // Record violations
308
+ for (const violation of violations) {
309
+ this.circuitBreaker.recordViolation(violation);
310
+ }
311
+
312
+ return {
313
+ valid: violations.length === 0 || !violations.some((v) => v.blocked),
314
+ violations,
315
+ transformedContent,
316
+ };
317
+ }
318
+
319
+ /**
320
+ * Validate output content
321
+ */
322
+ async validateOutput(content: string): Promise<{
323
+ valid: boolean;
324
+ violations: Violation[];
325
+ transformedContent?: string;
326
+ }> {
327
+ const violations: Violation[] = [];
328
+ let transformedContent: string | undefined;
329
+ let currentContent = content;
330
+
331
+ for (const guardrail of this.outputGuardrails) {
332
+ const result = await guardrail(currentContent, {});
333
+
334
+ if (!result.passed) {
335
+ violations.push(...result.violations);
336
+ }
337
+
338
+ if (result.transformedContent) {
339
+ transformedContent = result.transformedContent;
340
+ currentContent = result.transformedContent;
341
+ }
342
+ }
343
+
344
+ // Record violations
345
+ for (const violation of violations) {
346
+ this.circuitBreaker.recordViolation(violation);
347
+ }
348
+
349
+ return {
350
+ valid: violations.length === 0 || !violations.some((v) => v.blocked),
351
+ violations,
352
+ transformedContent,
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Get current metrics
358
+ */
359
+ getMetrics(): GuardianMetrics {
360
+ return this.metricsCollector.getMetrics(this.circuitBreaker.getState());
361
+ }
362
+
363
+ /**
364
+ * Get circuit breaker state
365
+ */
366
+ getCircuitBreakerState(): {
367
+ state: string;
368
+ violationCount: number;
369
+ timeUntilReset: number;
370
+ } {
371
+ return {
372
+ state: this.circuitBreaker.getState(),
373
+ violationCount: this.circuitBreaker.getViolationCount(),
374
+ timeUntilReset: this.circuitBreaker.getTimeUntilReset(),
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Get rate limit status
380
+ */
381
+ getRateLimitStatus(): ReturnType<RateLimiter['getStatus']> | undefined {
382
+ return this.rateLimiter?.getStatus();
383
+ }
384
+
385
+ /**
386
+ * Get the current policy
387
+ */
388
+ getPolicy(): GuardianPolicy {
389
+ return this.policy;
390
+ }
391
+
392
+ /**
393
+ * Update policy at runtime
394
+ */
395
+ updatePolicy(policy: GuardianPolicy): void {
396
+ this.policy = policy;
397
+
398
+ // Update circuit breaker
399
+ if (policy.circuitBreaker) {
400
+ this.circuitBreaker = new CircuitBreaker(policy.circuitBreaker);
401
+ }
402
+
403
+ // Update rate limiter
404
+ if (policy.rateLimits?.enabled) {
405
+ this.rateLimiter = new RateLimiter(policy.rateLimits);
406
+ } else {
407
+ this.rateLimiter = undefined;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Register an allowed action
413
+ */
414
+ registerAction(action: ActionDefinition): void {
415
+ this.actionValidator.registerAction(action);
416
+ }
417
+
418
+ /**
419
+ * Add a custom guardrail
420
+ */
421
+ addInputGuardrail(guardrail: GuardrailFn): void {
422
+ this.inputGuardrails.push(guardrail);
423
+ }
424
+
425
+ /**
426
+ * Add a custom output guardrail
427
+ */
428
+ addOutputGuardrail(guardrail: GuardrailFn): void {
429
+ this.outputGuardrails.push(guardrail);
430
+ }
431
+
432
+ /**
433
+ * Register an event handler
434
+ */
435
+ onEvent(handler: GuardianEventHandler): void {
436
+ this.eventHandlers.push(handler);
437
+ }
438
+
439
+ /**
440
+ * Remove an event handler
441
+ */
442
+ offEvent(handler: GuardianEventHandler): void {
443
+ this.eventHandlers = this.eventHandlers.filter((h) => h !== handler);
444
+ }
445
+
446
+ /**
447
+ * Reset all metrics and state
448
+ */
449
+ reset(): void {
450
+ this.metricsCollector.reset();
451
+ this.circuitBreaker.reset();
452
+ this.rateLimiter?.reset();
453
+ this.actionValidator.clearHistory();
454
+ }
455
+
456
+ /**
457
+ * Load or create policy
458
+ */
459
+ private loadOrCreatePolicy(policyInput?: string | GuardianPolicy): GuardianPolicy {
460
+ if (!policyInput) {
461
+ return createDefaultPolicy();
462
+ }
463
+
464
+ if (typeof policyInput === 'string') {
465
+ // Check if it's a file path or YAML content
466
+ if (policyInput.trim().startsWith('name:') || policyInput.includes('\n')) {
467
+ return parsePolicy(policyInput);
468
+ }
469
+ return loadPolicy(policyInput);
470
+ }
471
+
472
+ return policyInput;
473
+ }
474
+
475
+ /**
476
+ * Emit an event to all handlers
477
+ */
478
+ private emit(event: GuardianEvent): void {
479
+ for (const handler of this.eventHandlers) {
480
+ try {
481
+ handler(event);
482
+ } catch {
483
+ // Ignore handler errors
484
+ }
485
+ }
486
+ }
487
+ }
488
+
489
+ /**
490
+ * Create a guardian instance
491
+ */
492
+ export function createGuardian(config: GuardianConfig = {}): Guardian {
493
+ return new Guardian(config);
494
+ }
495
+
496
+ // Re-export error
497
+ export { GuardianBlockedError };