@vaikora/sdk 0.2.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/README.md +403 -0
- package/dist/index.d.mts +1106 -0
- package/dist/index.d.ts +1106 -0
- package/dist/index.js +1780 -0
- package/dist/index.mjs +1693 -0
- package/package.json +69 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1693 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import axios, { AxiosError } from "axios";
|
|
3
|
+
import axiosRetry from "axios-retry";
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var VaikoraError = class extends Error {
|
|
7
|
+
statusCode;
|
|
8
|
+
responseData;
|
|
9
|
+
constructor(message, statusCode, responseData) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "VaikoraError";
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.responseData = responseData;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var AuthenticationError = class extends VaikoraError {
|
|
17
|
+
constructor(message, statusCode, responseData) {
|
|
18
|
+
super(message, statusCode, responseData);
|
|
19
|
+
this.name = "AuthenticationError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var AuthorizationError = class extends VaikoraError {
|
|
23
|
+
constructor(message, statusCode, responseData) {
|
|
24
|
+
super(message, statusCode, responseData);
|
|
25
|
+
this.name = "AuthorizationError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var RateLimitError = class extends VaikoraError {
|
|
29
|
+
retryAfter;
|
|
30
|
+
constructor(message, retryAfter, statusCode, responseData) {
|
|
31
|
+
super(message, statusCode, responseData);
|
|
32
|
+
this.name = "RateLimitError";
|
|
33
|
+
this.retryAfter = retryAfter;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var PolicyViolationError = class extends VaikoraError {
|
|
37
|
+
policyId;
|
|
38
|
+
policyName;
|
|
39
|
+
constructor(message, policyId, policyName, statusCode, responseData) {
|
|
40
|
+
super(message, statusCode, responseData);
|
|
41
|
+
this.name = "PolicyViolationError";
|
|
42
|
+
this.policyId = policyId;
|
|
43
|
+
this.policyName = policyName;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var NetworkError = class extends VaikoraError {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "NetworkError";
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var TimeoutError = class extends NetworkError {
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = "TimeoutError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
var ValidationError = class extends VaikoraError {
|
|
59
|
+
errors;
|
|
60
|
+
constructor(message, errors, statusCode, responseData) {
|
|
61
|
+
super(message, statusCode, responseData);
|
|
62
|
+
this.name = "ValidationError";
|
|
63
|
+
this.errors = errors;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var NotFoundError = class extends VaikoraError {
|
|
67
|
+
constructor(message, statusCode, responseData) {
|
|
68
|
+
super(message, statusCode, responseData);
|
|
69
|
+
this.name = "NotFoundError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var ConflictError = class extends VaikoraError {
|
|
73
|
+
constructor(message, statusCode, responseData) {
|
|
74
|
+
super(message, statusCode, responseData);
|
|
75
|
+
this.name = "ConflictError";
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
var ServerError = class extends VaikoraError {
|
|
79
|
+
constructor(message, statusCode, responseData) {
|
|
80
|
+
super(message, statusCode, responseData);
|
|
81
|
+
this.name = "ServerError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function raiseForStatus(statusCode, responseData) {
|
|
85
|
+
const message = responseData.detail || responseData.message || "Unknown error";
|
|
86
|
+
if (statusCode === 401) {
|
|
87
|
+
throw new AuthenticationError(message, statusCode, responseData);
|
|
88
|
+
} else if (statusCode === 403) {
|
|
89
|
+
throw new AuthorizationError(message, statusCode, responseData);
|
|
90
|
+
} else if (statusCode === 404) {
|
|
91
|
+
throw new NotFoundError(message, statusCode, responseData);
|
|
92
|
+
} else if (statusCode === 409) {
|
|
93
|
+
throw new ConflictError(message, statusCode, responseData);
|
|
94
|
+
} else if (statusCode === 422) {
|
|
95
|
+
const errors = responseData.errors || responseData.detail;
|
|
96
|
+
const errorList = Array.isArray(errors) ? errors : typeof errors === "string" ? [{ msg: errors }] : [];
|
|
97
|
+
throw new ValidationError(message, errorList, statusCode, responseData);
|
|
98
|
+
} else if (statusCode === 429) {
|
|
99
|
+
const retryAfter = responseData.retry_after;
|
|
100
|
+
throw new RateLimitError(message, retryAfter, statusCode, responseData);
|
|
101
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
102
|
+
if (message.toLowerCase().includes("policy") || responseData.policy_id) {
|
|
103
|
+
throw new PolicyViolationError(
|
|
104
|
+
message,
|
|
105
|
+
responseData.policy_id,
|
|
106
|
+
responseData.policy_name,
|
|
107
|
+
statusCode,
|
|
108
|
+
responseData
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
throw new VaikoraError(message, statusCode, responseData);
|
|
112
|
+
} else if (statusCode >= 500) {
|
|
113
|
+
throw new ServerError(message, statusCode, responseData);
|
|
114
|
+
}
|
|
115
|
+
throw new VaikoraError(message, statusCode, responseData);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/utils.ts
|
|
119
|
+
function toCamelCase(str) {
|
|
120
|
+
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
121
|
+
}
|
|
122
|
+
function toSnakeCase(str) {
|
|
123
|
+
return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
124
|
+
}
|
|
125
|
+
function transformKeysToCamel(obj) {
|
|
126
|
+
if (Array.isArray(obj)) {
|
|
127
|
+
return obj.map((item) => transformKeysToCamel(item));
|
|
128
|
+
}
|
|
129
|
+
if (obj !== null && typeof obj === "object") {
|
|
130
|
+
return Object.entries(obj).reduce(
|
|
131
|
+
(acc, [key, value]) => ({
|
|
132
|
+
...acc,
|
|
133
|
+
[toCamelCase(key)]: transformKeysToCamel(value)
|
|
134
|
+
}),
|
|
135
|
+
{}
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return obj;
|
|
139
|
+
}
|
|
140
|
+
function transformKeysToSnake(obj) {
|
|
141
|
+
if (Array.isArray(obj)) {
|
|
142
|
+
return obj.map((item) => transformKeysToSnake(item));
|
|
143
|
+
}
|
|
144
|
+
if (obj !== null && typeof obj === "object") {
|
|
145
|
+
return Object.entries(obj).reduce(
|
|
146
|
+
(acc, [key, value]) => ({
|
|
147
|
+
...acc,
|
|
148
|
+
[toSnakeCase(key)]: transformKeysToSnake(value)
|
|
149
|
+
}),
|
|
150
|
+
{}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return obj;
|
|
154
|
+
}
|
|
155
|
+
function removeUndefined(obj) {
|
|
156
|
+
return Object.fromEntries(
|
|
157
|
+
Object.entries(obj).filter(([, value]) => value !== void 0)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function formatDate(date) {
|
|
161
|
+
if (!date) return void 0;
|
|
162
|
+
if (typeof date === "string") return date;
|
|
163
|
+
return date.toISOString();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/emergency.ts
|
|
167
|
+
var FreezeReason = /* @__PURE__ */ ((FreezeReason2) => {
|
|
168
|
+
FreezeReason2["MANUAL"] = "manual";
|
|
169
|
+
FreezeReason2["POLICY_VIOLATION"] = "policy_violation";
|
|
170
|
+
FreezeReason2["ANOMALY_DETECTED"] = "anomaly_detected";
|
|
171
|
+
FreezeReason2["RATE_LIMIT"] = "rate_limit";
|
|
172
|
+
FreezeReason2["SECURITY_THREAT"] = "security_threat";
|
|
173
|
+
FreezeReason2["SYSTEM_OVERLOAD"] = "system_overload";
|
|
174
|
+
FreezeReason2["CIRCUIT_BREAKER"] = "circuit_breaker";
|
|
175
|
+
FreezeReason2["MAINTENANCE"] = "maintenance";
|
|
176
|
+
return FreezeReason2;
|
|
177
|
+
})(FreezeReason || {});
|
|
178
|
+
var FreezeScope = /* @__PURE__ */ ((FreezeScope2) => {
|
|
179
|
+
FreezeScope2["ALL_ACTIONS"] = "all_actions";
|
|
180
|
+
FreezeScope2["WRITE_ACTIONS"] = "write_actions";
|
|
181
|
+
FreezeScope2["SPECIFIC_RESOURCE"] = "specific_resource";
|
|
182
|
+
FreezeScope2["SPECIFIC_ACTION_TYPE"] = "specific_action_type";
|
|
183
|
+
return FreezeScope2;
|
|
184
|
+
})(FreezeScope || {});
|
|
185
|
+
var CircuitState = /* @__PURE__ */ ((CircuitState2) => {
|
|
186
|
+
CircuitState2["CLOSED"] = "closed";
|
|
187
|
+
CircuitState2["OPEN"] = "open";
|
|
188
|
+
CircuitState2["HALF_OPEN"] = "half_open";
|
|
189
|
+
return CircuitState2;
|
|
190
|
+
})(CircuitState || {});
|
|
191
|
+
var CircuitBreaker = class {
|
|
192
|
+
name;
|
|
193
|
+
config;
|
|
194
|
+
state = "closed" /* CLOSED */;
|
|
195
|
+
failureCount = 0;
|
|
196
|
+
successCount = 0;
|
|
197
|
+
lastFailureTime;
|
|
198
|
+
halfOpenCalls = 0;
|
|
199
|
+
constructor(name, config) {
|
|
200
|
+
this.name = name;
|
|
201
|
+
this.config = {
|
|
202
|
+
failureThreshold: config?.failureThreshold ?? 5,
|
|
203
|
+
successThreshold: config?.successThreshold ?? 3,
|
|
204
|
+
timeoutSeconds: config?.timeoutSeconds ?? 60,
|
|
205
|
+
halfOpenMaxCalls: config?.halfOpenMaxCalls ?? 3
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Check if circuit is open (blocking).
|
|
210
|
+
*/
|
|
211
|
+
isOpen() {
|
|
212
|
+
if (this.state === "open" /* OPEN */) {
|
|
213
|
+
if (this.lastFailureTime) {
|
|
214
|
+
const elapsed = (Date.now() - this.lastFailureTime) / 1e3;
|
|
215
|
+
if (elapsed >= this.config.timeoutSeconds) {
|
|
216
|
+
this.state = "half_open" /* HALF_OPEN */;
|
|
217
|
+
this.halfOpenCalls = 0;
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Record a successful call.
|
|
227
|
+
*/
|
|
228
|
+
recordSuccess() {
|
|
229
|
+
if (this.state === "half_open" /* HALF_OPEN */) {
|
|
230
|
+
this.successCount++;
|
|
231
|
+
if (this.successCount >= this.config.successThreshold) {
|
|
232
|
+
this.state = "closed" /* CLOSED */;
|
|
233
|
+
this.failureCount = 0;
|
|
234
|
+
this.successCount = 0;
|
|
235
|
+
}
|
|
236
|
+
} else if (this.state === "closed" /* CLOSED */) {
|
|
237
|
+
this.failureCount = Math.max(0, this.failureCount - 1);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Record a failed call.
|
|
242
|
+
*/
|
|
243
|
+
recordFailure() {
|
|
244
|
+
this.failureCount++;
|
|
245
|
+
this.lastFailureTime = Date.now();
|
|
246
|
+
if (this.state === "half_open" /* HALF_OPEN */) {
|
|
247
|
+
this.state = "open" /* OPEN */;
|
|
248
|
+
this.successCount = 0;
|
|
249
|
+
} else if (this.failureCount >= this.config.failureThreshold) {
|
|
250
|
+
this.state = "open" /* OPEN */;
|
|
251
|
+
this.successCount = 0;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Check if request should be allowed.
|
|
256
|
+
*/
|
|
257
|
+
allowRequest() {
|
|
258
|
+
if (this.isOpen()) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
if (this.state === "half_open" /* HALF_OPEN */) {
|
|
262
|
+
if (this.halfOpenCalls >= this.config.halfOpenMaxCalls) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
this.halfOpenCalls++;
|
|
266
|
+
}
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Wrap a function with circuit breaker protection.
|
|
271
|
+
*/
|
|
272
|
+
wrap(fn) {
|
|
273
|
+
if (!this.allowRequest()) {
|
|
274
|
+
return Promise.reject(new CircuitBreakerOpenError(`Circuit breaker '${this.name}' is open`));
|
|
275
|
+
}
|
|
276
|
+
return fn().then((result) => {
|
|
277
|
+
this.recordSuccess();
|
|
278
|
+
return result;
|
|
279
|
+
}).catch((error) => {
|
|
280
|
+
this.recordFailure();
|
|
281
|
+
throw error;
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
var CircuitBreakerOpenError = class extends Error {
|
|
286
|
+
constructor(message) {
|
|
287
|
+
super(message);
|
|
288
|
+
this.name = "CircuitBreakerOpenError";
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
var AgentFrozenError = class extends Error {
|
|
292
|
+
freezeState;
|
|
293
|
+
constructor(message, freezeState) {
|
|
294
|
+
super(message);
|
|
295
|
+
this.name = "AgentFrozenError";
|
|
296
|
+
this.freezeState = freezeState;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
var EmergencyController = class {
|
|
300
|
+
agentId;
|
|
301
|
+
freezeState;
|
|
302
|
+
circuitBreakers = /* @__PURE__ */ new Map();
|
|
303
|
+
freezeCallbacks = [];
|
|
304
|
+
resumeCallbacks = [];
|
|
305
|
+
rateLimits = /* @__PURE__ */ new Map();
|
|
306
|
+
constructor(agentId) {
|
|
307
|
+
this.agentId = agentId;
|
|
308
|
+
this.freezeState = {
|
|
309
|
+
isFrozen: false,
|
|
310
|
+
scope: "all_actions" /* ALL_ACTIONS */,
|
|
311
|
+
affectedResources: [],
|
|
312
|
+
affectedActionTypes: [],
|
|
313
|
+
metadata: {}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check if agent is currently frozen.
|
|
318
|
+
*/
|
|
319
|
+
get isFrozen() {
|
|
320
|
+
if (this.isExpired()) {
|
|
321
|
+
this.autoUnfreeze();
|
|
322
|
+
}
|
|
323
|
+
return this.freezeState.isFrozen;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get current freeze state.
|
|
327
|
+
*/
|
|
328
|
+
getFreezeState() {
|
|
329
|
+
return { ...this.freezeState };
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Check if freeze has expired.
|
|
333
|
+
*/
|
|
334
|
+
isExpired() {
|
|
335
|
+
if (!this.freezeState.freezeUntil) return false;
|
|
336
|
+
return /* @__PURE__ */ new Date() > this.freezeState.freezeUntil;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Freeze the agent immediately.
|
|
340
|
+
*/
|
|
341
|
+
freeze(options) {
|
|
342
|
+
const now = /* @__PURE__ */ new Date();
|
|
343
|
+
let freezeUntil;
|
|
344
|
+
if (options.durationSeconds) {
|
|
345
|
+
freezeUntil = new Date(now.getTime() + options.durationSeconds * 1e3);
|
|
346
|
+
}
|
|
347
|
+
this.freezeState = {
|
|
348
|
+
isFrozen: true,
|
|
349
|
+
reason: options.reason,
|
|
350
|
+
scope: options.scope ?? "all_actions" /* ALL_ACTIONS */,
|
|
351
|
+
frozenAt: now,
|
|
352
|
+
frozenBy: options.frozenBy ?? "system",
|
|
353
|
+
freezeUntil,
|
|
354
|
+
affectedResources: options.affectedResources ?? [],
|
|
355
|
+
affectedActionTypes: options.affectedActionTypes ?? [],
|
|
356
|
+
message: options.message,
|
|
357
|
+
metadata: options.metadata ?? {}
|
|
358
|
+
};
|
|
359
|
+
console.warn(
|
|
360
|
+
`Agent ${this.agentId} FROZEN: reason=${options.reason}, scope=${options.scope ?? "all_actions"}, by=${options.frozenBy ?? "system"}`
|
|
361
|
+
);
|
|
362
|
+
for (const callback of this.freezeCallbacks) {
|
|
363
|
+
try {
|
|
364
|
+
callback(this.freezeState);
|
|
365
|
+
} catch (e) {
|
|
366
|
+
console.error("Freeze callback failed:", e);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return this.getFreezeState();
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Resume agent operations.
|
|
373
|
+
*/
|
|
374
|
+
resume(options = {}) {
|
|
375
|
+
if (!this.freezeState.isFrozen) {
|
|
376
|
+
return this.getFreezeState();
|
|
377
|
+
}
|
|
378
|
+
if (options.verificationPassed === false) {
|
|
379
|
+
console.warn(`Resume denied for agent ${this.agentId}: verification failed`);
|
|
380
|
+
return this.getFreezeState();
|
|
381
|
+
}
|
|
382
|
+
const oldState = { ...this.freezeState };
|
|
383
|
+
this.freezeState = {
|
|
384
|
+
isFrozen: false,
|
|
385
|
+
scope: "all_actions" /* ALL_ACTIONS */,
|
|
386
|
+
affectedResources: [],
|
|
387
|
+
affectedActionTypes: [],
|
|
388
|
+
metadata: {}
|
|
389
|
+
};
|
|
390
|
+
console.info(
|
|
391
|
+
`Agent ${this.agentId} RESUMED: by=${options.resumedBy ?? "system"}, was_frozen_for=${oldState.reason ?? "unknown"}`
|
|
392
|
+
);
|
|
393
|
+
for (const callback of this.resumeCallbacks) {
|
|
394
|
+
try {
|
|
395
|
+
callback(oldState, options.notes);
|
|
396
|
+
} catch (e) {
|
|
397
|
+
console.error("Resume callback failed:", e);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return this.getFreezeState();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Auto-unfreeze when time expires.
|
|
404
|
+
*/
|
|
405
|
+
autoUnfreeze() {
|
|
406
|
+
console.info(`Agent ${this.agentId} auto-unfreezing (time expired)`);
|
|
407
|
+
this.freezeState = {
|
|
408
|
+
isFrozen: false,
|
|
409
|
+
scope: "all_actions" /* ALL_ACTIONS */,
|
|
410
|
+
affectedResources: [],
|
|
411
|
+
affectedActionTypes: [],
|
|
412
|
+
metadata: {}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Check if a specific action is blocked.
|
|
417
|
+
*/
|
|
418
|
+
isActionBlocked(actionType, resource) {
|
|
419
|
+
if (!this.isFrozen) {
|
|
420
|
+
return { blocked: false };
|
|
421
|
+
}
|
|
422
|
+
const state = this.freezeState;
|
|
423
|
+
if (state.scope === "all_actions" /* ALL_ACTIONS */) {
|
|
424
|
+
return { blocked: true, message: state.message || `Agent frozen: ${state.reason}` };
|
|
425
|
+
}
|
|
426
|
+
if (state.scope === "write_actions" /* WRITE_ACTIONS */) {
|
|
427
|
+
const writeActions = ["create", "update", "delete", "write", "modify", "insert"];
|
|
428
|
+
if (writeActions.some((w) => actionType.toLowerCase().includes(w))) {
|
|
429
|
+
return { blocked: true, message: state.message || "Write actions frozen" };
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (state.scope === "specific_resource" /* SPECIFIC_RESOURCE */) {
|
|
433
|
+
if (resource && state.affectedResources.includes(resource)) {
|
|
434
|
+
return { blocked: true, message: state.message || `Resource ${resource} is frozen` };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (state.scope === "specific_action_type" /* SPECIFIC_ACTION_TYPE */) {
|
|
438
|
+
if (state.affectedActionTypes.includes(actionType)) {
|
|
439
|
+
return { blocked: true, message: state.message || `Action type ${actionType} is frozen` };
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return { blocked: false };
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Register a callback for freeze events.
|
|
446
|
+
*/
|
|
447
|
+
onFreeze(callback) {
|
|
448
|
+
this.freezeCallbacks.push(callback);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Register a callback for resume events.
|
|
452
|
+
*/
|
|
453
|
+
onResume(callback) {
|
|
454
|
+
this.resumeCallbacks.push(callback);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Get or create a circuit breaker.
|
|
458
|
+
*/
|
|
459
|
+
getCircuitBreaker(name, config) {
|
|
460
|
+
if (!this.circuitBreakers.has(name)) {
|
|
461
|
+
this.circuitBreakers.set(name, new CircuitBreaker(name, config));
|
|
462
|
+
}
|
|
463
|
+
return this.circuitBreakers.get(name);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Check if rate limit is exceeded.
|
|
467
|
+
*/
|
|
468
|
+
checkRateLimit(key, maxRequests, windowSeconds) {
|
|
469
|
+
const now = Date.now();
|
|
470
|
+
const cutoff = now - windowSeconds * 1e3;
|
|
471
|
+
if (!this.rateLimits.has(key)) {
|
|
472
|
+
this.rateLimits.set(key, []);
|
|
473
|
+
}
|
|
474
|
+
const timestamps = this.rateLimits.get(key).filter((t) => t > cutoff);
|
|
475
|
+
this.rateLimits.set(key, timestamps);
|
|
476
|
+
if (timestamps.length >= maxRequests) {
|
|
477
|
+
return { allowed: false, remaining: 0 };
|
|
478
|
+
}
|
|
479
|
+
timestamps.push(now);
|
|
480
|
+
return { allowed: true, remaining: maxRequests - timestamps.length };
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Get agent ID.
|
|
484
|
+
*/
|
|
485
|
+
getAgentId() {
|
|
486
|
+
return this.agentId;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
function assertNotFrozen(controller, actionType, resource) {
|
|
490
|
+
const { blocked, message } = controller.isActionBlocked(actionType, resource);
|
|
491
|
+
if (blocked) {
|
|
492
|
+
throw new AgentFrozenError(message || "Agent is frozen", controller.getFreezeState());
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function frozenGuard(controller, actionType, resource) {
|
|
496
|
+
return function(fn) {
|
|
497
|
+
return async function(...args) {
|
|
498
|
+
assertNotFrozen(controller, actionType, resource);
|
|
499
|
+
return fn.apply(this, args);
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/types.ts
|
|
505
|
+
var ApprovalStatus = /* @__PURE__ */ ((ApprovalStatus2) => {
|
|
506
|
+
ApprovalStatus2["PENDING"] = "pending";
|
|
507
|
+
ApprovalStatus2["APPROVED"] = "approved";
|
|
508
|
+
ApprovalStatus2["DENIED"] = "denied";
|
|
509
|
+
ApprovalStatus2["EXPIRED"] = "expired";
|
|
510
|
+
return ApprovalStatus2;
|
|
511
|
+
})(ApprovalStatus || {});
|
|
512
|
+
|
|
513
|
+
// src/identity.ts
|
|
514
|
+
import crypto from "crypto";
|
|
515
|
+
var AgentIdentity = class _AgentIdentity {
|
|
516
|
+
static NONCE_LENGTH = 16;
|
|
517
|
+
static MAX_TIMESTAMP_DRIFT_SECONDS = 300;
|
|
518
|
+
// 5 minutes
|
|
519
|
+
agentId;
|
|
520
|
+
apiKey;
|
|
521
|
+
secretKey;
|
|
522
|
+
environment;
|
|
523
|
+
requestCounter = 0;
|
|
524
|
+
usedNonces = /* @__PURE__ */ new Set();
|
|
525
|
+
nonceExpiry = /* @__PURE__ */ new Map();
|
|
526
|
+
constructor(agentId, apiKey, secretKey, environment = "production") {
|
|
527
|
+
this.agentId = agentId;
|
|
528
|
+
this.apiKey = apiKey;
|
|
529
|
+
this.secretKey = secretKey || this.generateSecretKey();
|
|
530
|
+
this.environment = environment;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Generate a cryptographically secure secret key.
|
|
534
|
+
*/
|
|
535
|
+
generateSecretKey() {
|
|
536
|
+
return crypto.randomBytes(32).toString("base64url");
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Generate a unique nonce for request signing.
|
|
540
|
+
*/
|
|
541
|
+
generateNonce() {
|
|
542
|
+
return crypto.randomBytes(_AgentIdentity.NONCE_LENGTH).toString("base64url");
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Get current timestamp in ISO format.
|
|
546
|
+
*/
|
|
547
|
+
getTimestamp() {
|
|
548
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Create SHA-256 hash of payload.
|
|
552
|
+
*/
|
|
553
|
+
hashPayload(payload) {
|
|
554
|
+
let payloadStr;
|
|
555
|
+
if (payload === null || payload === void 0) {
|
|
556
|
+
payloadStr = "";
|
|
557
|
+
} else if (typeof payload === "string") {
|
|
558
|
+
payloadStr = payload;
|
|
559
|
+
} else {
|
|
560
|
+
payloadStr = JSON.stringify(payload);
|
|
561
|
+
}
|
|
562
|
+
return crypto.createHash("sha256").update(payloadStr).digest("hex");
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Sign an API request.
|
|
566
|
+
*/
|
|
567
|
+
signRequest(method, path, payload, additionalHeaders) {
|
|
568
|
+
const timestamp = this.getTimestamp();
|
|
569
|
+
const nonce = this.generateNonce();
|
|
570
|
+
const payloadHash = this.hashPayload(payload);
|
|
571
|
+
this.requestCounter++;
|
|
572
|
+
const requestId = `${this.agentId}-${Date.now()}-${String(this.requestCounter).padStart(6, "0")}`;
|
|
573
|
+
const canonicalString = [
|
|
574
|
+
method.toUpperCase(),
|
|
575
|
+
path,
|
|
576
|
+
timestamp,
|
|
577
|
+
nonce,
|
|
578
|
+
payloadHash,
|
|
579
|
+
this.agentId
|
|
580
|
+
].join("\n");
|
|
581
|
+
const signature = crypto.createHmac("sha256", this.secretKey).update(canonicalString).digest("hex");
|
|
582
|
+
const signedHeaders = {
|
|
583
|
+
"X-Vaikora-Agent-ID": this.agentId,
|
|
584
|
+
"X-Vaikora-Request-ID": requestId,
|
|
585
|
+
"X-Vaikora-Timestamp": timestamp,
|
|
586
|
+
"X-Vaikora-Nonce": nonce,
|
|
587
|
+
"X-Vaikora-Signature": signature,
|
|
588
|
+
"X-Vaikora-Payload-Hash": payloadHash,
|
|
589
|
+
...additionalHeaders
|
|
590
|
+
};
|
|
591
|
+
const signatureDetails = {
|
|
592
|
+
requestId,
|
|
593
|
+
agentId: this.agentId,
|
|
594
|
+
timestamp,
|
|
595
|
+
signature,
|
|
596
|
+
payloadHash,
|
|
597
|
+
nonce
|
|
598
|
+
};
|
|
599
|
+
this.usedNonces.add(nonce);
|
|
600
|
+
this.nonceExpiry.set(nonce, Date.now() + _AgentIdentity.MAX_TIMESTAMP_DRIFT_SECONDS * 1e3);
|
|
601
|
+
this.cleanupNonces();
|
|
602
|
+
return { headers: signedHeaders, signature: signatureDetails };
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Verify a signed request.
|
|
606
|
+
*/
|
|
607
|
+
verifySignature(method, path, timestamp, nonce, payloadHash, providedSignature, agentId) {
|
|
608
|
+
if (agentId !== this.agentId) {
|
|
609
|
+
return { isValid: false, errorMessage: "Agent ID mismatch" };
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
const requestTime = new Date(timestamp);
|
|
613
|
+
const now = /* @__PURE__ */ new Date();
|
|
614
|
+
const driftSeconds = Math.abs((now.getTime() - requestTime.getTime()) / 1e3);
|
|
615
|
+
if (driftSeconds > _AgentIdentity.MAX_TIMESTAMP_DRIFT_SECONDS) {
|
|
616
|
+
return { isValid: false, errorMessage: `Timestamp too old: ${driftSeconds}s drift` };
|
|
617
|
+
}
|
|
618
|
+
} catch (e) {
|
|
619
|
+
return { isValid: false, errorMessage: `Invalid timestamp format: ${e}` };
|
|
620
|
+
}
|
|
621
|
+
if (this.usedNonces.has(nonce)) {
|
|
622
|
+
return { isValid: false, errorMessage: "Nonce already used (possible replay attack)" };
|
|
623
|
+
}
|
|
624
|
+
const canonicalString = [
|
|
625
|
+
method.toUpperCase(),
|
|
626
|
+
path,
|
|
627
|
+
timestamp,
|
|
628
|
+
nonce,
|
|
629
|
+
payloadHash,
|
|
630
|
+
agentId
|
|
631
|
+
].join("\n");
|
|
632
|
+
const expectedSignature = crypto.createHmac("sha256", this.secretKey).update(canonicalString).digest("hex");
|
|
633
|
+
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(providedSignature))) {
|
|
634
|
+
return { isValid: false, errorMessage: "Signature mismatch" };
|
|
635
|
+
}
|
|
636
|
+
this.usedNonces.add(nonce);
|
|
637
|
+
this.nonceExpiry.set(nonce, Date.now() + _AgentIdentity.MAX_TIMESTAMP_DRIFT_SECONDS * 1e3);
|
|
638
|
+
return { isValid: true };
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Clean up expired nonces.
|
|
642
|
+
*/
|
|
643
|
+
cleanupNonces() {
|
|
644
|
+
const now = Date.now();
|
|
645
|
+
for (const [nonce, expiry] of this.nonceExpiry.entries()) {
|
|
646
|
+
if (expiry < now) {
|
|
647
|
+
this.usedNonces.delete(nonce);
|
|
648
|
+
this.nonceExpiry.delete(nonce);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Create a signed attestation of claims.
|
|
654
|
+
*/
|
|
655
|
+
createAttestation(claims) {
|
|
656
|
+
const attestation = {
|
|
657
|
+
agentId: this.agentId,
|
|
658
|
+
claims,
|
|
659
|
+
issuedAt: this.getTimestamp(),
|
|
660
|
+
environment: this.environment
|
|
661
|
+
};
|
|
662
|
+
const payload = JSON.stringify(attestation);
|
|
663
|
+
const signature = crypto.createHmac("sha256", this.secretKey).update(payload).digest("hex");
|
|
664
|
+
attestation.signature = signature;
|
|
665
|
+
return Buffer.from(JSON.stringify(attestation)).toString("base64url");
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Verify a signed attestation.
|
|
669
|
+
*/
|
|
670
|
+
verifyAttestation(encodedAttestation) {
|
|
671
|
+
try {
|
|
672
|
+
const decoded = JSON.parse(Buffer.from(encodedAttestation, "base64url").toString());
|
|
673
|
+
const providedSignature = decoded.signature;
|
|
674
|
+
if (!providedSignature) {
|
|
675
|
+
return { isValid: false };
|
|
676
|
+
}
|
|
677
|
+
delete decoded.signature;
|
|
678
|
+
const payload = JSON.stringify(decoded);
|
|
679
|
+
const expectedSignature = crypto.createHmac("sha256", this.secretKey).update(payload).digest("hex");
|
|
680
|
+
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature), Buffer.from(providedSignature))) {
|
|
681
|
+
return { isValid: false };
|
|
682
|
+
}
|
|
683
|
+
return { isValid: true, attestation: decoded };
|
|
684
|
+
} catch (e) {
|
|
685
|
+
return { isValid: false };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Rotate the secret key.
|
|
690
|
+
*/
|
|
691
|
+
rotateSecret() {
|
|
692
|
+
this.secretKey = this.generateSecretKey();
|
|
693
|
+
this.usedNonces.clear();
|
|
694
|
+
this.nonceExpiry.clear();
|
|
695
|
+
return this.secretKey;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Get identity information (without secrets).
|
|
699
|
+
*/
|
|
700
|
+
getIdentityInfo() {
|
|
701
|
+
return {
|
|
702
|
+
agentId: this.agentId,
|
|
703
|
+
apiKey: this.apiKey,
|
|
704
|
+
environment: this.environment,
|
|
705
|
+
requestCount: this.requestCounter
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Get the agent ID.
|
|
710
|
+
*/
|
|
711
|
+
getAgentId() {
|
|
712
|
+
return this.agentId;
|
|
713
|
+
}
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
// src/context.ts
|
|
717
|
+
import { randomUUID } from "crypto";
|
|
718
|
+
var uuidv4 = () => randomUUID();
|
|
719
|
+
var ExecutionMode = /* @__PURE__ */ ((ExecutionMode2) => {
|
|
720
|
+
ExecutionMode2["ENFORCE"] = "enforce";
|
|
721
|
+
ExecutionMode2["MONITOR"] = "monitor";
|
|
722
|
+
ExecutionMode2["SIMULATE"] = "simulate";
|
|
723
|
+
return ExecutionMode2;
|
|
724
|
+
})(ExecutionMode || {});
|
|
725
|
+
var ContextLevel = /* @__PURE__ */ ((ContextLevel2) => {
|
|
726
|
+
ContextLevel2["ROOT"] = "root";
|
|
727
|
+
ContextLevel2["CHILD"] = "child";
|
|
728
|
+
ContextLevel2["NESTED"] = "nested";
|
|
729
|
+
return ContextLevel2;
|
|
730
|
+
})(ContextLevel || {});
|
|
731
|
+
var ExecutionContext = class _ExecutionContext {
|
|
732
|
+
// Identity
|
|
733
|
+
agentId;
|
|
734
|
+
sessionId;
|
|
735
|
+
// Execution settings
|
|
736
|
+
mode;
|
|
737
|
+
environment;
|
|
738
|
+
// Tracing
|
|
739
|
+
traceId;
|
|
740
|
+
spanId;
|
|
741
|
+
parentSpanId;
|
|
742
|
+
// Context level
|
|
743
|
+
level;
|
|
744
|
+
depth;
|
|
745
|
+
// Metadata
|
|
746
|
+
userId;
|
|
747
|
+
tenantId;
|
|
748
|
+
correlationId;
|
|
749
|
+
// Timing
|
|
750
|
+
createdAt;
|
|
751
|
+
timeoutSeconds;
|
|
752
|
+
deadline;
|
|
753
|
+
// Custom context
|
|
754
|
+
attributes;
|
|
755
|
+
baggage;
|
|
756
|
+
// Internal state
|
|
757
|
+
traces = [];
|
|
758
|
+
activeTrace;
|
|
759
|
+
constructor(options) {
|
|
760
|
+
this.agentId = options.agentId;
|
|
761
|
+
this.sessionId = options.sessionId || uuidv4();
|
|
762
|
+
this.mode = options.mode || "enforce" /* ENFORCE */;
|
|
763
|
+
this.environment = options.environment || "production";
|
|
764
|
+
this.traceId = options.traceId || uuidv4();
|
|
765
|
+
this.spanId = options.spanId || uuidv4().substring(0, 16);
|
|
766
|
+
this.parentSpanId = options.parentSpanId;
|
|
767
|
+
this.level = options.level || "root" /* ROOT */;
|
|
768
|
+
this.depth = options.depth || 0;
|
|
769
|
+
this.userId = options.userId;
|
|
770
|
+
this.tenantId = options.tenantId;
|
|
771
|
+
this.correlationId = options.correlationId;
|
|
772
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
773
|
+
this.timeoutSeconds = options.timeoutSeconds;
|
|
774
|
+
this.attributes = options.attributes || {};
|
|
775
|
+
this.baggage = options.baggage || {};
|
|
776
|
+
if (options.timeoutSeconds) {
|
|
777
|
+
this.deadline = new Date(Date.now() + options.timeoutSeconds * 1e3);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Check if this is a dry-run (simulate mode).
|
|
782
|
+
*/
|
|
783
|
+
isDryRun() {
|
|
784
|
+
return this.mode === "simulate" /* SIMULATE */;
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Check if policy enforcement is active.
|
|
788
|
+
*/
|
|
789
|
+
shouldEnforce() {
|
|
790
|
+
return this.mode === "enforce" /* ENFORCE */;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Check if violations should block execution.
|
|
794
|
+
*/
|
|
795
|
+
shouldBlockViolations() {
|
|
796
|
+
return this.mode === "enforce" /* ENFORCE */;
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Check if context deadline has passed.
|
|
800
|
+
*/
|
|
801
|
+
isExpired() {
|
|
802
|
+
if (!this.deadline) return false;
|
|
803
|
+
return /* @__PURE__ */ new Date() > this.deadline;
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Get remaining time before deadline.
|
|
807
|
+
*/
|
|
808
|
+
remainingTimeSeconds() {
|
|
809
|
+
if (!this.deadline) return void 0;
|
|
810
|
+
const remaining = (this.deadline.getTime() - Date.now()) / 1e3;
|
|
811
|
+
return Math.max(0, remaining);
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Start a new trace span.
|
|
815
|
+
*/
|
|
816
|
+
startSpan(operationName, tags) {
|
|
817
|
+
const trace = {
|
|
818
|
+
traceId: this.traceId,
|
|
819
|
+
spanId: uuidv4().substring(0, 16),
|
|
820
|
+
parentSpanId: this.activeTrace?.spanId || this.spanId,
|
|
821
|
+
operationName,
|
|
822
|
+
startTime: Date.now(),
|
|
823
|
+
status: "pending",
|
|
824
|
+
tags: tags || {},
|
|
825
|
+
logs: []
|
|
826
|
+
};
|
|
827
|
+
this.traces.push(trace);
|
|
828
|
+
this.activeTrace = trace;
|
|
829
|
+
return trace;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* End the current trace span.
|
|
833
|
+
*/
|
|
834
|
+
endSpan(status = "ok") {
|
|
835
|
+
if (this.activeTrace) {
|
|
836
|
+
this.activeTrace.endTime = Date.now();
|
|
837
|
+
this.activeTrace.durationMs = this.activeTrace.endTime - this.activeTrace.startTime;
|
|
838
|
+
this.activeTrace.status = status;
|
|
839
|
+
this.activeTrace = void 0;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Add a log entry to the current trace.
|
|
844
|
+
*/
|
|
845
|
+
addLog(message, level = "info") {
|
|
846
|
+
if (this.activeTrace) {
|
|
847
|
+
this.activeTrace.logs.push({
|
|
848
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
849
|
+
level,
|
|
850
|
+
message
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
855
|
+
* Create a child context for nested operations.
|
|
856
|
+
*/
|
|
857
|
+
childContext(_operation) {
|
|
858
|
+
return new _ExecutionContext({
|
|
859
|
+
agentId: this.agentId,
|
|
860
|
+
sessionId: this.sessionId,
|
|
861
|
+
mode: this.mode,
|
|
862
|
+
environment: this.environment,
|
|
863
|
+
traceId: this.traceId,
|
|
864
|
+
spanId: uuidv4().substring(0, 16),
|
|
865
|
+
parentSpanId: this.spanId,
|
|
866
|
+
level: this.level === "root" /* ROOT */ ? "child" /* CHILD */ : "nested" /* NESTED */,
|
|
867
|
+
depth: this.depth + 1,
|
|
868
|
+
userId: this.userId,
|
|
869
|
+
tenantId: this.tenantId,
|
|
870
|
+
correlationId: this.correlationId,
|
|
871
|
+
timeoutSeconds: this.remainingTimeSeconds(),
|
|
872
|
+
attributes: { ...this.attributes },
|
|
873
|
+
baggage: { ...this.baggage }
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Create a new context with different execution mode.
|
|
878
|
+
*/
|
|
879
|
+
withMode(mode) {
|
|
880
|
+
const ctx = this.childContext();
|
|
881
|
+
ctx.mode = mode;
|
|
882
|
+
return ctx;
|
|
883
|
+
}
|
|
884
|
+
/**
|
|
885
|
+
* Set a context attribute.
|
|
886
|
+
*/
|
|
887
|
+
setAttribute(key, value) {
|
|
888
|
+
this.attributes[key] = value;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Get a context attribute.
|
|
892
|
+
*/
|
|
893
|
+
getAttribute(key, defaultValue) {
|
|
894
|
+
return this.attributes[key] || defaultValue;
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Set baggage (propagated across service boundaries).
|
|
898
|
+
*/
|
|
899
|
+
setBaggage(key, value) {
|
|
900
|
+
this.baggage[key] = value;
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Get baggage value.
|
|
904
|
+
*/
|
|
905
|
+
getBaggage(key, defaultValue = "") {
|
|
906
|
+
return this.baggage[key] || defaultValue;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Export context to HTTP headers for propagation.
|
|
910
|
+
*/
|
|
911
|
+
toHeaders() {
|
|
912
|
+
const headers = {
|
|
913
|
+
"X-Vaikora-Trace-ID": this.traceId,
|
|
914
|
+
"X-Vaikora-Span-ID": this.spanId,
|
|
915
|
+
"X-Vaikora-Agent-ID": this.agentId,
|
|
916
|
+
"X-Vaikora-Session-ID": this.sessionId,
|
|
917
|
+
"X-Vaikora-Mode": this.mode
|
|
918
|
+
};
|
|
919
|
+
if (this.parentSpanId) {
|
|
920
|
+
headers["X-Vaikora-Parent-Span-ID"] = this.parentSpanId;
|
|
921
|
+
}
|
|
922
|
+
if (this.correlationId) {
|
|
923
|
+
headers["X-Vaikora-Correlation-ID"] = this.correlationId;
|
|
924
|
+
}
|
|
925
|
+
if (this.tenantId) {
|
|
926
|
+
headers["X-Vaikora-Tenant-ID"] = this.tenantId;
|
|
927
|
+
}
|
|
928
|
+
for (const [key, value] of Object.entries(this.baggage)) {
|
|
929
|
+
headers[`X-Vaikora-Baggage-${key}`] = value;
|
|
930
|
+
}
|
|
931
|
+
return headers;
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Create context from HTTP headers.
|
|
935
|
+
*/
|
|
936
|
+
static fromHeaders(headers, agentId) {
|
|
937
|
+
const modeStr = headers["X-Vaikora-Mode"] || headers["x-vaikora-mode"] || "enforce";
|
|
938
|
+
let mode;
|
|
939
|
+
try {
|
|
940
|
+
mode = modeStr;
|
|
941
|
+
} catch {
|
|
942
|
+
mode = "enforce" /* ENFORCE */;
|
|
943
|
+
}
|
|
944
|
+
const baggage = {};
|
|
945
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
946
|
+
const lowerKey = key.toLowerCase();
|
|
947
|
+
if (lowerKey.startsWith("x-vaikora-baggage-")) {
|
|
948
|
+
const baggageKey = key.substring(18);
|
|
949
|
+
baggage[baggageKey] = value;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return new _ExecutionContext({
|
|
953
|
+
agentId,
|
|
954
|
+
sessionId: headers["X-Vaikora-Session-ID"] || headers["x-vaikora-session-id"],
|
|
955
|
+
mode,
|
|
956
|
+
traceId: headers["X-Vaikora-Trace-ID"] || headers["x-vaikora-trace-id"],
|
|
957
|
+
parentSpanId: headers["X-Vaikora-Span-ID"] || headers["x-vaikora-span-id"],
|
|
958
|
+
correlationId: headers["X-Vaikora-Correlation-ID"] || headers["x-vaikora-correlation-id"],
|
|
959
|
+
tenantId: headers["X-Vaikora-Tenant-ID"] || headers["x-vaikora-tenant-id"],
|
|
960
|
+
level: "child" /* CHILD */,
|
|
961
|
+
baggage
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Export context to dictionary.
|
|
966
|
+
*/
|
|
967
|
+
toDict() {
|
|
968
|
+
return {
|
|
969
|
+
agentId: this.agentId,
|
|
970
|
+
sessionId: this.sessionId,
|
|
971
|
+
mode: this.mode,
|
|
972
|
+
environment: this.environment,
|
|
973
|
+
traceId: this.traceId,
|
|
974
|
+
spanId: this.spanId,
|
|
975
|
+
parentSpanId: this.parentSpanId,
|
|
976
|
+
level: this.level,
|
|
977
|
+
depth: this.depth,
|
|
978
|
+
userId: this.userId,
|
|
979
|
+
tenantId: this.tenantId,
|
|
980
|
+
correlationId: this.correlationId,
|
|
981
|
+
createdAt: this.createdAt.toISOString(),
|
|
982
|
+
attributes: this.attributes,
|
|
983
|
+
baggage: this.baggage,
|
|
984
|
+
traceCount: this.traces.length
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Get all traces.
|
|
989
|
+
*/
|
|
990
|
+
getTraces() {
|
|
991
|
+
return [...this.traces];
|
|
992
|
+
}
|
|
993
|
+
};
|
|
994
|
+
var currentContext;
|
|
995
|
+
function getCurrentContext() {
|
|
996
|
+
return currentContext;
|
|
997
|
+
}
|
|
998
|
+
function setCurrentContext(ctx) {
|
|
999
|
+
currentContext = ctx;
|
|
1000
|
+
}
|
|
1001
|
+
function clearContext() {
|
|
1002
|
+
currentContext = void 0;
|
|
1003
|
+
}
|
|
1004
|
+
async function withContextScope(ctx, fn) {
|
|
1005
|
+
const previousContext = currentContext;
|
|
1006
|
+
currentContext = ctx;
|
|
1007
|
+
try {
|
|
1008
|
+
return await fn();
|
|
1009
|
+
} finally {
|
|
1010
|
+
currentContext = previousContext;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
function withContext(mode = "enforce" /* ENFORCE */, timeoutSeconds) {
|
|
1014
|
+
return function(target, context) {
|
|
1015
|
+
return async function(...args) {
|
|
1016
|
+
const existing = getCurrentContext();
|
|
1017
|
+
let ctx;
|
|
1018
|
+
if (existing) {
|
|
1019
|
+
ctx = existing.childContext();
|
|
1020
|
+
} else {
|
|
1021
|
+
ctx = new ExecutionContext({ agentId: "unknown" });
|
|
1022
|
+
}
|
|
1023
|
+
ctx.mode = mode;
|
|
1024
|
+
if (timeoutSeconds) {
|
|
1025
|
+
ctx.setAttribute("_requestedTimeout", timeoutSeconds);
|
|
1026
|
+
}
|
|
1027
|
+
ctx.startSpan(context?.name || "anonymous");
|
|
1028
|
+
return withContextScope(ctx, async () => {
|
|
1029
|
+
try {
|
|
1030
|
+
const result = await target.apply(this, args);
|
|
1031
|
+
ctx.endSpan("ok");
|
|
1032
|
+
return result;
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
ctx.endSpan("error");
|
|
1035
|
+
ctx.addLog(String(error), "error");
|
|
1036
|
+
throw error;
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/interceptor.ts
|
|
1044
|
+
var InterceptorAction = /* @__PURE__ */ ((InterceptorAction2) => {
|
|
1045
|
+
InterceptorAction2["BLOCK"] = "block";
|
|
1046
|
+
InterceptorAction2["WARN"] = "warn";
|
|
1047
|
+
InterceptorAction2["QUEUE"] = "queue";
|
|
1048
|
+
InterceptorAction2["FALLBACK"] = "fallback";
|
|
1049
|
+
return InterceptorAction2;
|
|
1050
|
+
})(InterceptorAction || {});
|
|
1051
|
+
var PolicyDeniedException = class extends Error {
|
|
1052
|
+
constructor(message, policyId, actionId) {
|
|
1053
|
+
super(message);
|
|
1054
|
+
this.policyId = policyId;
|
|
1055
|
+
this.actionId = actionId;
|
|
1056
|
+
this.name = "PolicyDeniedException";
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
var ValidationException = class extends Error {
|
|
1060
|
+
constructor(message, issues) {
|
|
1061
|
+
super(message);
|
|
1062
|
+
this.issues = issues;
|
|
1063
|
+
this.name = "ValidationException";
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
var ApprovalRequiredException = class extends Error {
|
|
1067
|
+
constructor(message, approvalId, actionId) {
|
|
1068
|
+
super(message);
|
|
1069
|
+
this.approvalId = approvalId;
|
|
1070
|
+
this.actionId = actionId;
|
|
1071
|
+
this.name = "ApprovalRequiredException";
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
var ApprovalDeniedException = class extends Error {
|
|
1075
|
+
constructor(message, approvalId, reason) {
|
|
1076
|
+
super(message);
|
|
1077
|
+
this.approvalId = approvalId;
|
|
1078
|
+
this.reason = reason;
|
|
1079
|
+
this.name = "ApprovalDeniedException";
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
var VaikoraInterceptor = class {
|
|
1083
|
+
client;
|
|
1084
|
+
config;
|
|
1085
|
+
constructor(client, config) {
|
|
1086
|
+
this.client = client;
|
|
1087
|
+
this.config = {
|
|
1088
|
+
actionType: config.actionType,
|
|
1089
|
+
resource: config.resource || "",
|
|
1090
|
+
agentId: config.agentId || client.getDefaultAgentId() || "",
|
|
1091
|
+
validateInputs: config.validateInputs ?? true,
|
|
1092
|
+
validateOutputs: config.validateOutputs ?? false,
|
|
1093
|
+
validationStrict: config.validationStrict ?? false,
|
|
1094
|
+
checkPii: config.checkPii ?? true,
|
|
1095
|
+
checkAnomalies: config.checkAnomalies ?? true,
|
|
1096
|
+
checkToxicity: config.checkToxicity ?? true,
|
|
1097
|
+
autoClean: config.autoClean ?? false,
|
|
1098
|
+
evaluatePolicy: config.evaluatePolicy ?? true,
|
|
1099
|
+
onPolicyDeny: config.onPolicyDeny ?? "block" /* BLOCK */,
|
|
1100
|
+
requireApproval: config.requireApproval ?? false,
|
|
1101
|
+
approvalTimeoutSeconds: config.approvalTimeoutSeconds ?? 3600,
|
|
1102
|
+
approvalNotifyChannels: config.approvalNotifyChannels ?? ["email"],
|
|
1103
|
+
approvalMessage: config.approvalMessage || "",
|
|
1104
|
+
fallbackFunction: config.fallbackFunction,
|
|
1105
|
+
metadata: config.metadata ?? {},
|
|
1106
|
+
tags: config.tags ?? []
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Wrap a function with Vaikora security
|
|
1111
|
+
*/
|
|
1112
|
+
wrap(fn) {
|
|
1113
|
+
return async (...args) => {
|
|
1114
|
+
const startTime = Date.now();
|
|
1115
|
+
const result = {
|
|
1116
|
+
success: false,
|
|
1117
|
+
validationIssues: [],
|
|
1118
|
+
approvalRequired: false,
|
|
1119
|
+
executionTimeMs: 0
|
|
1120
|
+
};
|
|
1121
|
+
try {
|
|
1122
|
+
if (this.config.validateInputs) {
|
|
1123
|
+
const validationResult = await this.validateData(args);
|
|
1124
|
+
if (!validationResult.isValid && this.config.validationStrict) {
|
|
1125
|
+
throw new ValidationException(
|
|
1126
|
+
"Input validation failed",
|
|
1127
|
+
validationResult.issues || []
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
result.validationIssues.push(...validationResult.issues || []);
|
|
1131
|
+
}
|
|
1132
|
+
if (this.config.evaluatePolicy) {
|
|
1133
|
+
const actionResult = await this.client.actions.submit({
|
|
1134
|
+
agentId: this.config.agentId,
|
|
1135
|
+
actionType: this.config.actionType,
|
|
1136
|
+
resource: this.config.resource,
|
|
1137
|
+
payload: { args },
|
|
1138
|
+
metadata: {
|
|
1139
|
+
...this.config.metadata,
|
|
1140
|
+
function: fn.name || "anonymous",
|
|
1141
|
+
tags: this.config.tags
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
result.actionId = actionResult.actionId;
|
|
1145
|
+
if (!actionResult.approved) {
|
|
1146
|
+
result.policyViolation = actionResult.denialReason || "Policy denied";
|
|
1147
|
+
if (this.config.onPolicyDeny === "block" /* BLOCK */) {
|
|
1148
|
+
throw new PolicyDeniedException(
|
|
1149
|
+
result.policyViolation,
|
|
1150
|
+
actionResult.policyId ?? void 0,
|
|
1151
|
+
actionResult.actionId
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (this.config.onPolicyDeny === "fallback" /* FALLBACK */ && this.config.fallbackFunction) {
|
|
1155
|
+
result.result = await this.config.fallbackFunction(...args);
|
|
1156
|
+
result.success = true;
|
|
1157
|
+
result.executionTimeMs = Date.now() - startTime;
|
|
1158
|
+
return result;
|
|
1159
|
+
}
|
|
1160
|
+
if (this.config.onPolicyDeny === "warn" /* WARN */) {
|
|
1161
|
+
console.warn(`[Vaikora] Policy warning: ${result.policyViolation}`);
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const extResult = actionResult;
|
|
1165
|
+
if (this.config.requireApproval && extResult.requiresApproval) {
|
|
1166
|
+
result.approvalRequired = true;
|
|
1167
|
+
result.approvalId = extResult.approvalId;
|
|
1168
|
+
const approved = await this.waitForApproval(extResult.approvalId);
|
|
1169
|
+
if (!approved) {
|
|
1170
|
+
throw new ApprovalDeniedException(
|
|
1171
|
+
"Approval denied",
|
|
1172
|
+
extResult.approvalId
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
result.result = await fn(...args);
|
|
1178
|
+
if (this.config.validateOutputs && result.result !== void 0) {
|
|
1179
|
+
const outputValidation = await this.validateData([result.result]);
|
|
1180
|
+
if (!outputValidation.isValid && this.config.validationStrict) {
|
|
1181
|
+
throw new ValidationException(
|
|
1182
|
+
"Output validation failed",
|
|
1183
|
+
outputValidation.issues || []
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
result.validationIssues.push(...outputValidation.issues || []);
|
|
1187
|
+
}
|
|
1188
|
+
if (result.actionId) {
|
|
1189
|
+
await this.client.actions.complete(result.actionId, {
|
|
1190
|
+
status: "executed",
|
|
1191
|
+
executionTimeMs: Date.now() - startTime
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
result.success = true;
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
1197
|
+
if (result.actionId) {
|
|
1198
|
+
await this.client.actions.complete(result.actionId, {
|
|
1199
|
+
status: "failed",
|
|
1200
|
+
executionTimeMs: Date.now() - startTime,
|
|
1201
|
+
errorMessage: result.error
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
throw error;
|
|
1205
|
+
} finally {
|
|
1206
|
+
result.executionTimeMs = Date.now() - startTime;
|
|
1207
|
+
}
|
|
1208
|
+
return result;
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Validate data using Vaikora API
|
|
1213
|
+
*/
|
|
1214
|
+
async validateData(data) {
|
|
1215
|
+
try {
|
|
1216
|
+
const response = await this.client.request(
|
|
1217
|
+
"POST",
|
|
1218
|
+
"/validate",
|
|
1219
|
+
{
|
|
1220
|
+
data: data.length === 1 ? data[0] : data,
|
|
1221
|
+
check_pii: this.config.checkPii,
|
|
1222
|
+
check_anomalies: this.config.checkAnomalies,
|
|
1223
|
+
check_toxicity: this.config.checkToxicity,
|
|
1224
|
+
auto_clean: this.config.autoClean
|
|
1225
|
+
}
|
|
1226
|
+
);
|
|
1227
|
+
const issues = [];
|
|
1228
|
+
if (response.piiDetected) {
|
|
1229
|
+
issues.push(`PII detected: ${response.piiDetections?.length || 0} items`);
|
|
1230
|
+
}
|
|
1231
|
+
if (response.anomalyDetected) {
|
|
1232
|
+
issues.push(`Anomalies detected: ${response.anomalyDetections?.length || 0} items`);
|
|
1233
|
+
}
|
|
1234
|
+
if (response.toxicityDetected) {
|
|
1235
|
+
issues.push(`Toxicity detected: ${response.toxicityDetections?.length || 0} items`);
|
|
1236
|
+
}
|
|
1237
|
+
return {
|
|
1238
|
+
isValid: issues.length === 0,
|
|
1239
|
+
issues
|
|
1240
|
+
};
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
console.warn("[Vaikora] Validation failed:", error);
|
|
1243
|
+
return { isValid: true, issues: [] };
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Wait for approval to be granted or denied
|
|
1248
|
+
*/
|
|
1249
|
+
async waitForApproval(approvalId) {
|
|
1250
|
+
const timeout = this.config.approvalTimeoutSeconds * 1e3;
|
|
1251
|
+
const pollInterval = 5e3;
|
|
1252
|
+
const startTime = Date.now();
|
|
1253
|
+
while (Date.now() - startTime < timeout) {
|
|
1254
|
+
try {
|
|
1255
|
+
const response = await this.client.request(
|
|
1256
|
+
"GET",
|
|
1257
|
+
`/approvals/${approvalId}/status`
|
|
1258
|
+
);
|
|
1259
|
+
if (response.status === "approved" /* APPROVED */) {
|
|
1260
|
+
return true;
|
|
1261
|
+
}
|
|
1262
|
+
if (response.status === "denied" /* DENIED */) {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
console.warn("[Vaikora] Error checking approval status:", error);
|
|
1268
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
throw new ApprovalRequiredException(
|
|
1272
|
+
"Approval request timed out",
|
|
1273
|
+
approvalId
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
function createInterceptor(client, config) {
|
|
1278
|
+
const interceptor2 = new VaikoraInterceptor(client, config);
|
|
1279
|
+
return (fn) => interceptor2.wrap(fn);
|
|
1280
|
+
}
|
|
1281
|
+
function interceptor(client, config) {
|
|
1282
|
+
return function(_target, _propertyKey, descriptor) {
|
|
1283
|
+
const originalMethod = descriptor.value;
|
|
1284
|
+
const interceptorInstance = new VaikoraInterceptor(client, config);
|
|
1285
|
+
descriptor.value = async function(...args) {
|
|
1286
|
+
const result = await interceptorInstance.wrap(originalMethod.bind(this))(...args);
|
|
1287
|
+
return result.result;
|
|
1288
|
+
};
|
|
1289
|
+
return descriptor;
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/index.ts
|
|
1294
|
+
var DEFAULT_BASE_URL = "https://api.vaikora.com";
|
|
1295
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
1296
|
+
var DEFAULT_RETRY_COUNT = 3;
|
|
1297
|
+
var AgentsAPI = class {
|
|
1298
|
+
constructor(client) {
|
|
1299
|
+
this.client = client;
|
|
1300
|
+
}
|
|
1301
|
+
async register(data) {
|
|
1302
|
+
const response = await this.client.request("POST", "/agents", transformKeysToSnake(data));
|
|
1303
|
+
return response;
|
|
1304
|
+
}
|
|
1305
|
+
async get(agentId) {
|
|
1306
|
+
return this.client.request("GET", `/agents/${agentId}`);
|
|
1307
|
+
}
|
|
1308
|
+
async list(params = {}) {
|
|
1309
|
+
const queryParams = removeUndefined({
|
|
1310
|
+
page: params.page,
|
|
1311
|
+
page_size: params.pageSize,
|
|
1312
|
+
status: params.status,
|
|
1313
|
+
agent_type: params.agentType
|
|
1314
|
+
});
|
|
1315
|
+
return this.client.request("GET", "/agents", void 0, queryParams);
|
|
1316
|
+
}
|
|
1317
|
+
async update(agentId, data) {
|
|
1318
|
+
const updateData = removeUndefined(transformKeysToSnake(data));
|
|
1319
|
+
return this.client.request("PATCH", `/agents/${agentId}`, updateData);
|
|
1320
|
+
}
|
|
1321
|
+
async deactivate(agentId) {
|
|
1322
|
+
return this.update(agentId, { status: "inactive" });
|
|
1323
|
+
}
|
|
1324
|
+
async delete(agentId) {
|
|
1325
|
+
await this.client.request("DELETE", `/agents/${agentId}`);
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
var ActionsAPI = class {
|
|
1329
|
+
constructor(client) {
|
|
1330
|
+
this.client = client;
|
|
1331
|
+
}
|
|
1332
|
+
async submit(data) {
|
|
1333
|
+
const requestData = removeUndefined({
|
|
1334
|
+
agent_id: data.agentId,
|
|
1335
|
+
action_type: data.actionType,
|
|
1336
|
+
resource: data.resource,
|
|
1337
|
+
payload: data.payload || {},
|
|
1338
|
+
metadata: data.metadata || {}
|
|
1339
|
+
});
|
|
1340
|
+
return this.client.request("POST", "/actions/evaluate", requestData);
|
|
1341
|
+
}
|
|
1342
|
+
async submitSandbox(data) {
|
|
1343
|
+
const requestData = removeUndefined({
|
|
1344
|
+
agent_id: data.agentId,
|
|
1345
|
+
action_type: data.actionType,
|
|
1346
|
+
resource: data.resource,
|
|
1347
|
+
payload: data.payload || {},
|
|
1348
|
+
metadata: data.metadata || {}
|
|
1349
|
+
});
|
|
1350
|
+
return this.client.request("POST", "/actions/sandbox", requestData);
|
|
1351
|
+
}
|
|
1352
|
+
async get(actionId) {
|
|
1353
|
+
return this.client.request("GET", `/actions/${actionId}`);
|
|
1354
|
+
}
|
|
1355
|
+
async list(params = {}) {
|
|
1356
|
+
const queryParams = removeUndefined({
|
|
1357
|
+
page: params.page,
|
|
1358
|
+
page_size: params.pageSize,
|
|
1359
|
+
agent_id: params.agentId,
|
|
1360
|
+
action_type: params.actionType,
|
|
1361
|
+
status: params.status,
|
|
1362
|
+
is_anomaly: params.isAnomaly,
|
|
1363
|
+
start_date: formatDate(params.startDate),
|
|
1364
|
+
end_date: formatDate(params.endDate)
|
|
1365
|
+
});
|
|
1366
|
+
return this.client.request("GET", "/actions", void 0, queryParams);
|
|
1367
|
+
}
|
|
1368
|
+
async complete(actionId, data) {
|
|
1369
|
+
const requestData = removeUndefined({
|
|
1370
|
+
status: data.status,
|
|
1371
|
+
execution_time_ms: data.executionTimeMs,
|
|
1372
|
+
error_message: data.errorMessage,
|
|
1373
|
+
result_metadata: data.resultMetadata || {}
|
|
1374
|
+
});
|
|
1375
|
+
return this.client.request("POST", `/actions/${actionId}/complete`, requestData);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
var PoliciesAPI = class {
|
|
1379
|
+
constructor(client) {
|
|
1380
|
+
this.client = client;
|
|
1381
|
+
}
|
|
1382
|
+
async list(params = {}) {
|
|
1383
|
+
const queryParams = removeUndefined({
|
|
1384
|
+
page: params.page,
|
|
1385
|
+
page_size: params.pageSize,
|
|
1386
|
+
enabled: params.enabled,
|
|
1387
|
+
policy_type: params.policyType
|
|
1388
|
+
});
|
|
1389
|
+
return this.client.request("GET", "/policies", void 0, queryParams);
|
|
1390
|
+
}
|
|
1391
|
+
async get(policyId) {
|
|
1392
|
+
return this.client.request("GET", `/policies/${policyId}`);
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
var AlertsAPI = class {
|
|
1396
|
+
constructor(client) {
|
|
1397
|
+
this.client = client;
|
|
1398
|
+
}
|
|
1399
|
+
async list(params = {}) {
|
|
1400
|
+
const queryParams = removeUndefined({
|
|
1401
|
+
page: params.page,
|
|
1402
|
+
page_size: params.pageSize,
|
|
1403
|
+
status: params.status,
|
|
1404
|
+
severity: params.severity,
|
|
1405
|
+
agent_id: params.agentId
|
|
1406
|
+
});
|
|
1407
|
+
return this.client.request("GET", "/alerts", void 0, queryParams);
|
|
1408
|
+
}
|
|
1409
|
+
async get(alertId) {
|
|
1410
|
+
return this.client.request("GET", `/alerts/${alertId}`);
|
|
1411
|
+
}
|
|
1412
|
+
async acknowledge(alertId) {
|
|
1413
|
+
return this.client.request("POST", `/alerts/${alertId}/acknowledge`);
|
|
1414
|
+
}
|
|
1415
|
+
async resolve(alertId, options) {
|
|
1416
|
+
const data = options?.notes ? { resolution_notes: options.notes } : {};
|
|
1417
|
+
return this.client.request("POST", `/alerts/${alertId}/resolve`, data);
|
|
1418
|
+
}
|
|
1419
|
+
async ignore(alertId) {
|
|
1420
|
+
return this.client.request("POST", `/alerts/${alertId}/ignore`);
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
var VaikoraClient = class {
|
|
1424
|
+
httpClient;
|
|
1425
|
+
defaultAgentId;
|
|
1426
|
+
agents;
|
|
1427
|
+
actions;
|
|
1428
|
+
policies;
|
|
1429
|
+
alerts;
|
|
1430
|
+
constructor(config) {
|
|
1431
|
+
const baseURL = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/$/, "") + "/api/v1";
|
|
1432
|
+
const timeout = config.timeout || DEFAULT_TIMEOUT;
|
|
1433
|
+
const retryCount = config.retryCount || DEFAULT_RETRY_COUNT;
|
|
1434
|
+
this.defaultAgentId = config.agentId;
|
|
1435
|
+
this.httpClient = axios.create({
|
|
1436
|
+
baseURL,
|
|
1437
|
+
timeout,
|
|
1438
|
+
headers: {
|
|
1439
|
+
"Authorization": `Bearer ${config.apiKey}`,
|
|
1440
|
+
"X-API-Key": config.apiKey,
|
|
1441
|
+
"Content-Type": "application/json",
|
|
1442
|
+
"User-Agent": "@vaikora/sdk/0.1.0"
|
|
1443
|
+
}
|
|
1444
|
+
});
|
|
1445
|
+
axiosRetry(this.httpClient, {
|
|
1446
|
+
retries: retryCount,
|
|
1447
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
1448
|
+
retryCondition: (error) => {
|
|
1449
|
+
return axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status === 429 || error.response?.status !== void 0 && error.response.status >= 500;
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
this.agents = new AgentsAPI(this);
|
|
1453
|
+
this.actions = new ActionsAPI(this);
|
|
1454
|
+
this.policies = new PoliciesAPI(this);
|
|
1455
|
+
this.alerts = new AlertsAPI(this);
|
|
1456
|
+
}
|
|
1457
|
+
/**
|
|
1458
|
+
* Make an HTTP request
|
|
1459
|
+
*/
|
|
1460
|
+
async request(method, path, data, params) {
|
|
1461
|
+
try {
|
|
1462
|
+
const response = await this.httpClient.request({
|
|
1463
|
+
method,
|
|
1464
|
+
url: path,
|
|
1465
|
+
data,
|
|
1466
|
+
params
|
|
1467
|
+
});
|
|
1468
|
+
if (response.status === 204) {
|
|
1469
|
+
return {};
|
|
1470
|
+
}
|
|
1471
|
+
return transformKeysToCamel(response.data);
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
if (error instanceof AxiosError) {
|
|
1474
|
+
if (error.code === "ECONNABORTED") {
|
|
1475
|
+
throw new TimeoutError(`Request timed out: ${error.message}`);
|
|
1476
|
+
}
|
|
1477
|
+
if (!error.response) {
|
|
1478
|
+
throw new NetworkError(`Network error: ${error.message}`);
|
|
1479
|
+
}
|
|
1480
|
+
const responseData = error.response.data || {};
|
|
1481
|
+
raiseForStatus(error.response.status, responseData);
|
|
1482
|
+
}
|
|
1483
|
+
throw error;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Get the default agent ID
|
|
1488
|
+
*/
|
|
1489
|
+
getDefaultAgentId() {
|
|
1490
|
+
return this.defaultAgentId;
|
|
1491
|
+
}
|
|
1492
|
+
};
|
|
1493
|
+
function secureAction(client, options) {
|
|
1494
|
+
return (fn) => {
|
|
1495
|
+
return async (...args) => {
|
|
1496
|
+
const agentId = options.agentId || client.getDefaultAgentId();
|
|
1497
|
+
if (!agentId) {
|
|
1498
|
+
throw new Error("agentId must be provided either to secureAction or client");
|
|
1499
|
+
}
|
|
1500
|
+
const startTime = Date.now();
|
|
1501
|
+
const result = await client.actions.submit({
|
|
1502
|
+
agentId,
|
|
1503
|
+
actionType: options.actionType,
|
|
1504
|
+
resource: options.resource,
|
|
1505
|
+
payload: { args },
|
|
1506
|
+
metadata: { function: fn.name || "anonymous" }
|
|
1507
|
+
});
|
|
1508
|
+
if (!result.approved) {
|
|
1509
|
+
throw new PolicyViolationError(
|
|
1510
|
+
result.denialReason || "Action denied by policy",
|
|
1511
|
+
result.policyId || void 0,
|
|
1512
|
+
void 0
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
const output = await fn(...args);
|
|
1517
|
+
const executionTimeMs = Date.now() - startTime;
|
|
1518
|
+
await client.actions.complete(result.actionId, {
|
|
1519
|
+
status: "executed",
|
|
1520
|
+
executionTimeMs
|
|
1521
|
+
});
|
|
1522
|
+
return output;
|
|
1523
|
+
} catch (err) {
|
|
1524
|
+
const executionTimeMs = Date.now() - startTime;
|
|
1525
|
+
await client.actions.complete(result.actionId, {
|
|
1526
|
+
status: "failed",
|
|
1527
|
+
executionTimeMs,
|
|
1528
|
+
errorMessage: err instanceof Error ? err.message : String(err)
|
|
1529
|
+
});
|
|
1530
|
+
throw err;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
var globalClient = null;
|
|
1536
|
+
var globalAgentId = null;
|
|
1537
|
+
function configure(apiKey, options = {}) {
|
|
1538
|
+
globalClient = new VaikoraClient({
|
|
1539
|
+
apiKey,
|
|
1540
|
+
baseUrl: options.baseUrl,
|
|
1541
|
+
timeout: options.timeout,
|
|
1542
|
+
retryCount: options.retryCount
|
|
1543
|
+
});
|
|
1544
|
+
return globalClient;
|
|
1545
|
+
}
|
|
1546
|
+
function getClient() {
|
|
1547
|
+
if (!globalClient) {
|
|
1548
|
+
throw new Error(
|
|
1549
|
+
"Vaikora client not configured. Call configure(apiKey) first."
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
return globalClient;
|
|
1553
|
+
}
|
|
1554
|
+
async function register(options) {
|
|
1555
|
+
const client = getClient();
|
|
1556
|
+
const agent = await client.agents.register({
|
|
1557
|
+
name: options.name,
|
|
1558
|
+
agentType: options.agentType || "autonomous",
|
|
1559
|
+
capabilities: options.capabilities || [],
|
|
1560
|
+
metadata: options.metadata || {}
|
|
1561
|
+
});
|
|
1562
|
+
globalAgentId = agent.id;
|
|
1563
|
+
return agent;
|
|
1564
|
+
}
|
|
1565
|
+
function getAgentId() {
|
|
1566
|
+
return globalAgentId;
|
|
1567
|
+
}
|
|
1568
|
+
function setAgentId(agentId) {
|
|
1569
|
+
globalAgentId = agentId;
|
|
1570
|
+
}
|
|
1571
|
+
async function validateData(data, options = {}) {
|
|
1572
|
+
const client = getClient();
|
|
1573
|
+
return client.request("POST", "/validate", {
|
|
1574
|
+
data,
|
|
1575
|
+
check_pii: options.checkPii ?? true,
|
|
1576
|
+
check_anomalies: options.checkAnomalies ?? true,
|
|
1577
|
+
check_toxicity: options.checkToxicity ?? true,
|
|
1578
|
+
auto_clean: options.autoClean ?? false
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
async function submitAction(options) {
|
|
1582
|
+
const client = getClient();
|
|
1583
|
+
const agentId = options.agentId || globalAgentId;
|
|
1584
|
+
if (!agentId) {
|
|
1585
|
+
throw new Error(
|
|
1586
|
+
"No agent ID available. Call register() first or provide agentId in options."
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
1589
|
+
return client.actions.submit({
|
|
1590
|
+
agentId,
|
|
1591
|
+
actionType: options.actionType,
|
|
1592
|
+
resource: options.resource,
|
|
1593
|
+
payload: options.payload || {},
|
|
1594
|
+
metadata: options.metadata || {}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
function wrapAction(options, fn) {
|
|
1598
|
+
const client = getClient();
|
|
1599
|
+
const effectiveOptions = {
|
|
1600
|
+
...options,
|
|
1601
|
+
agentId: options.agentId || globalAgentId || void 0
|
|
1602
|
+
};
|
|
1603
|
+
return secureAction(client, effectiveOptions)(fn);
|
|
1604
|
+
}
|
|
1605
|
+
var globalEmergencyController = null;
|
|
1606
|
+
function getEmergencyController() {
|
|
1607
|
+
if (!globalEmergencyController) {
|
|
1608
|
+
if (!globalAgentId) {
|
|
1609
|
+
throw new Error("Agent not registered. Call register() first.");
|
|
1610
|
+
}
|
|
1611
|
+
globalEmergencyController = new EmergencyController(globalAgentId);
|
|
1612
|
+
}
|
|
1613
|
+
return globalEmergencyController;
|
|
1614
|
+
}
|
|
1615
|
+
function freezeAgent(options) {
|
|
1616
|
+
const controller = getEmergencyController();
|
|
1617
|
+
return controller.freeze(options);
|
|
1618
|
+
}
|
|
1619
|
+
function resumeAgent(options = {}) {
|
|
1620
|
+
const controller = getEmergencyController();
|
|
1621
|
+
return controller.resume(options);
|
|
1622
|
+
}
|
|
1623
|
+
function isAgentFrozen() {
|
|
1624
|
+
try {
|
|
1625
|
+
const controller = getEmergencyController();
|
|
1626
|
+
return controller.isFrozen;
|
|
1627
|
+
} catch {
|
|
1628
|
+
return false;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function getFreezeState() {
|
|
1632
|
+
try {
|
|
1633
|
+
const controller = getEmergencyController();
|
|
1634
|
+
return controller.getFreezeState();
|
|
1635
|
+
} catch {
|
|
1636
|
+
return null;
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
export {
|
|
1640
|
+
AgentFrozenError,
|
|
1641
|
+
AgentIdentity,
|
|
1642
|
+
ApprovalDeniedException,
|
|
1643
|
+
ApprovalRequiredException,
|
|
1644
|
+
ApprovalStatus,
|
|
1645
|
+
AuthenticationError,
|
|
1646
|
+
AuthorizationError,
|
|
1647
|
+
CircuitBreaker,
|
|
1648
|
+
CircuitBreakerOpenError,
|
|
1649
|
+
CircuitState,
|
|
1650
|
+
ConflictError,
|
|
1651
|
+
ContextLevel,
|
|
1652
|
+
EmergencyController,
|
|
1653
|
+
ExecutionContext,
|
|
1654
|
+
ExecutionMode,
|
|
1655
|
+
FreezeReason,
|
|
1656
|
+
FreezeScope,
|
|
1657
|
+
InterceptorAction,
|
|
1658
|
+
NetworkError,
|
|
1659
|
+
NotFoundError,
|
|
1660
|
+
PolicyDeniedException,
|
|
1661
|
+
PolicyViolationError,
|
|
1662
|
+
RateLimitError,
|
|
1663
|
+
ServerError,
|
|
1664
|
+
TimeoutError,
|
|
1665
|
+
VaikoraClient,
|
|
1666
|
+
VaikoraError,
|
|
1667
|
+
VaikoraInterceptor,
|
|
1668
|
+
ValidationError,
|
|
1669
|
+
ValidationException,
|
|
1670
|
+
assertNotFrozen,
|
|
1671
|
+
clearContext,
|
|
1672
|
+
configure,
|
|
1673
|
+
createInterceptor,
|
|
1674
|
+
freezeAgent,
|
|
1675
|
+
frozenGuard,
|
|
1676
|
+
getAgentId,
|
|
1677
|
+
getClient,
|
|
1678
|
+
getCurrentContext,
|
|
1679
|
+
getFreezeState,
|
|
1680
|
+
interceptor,
|
|
1681
|
+
isAgentFrozen,
|
|
1682
|
+
raiseForStatus,
|
|
1683
|
+
register,
|
|
1684
|
+
resumeAgent,
|
|
1685
|
+
secureAction,
|
|
1686
|
+
setAgentId,
|
|
1687
|
+
setCurrentContext,
|
|
1688
|
+
submitAction,
|
|
1689
|
+
validateData,
|
|
1690
|
+
withContext,
|
|
1691
|
+
withContextScope,
|
|
1692
|
+
wrapAction
|
|
1693
|
+
};
|