@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.
- package/CHANGELOG.md +134 -0
- package/README.md +173 -0
- package/adapters/openai/dist/index.js +5625 -0
- package/dist/index.js +42577 -0
- package/dist/matchers/index.js +224 -0
- package/dist/matchers/jest.js +257 -0
- package/dist/matchers/vitest.js +257 -0
- package/package.json +78 -0
- package/src/__tests__/artemiskit.test.ts +425 -0
- package/src/__tests__/matchers.test.ts +450 -0
- package/src/artemiskit.ts +791 -0
- package/src/guardian/action-validator.ts +585 -0
- package/src/guardian/circuit-breaker.ts +655 -0
- package/src/guardian/guardian.ts +497 -0
- package/src/guardian/guardrails.ts +536 -0
- package/src/guardian/index.ts +142 -0
- package/src/guardian/intent-classifier.ts +378 -0
- package/src/guardian/interceptor.ts +381 -0
- package/src/guardian/policy.ts +446 -0
- package/src/guardian/types.ts +436 -0
- package/src/index.ts +164 -0
- package/src/matchers/core.ts +315 -0
- package/src/matchers/index.ts +26 -0
- package/src/matchers/jest.ts +112 -0
- package/src/matchers/vitest.ts +84 -0
- package/src/types.ts +259 -0
- package/tsconfig.json +11 -0
|
@@ -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 };
|