@vantinelai/node-sdk 0.4.5

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/dist/client.js ADDED
@@ -0,0 +1,431 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.MODEL_PRICING = exports.VantinelClient = void 0;
40
+ exports.estimateCostFromTokens = estimateCostFromTokens;
41
+ exports.estimateCostFromText = estimateCostFromText;
42
+ exports.estimateTokens = estimateTokens;
43
+ const axios_1 = __importDefault(require("axios"));
44
+ const security_1 = require("./security");
45
+ /**
46
+ * Rough token estimation: ~4 characters per token.
47
+ */
48
+ function estimateTokens(text) {
49
+ return Math.ceil(text.length / 4);
50
+ }
51
+ const DEFAULT_COST_PER_1K_TOKENS = 0.01;
52
+ /**
53
+ * Model pricing per 1k tokens (input, output, cache_read) in USD.
54
+ */
55
+ const MODEL_PRICING = {
56
+ // OpenAI 2026 Models
57
+ 'gpt-5.2': { input: 0.00175, output: 0.014 },
58
+ 'gpt-5.2-pro': { input: 0.021, output: 0.168 },
59
+ 'gpt-5-mini': { input: 0.00025, output: 0.002 },
60
+ // OpenAI Legacy
61
+ 'gpt-4o': { input: 0.0025, output: 0.010 },
62
+ 'gpt-4o-2024-05-13': { input: 0.005, output: 0.015 },
63
+ 'gpt-4o-2024-08-06': { input: 0.0025, output: 0.010 },
64
+ 'gpt-4o-2024-11-20': { input: 0.0025, output: 0.010 },
65
+ 'gpt-4o-mini': { input: 0.00015, output: 0.0006 },
66
+ 'o1': { input: 0.015, output: 0.060 },
67
+ 'o3-mini': { input: 0.0011, output: 0.0044 },
68
+ 'gpt-4-turbo': { input: 0.01, output: 0.03 },
69
+ // Anthropic 2026 Models
70
+ 'claude-4.6-opus': { input: 0.005, output: 0.025 },
71
+ 'claude-4.6-sonnet': { input: 0.003, output: 0.015 },
72
+ 'claude-4.5-opus': { input: 0.005, output: 0.025 },
73
+ 'claude-4.5-sonnet': { input: 0.003, output: 0.015 },
74
+ 'claude-4.5-haiku': { input: 0.001, output: 0.005 },
75
+ // Anthropic Legacy
76
+ 'claude-3-5-sonnet-20241022': { input: 0.003, output: 0.015 },
77
+ 'claude-3-5-haiku-20241022': { input: 0.0008, output: 0.004 },
78
+ 'claude-3-opus': { input: 0.015, output: 0.075 },
79
+ // Google Models 2026
80
+ 'gemini-3.1-pro': { input: 0.002, output: 0.012 },
81
+ 'gemini-3.0-pro': { input: 0.002, output: 0.012 },
82
+ 'gemini-3-flash': { input: 0.0005, output: 0.003 },
83
+ 'gemini-2.5-flash': { input: 0.000075, output: 0.0003 },
84
+ 'gemini-2.0-flash': { input: 0.0001, output: 0.0004 },
85
+ 'gemini-1.5-pro': { input: 0.00125, output: 0.005 },
86
+ };
87
+ exports.MODEL_PRICING = MODEL_PRICING;
88
+ function estimateCostFromText(text) {
89
+ const tokens = estimateTokens(text);
90
+ return (tokens / 1000) * DEFAULT_COST_PER_1K_TOKENS;
91
+ }
92
+ function estimateCostFromTokens(model, inputTokens, outputTokens, cachedTokens = 0) {
93
+ const pricing = MODEL_PRICING[model];
94
+ if (pricing) {
95
+ const regularInputTokens = Math.max(0, inputTokens - cachedTokens);
96
+ const cacheReadPrice = pricing.cache_read !== undefined ? pricing.cache_read : pricing.input * 0.5;
97
+ return (regularInputTokens / 1000) * pricing.input +
98
+ (cachedTokens / 1000) * cacheReadPrice +
99
+ (outputTokens / 1000) * pricing.output;
100
+ }
101
+ return ((inputTokens + outputTokens) / 1000) * DEFAULT_COST_PER_1K_TOKENS;
102
+ }
103
+ class VantinelClient {
104
+ constructor(config) {
105
+ this.config = config;
106
+ this.batchQueue = [];
107
+ this.flushTimer = null;
108
+ this.globalMetadata = {};
109
+ const validatedUrl = (0, security_1.validateCollectorUrl)(config.collectorUrl || 'http://localhost:8000');
110
+ this.client = axios_1.default.create({
111
+ baseURL: validatedUrl,
112
+ timeout: 2000,
113
+ });
114
+ const interval = config.flushInterval ?? 0;
115
+ if (interval > 0) {
116
+ this.flushTimer = setInterval(() => {
117
+ this.flush().catch((err) => {
118
+ console.warn('[Vantinel] Background flush failed:', err.message);
119
+ });
120
+ }, interval);
121
+ // Prevent the timer from keeping the Node.js process alive
122
+ if (this.flushTimer.unref) {
123
+ this.flushTimer.unref();
124
+ }
125
+ }
126
+ }
127
+ setGlobalMetadata(metadata) {
128
+ this.globalMetadata = { ...this.globalMetadata, ...metadata };
129
+ }
130
+ mergeGlobalMetadata(event) {
131
+ if (Object.keys(this.globalMetadata).length === 0)
132
+ return event;
133
+ return {
134
+ ...event,
135
+ metadata: { ...this.globalMetadata, ...(event.metadata ?? {}) },
136
+ };
137
+ }
138
+ async sendWithRetry(events) {
139
+ const maxRetries = this.config.retry?.maxRetries ?? 0;
140
+ const backoffMs = this.config.retry?.backoffMs ?? 100;
141
+ const payload = events.length === 1 ? events[0] : events;
142
+ const body = JSON.stringify(payload);
143
+ const timestamp = Date.now();
144
+ const nonce = (0, security_1.generateNonce)();
145
+ const signature = (0, security_1.hmacSign)(this.config.apiKey || '', timestamp, body, nonce);
146
+ const headers = {
147
+ 'Content-Type': 'application/json',
148
+ 'X-Vantinel-API-Key': this.config.apiKey || '',
149
+ 'X-Vantinel-Signature': signature,
150
+ 'X-Vantinel-Timestamp': String(timestamp),
151
+ 'X-Vantinel-Nonce': nonce,
152
+ 'X-Vantinel-Client': this.config.clientId || '',
153
+ };
154
+ let lastError = null;
155
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
156
+ if (attempt > 0) {
157
+ await new Promise((resolve) => setTimeout(resolve, backoffMs * attempt));
158
+ }
159
+ try {
160
+ const response = await this.client.post('/v1/events', body, { headers });
161
+ if (Array.isArray(payload)) {
162
+ // For batch sends, use the server's aggregate decision if available
163
+ const data = response.data;
164
+ if (data && typeof data.decision === 'string') {
165
+ return data;
166
+ }
167
+ // If server returns an array of decisions, use the most restrictive one
168
+ if (Array.isArray(data)) {
169
+ const dominated = data.find((d) => d.decision === 'block');
170
+ if (dominated)
171
+ return dominated;
172
+ const approval = data.find((d) => d.decision === 'require_approval');
173
+ if (approval)
174
+ return approval;
175
+ }
176
+ return { decision: 'allow' };
177
+ }
178
+ return response.data;
179
+ }
180
+ catch (error) {
181
+ const status = error.response?.status;
182
+ // Retry on network errors or 5xx
183
+ if (!status || status >= 500) {
184
+ lastError = error;
185
+ continue;
186
+ }
187
+ // Non-retryable error (4xx etc.)
188
+ console.warn('[Vantinel] Non-retryable error from collector:', error.message);
189
+ if (this.config.failMode === 'closed') {
190
+ return { decision: 'block', message: 'Vantinel Gateway returned non-retryable error and failMode is closed.' };
191
+ }
192
+ return { decision: 'allow' };
193
+ }
194
+ }
195
+ console.warn('[Vantinel] Failed to contact collector after retries:', lastError?.message);
196
+ if (this.config.failMode === 'closed') {
197
+ return { decision: 'block', message: 'Vantinel Gateway is unreachable and failMode is closed.' };
198
+ }
199
+ return { decision: 'allow' };
200
+ }
201
+ /**
202
+ * Send an event to the collector.
203
+ * Cost is only included if explicitly set on the event.
204
+ */
205
+ async sendEvent(event) {
206
+ const enriched = this.mergeGlobalMetadata(event);
207
+ if (this.config.dryRun) {
208
+ console.log('[Vantinel DryRun] Event:', enriched);
209
+ return { decision: 'allow' };
210
+ }
211
+ const batchSize = this.config.batchSize ?? 1;
212
+ if (batchSize <= 1) {
213
+ // No batching — send immediately
214
+ return this.sendWithRetry([enriched]);
215
+ }
216
+ // Batching mode — buffer and flush when full
217
+ return new Promise((resolve) => {
218
+ this.batchQueue.push(enriched);
219
+ if (this.batchQueue.length >= batchSize) {
220
+ const batch = this.batchQueue.splice(0, batchSize);
221
+ this.sendWithRetry(batch)
222
+ .then((decision) => resolve(decision))
223
+ .catch(() => resolve(this.config.failMode === 'closed' ? { decision: 'block', message: 'Failed to send batch and failMode is closed.' } : { decision: 'allow' }));
224
+ }
225
+ else {
226
+ // Resolve immediately with allow; batch will be sent when full or on flush()
227
+ resolve({ decision: 'allow' });
228
+ }
229
+ });
230
+ }
231
+ /**
232
+ * Wrap a tool function — automatically measures latency and tracks it.
233
+ * This is the recommended way to use Vantinel.
234
+ *
235
+ * Cost is only reported if you explicitly provide it (e.g., from your LLM provider's usage data).
236
+ * Latency is always automatically measured.
237
+ *
238
+ * @example
239
+ * const result = await client.wrap('search_db', '{"query":"test"}', async () => {
240
+ * return await searchDatabase('test');
241
+ * });
242
+ */
243
+ async wrap(toolName, toolArgs, fn, options) {
244
+ const { createHash } = await Promise.resolve().then(() => __importStar(require('crypto')));
245
+ const hash = createHash('sha256').update(`${toolName}:${toolArgs}`).digest('hex').slice(0, 32);
246
+ const sessionId = options?.sessionId || `session_${Date.now()}`;
247
+ // Pre-check with the gateway
248
+ const preEvent = {
249
+ event_type: 'tool_call',
250
+ session_id: sessionId,
251
+ agent_id: this.config.agentId,
252
+ tool_name: toolName,
253
+ tool_args_hash: hash,
254
+ timestamp: Date.now(),
255
+ estimated_cost: options?.estimatedCost,
256
+ trace_id: options?.traceId,
257
+ };
258
+ const decision = await this.sendEvent(preEvent);
259
+ if (decision.decision === 'block') {
260
+ throw new Error(`[Vantinel] Tool call blocked: ${toolName} — ${decision.message || 'Policy violation'}`);
261
+ }
262
+ // Execute and time
263
+ const startTime = process.hrtime.bigint();
264
+ try {
265
+ const result = await fn();
266
+ const latencyMs = Number(process.hrtime.bigint() - startTime) / 1000000;
267
+ // Send completion with latency
268
+ const completionEvent = {
269
+ ...preEvent,
270
+ latency_ms: latencyMs,
271
+ timestamp: Date.now(),
272
+ };
273
+ await this.sendEvent(completionEvent).catch(() => { });
274
+ return result;
275
+ }
276
+ catch (err) {
277
+ const latencyMs = Number(process.hrtime.bigint() - startTime) / 1000000;
278
+ // Report error with latency
279
+ const errorEvent = {
280
+ ...preEvent,
281
+ event_type: 'tool_error',
282
+ latency_ms: latencyMs,
283
+ timestamp: Date.now(),
284
+ metadata: {
285
+ error_message: err instanceof Error ? err.message : String(err),
286
+ },
287
+ };
288
+ await this.sendEvent(errorEvent).catch(() => { });
289
+ throw err;
290
+ }
291
+ }
292
+ /**
293
+ * Auto-instrument an OpenAI client.
294
+ * This monkey-patches `chat.completions.create` to automatically intercept calls,
295
+ * measure latency, extract exact token usage from the response, and calculate true cost.
296
+ *
297
+ * @param openaiClient - The instantiated OpenAI client (e.g., `new OpenAI()`)
298
+ * @param options - Optional configuration for the intercepted calls
299
+ * @returns The patched OpenAI client
300
+ */
301
+ wrapOpenAI(openaiClient, options) {
302
+ if (!openaiClient?.chat?.completions?.create) {
303
+ console.warn('[Vantinel] Provided client does not look like an OpenAI client. wrapOpenAI failed.');
304
+ return openaiClient;
305
+ }
306
+ const originalCreate = openaiClient.chat.completions.create.bind(openaiClient.chat.completions);
307
+ openaiClient.chat.completions.create = async (body, reqOptions) => {
308
+ const toolName = 'openai_chat';
309
+ const argsText = typeof body === 'string' ? body : JSON.stringify(body);
310
+ const sessionId = options?.sessionId || `session_${Date.now()}`;
311
+ const { createHash } = await Promise.resolve().then(() => __importStar(require('crypto')));
312
+ const hash = createHash('sha256').update(`${toolName}:${argsText}`).digest('hex').slice(0, 32);
313
+ const isStream = body?.stream === true || reqOptions?.stream === true;
314
+ // Clone to avoid mutating the caller's object
315
+ const modifiedBody = isStream && !body.stream_options
316
+ ? { ...body, stream_options: { include_usage: true } }
317
+ : body;
318
+ const preEvent = {
319
+ event_type: 'tool_call',
320
+ session_id: sessionId,
321
+ agent_id: this.config.agentId,
322
+ tool_name: toolName,
323
+ tool_args_hash: hash,
324
+ timestamp: Date.now(),
325
+ trace_id: options?.traceId,
326
+ };
327
+ const decision = await this.sendEvent(preEvent);
328
+ if (decision.decision === 'block') {
329
+ throw new Error(`[Vantinel] Tool call blocked: ${toolName} — ${decision.message || 'Policy violation'}`);
330
+ }
331
+ const startTime = process.hrtime.bigint();
332
+ try {
333
+ const response = await originalCreate(modifiedBody, reqOptions);
334
+ if (isStream) {
335
+ const self = this;
336
+ async function* wrapper() {
337
+ let finalUsage = null;
338
+ let finalModel = body.model;
339
+ try {
340
+ for await (const chunk of response) {
341
+ if (chunk.usage)
342
+ finalUsage = chunk.usage;
343
+ if (chunk.model)
344
+ finalModel = chunk.model;
345
+ yield chunk;
346
+ }
347
+ }
348
+ finally {
349
+ const latencyMs = Number(process.hrtime.bigint() - startTime) / 1000000;
350
+ let estimatedCost = undefined;
351
+ if (finalUsage && finalModel) {
352
+ const cachedTokens = finalUsage.prompt_tokens_details?.cached_tokens || 0;
353
+ estimatedCost = estimateCostFromTokens(finalModel, finalUsage.prompt_tokens || 0, finalUsage.completion_tokens || 0, cachedTokens);
354
+ }
355
+ const completionEvent = {
356
+ ...preEvent,
357
+ latency_ms: latencyMs,
358
+ estimated_cost: estimatedCost,
359
+ timestamp: Date.now(),
360
+ };
361
+ self.sendEvent(completionEvent).catch(() => { });
362
+ }
363
+ }
364
+ return wrapper();
365
+ }
366
+ const latencyMs = Number(process.hrtime.bigint() - startTime) / 1000000;
367
+ // Calculate exact cost from the actual usage reported by OpenAI!
368
+ let estimatedCost = undefined;
369
+ if (response.usage && response.model) {
370
+ const cachedTokens = response.usage.prompt_tokens_details?.cached_tokens || 0;
371
+ estimatedCost = estimateCostFromTokens(response.model, response.usage.prompt_tokens || 0, response.usage.completion_tokens || 0, cachedTokens);
372
+ }
373
+ const completionEvent = {
374
+ ...preEvent,
375
+ latency_ms: latencyMs,
376
+ estimated_cost: estimatedCost,
377
+ timestamp: Date.now(),
378
+ };
379
+ await this.sendEvent(completionEvent).catch(() => { });
380
+ return response;
381
+ }
382
+ catch (err) {
383
+ const latencyMs = Number(process.hrtime.bigint() - startTime) / 1000000;
384
+ const errorEvent = {
385
+ ...preEvent,
386
+ event_type: 'tool_error',
387
+ latency_ms: latencyMs,
388
+ timestamp: Date.now(),
389
+ metadata: {
390
+ error_message: err instanceof Error ? err.message : String(err),
391
+ },
392
+ };
393
+ await this.sendEvent(errorEvent).catch(() => { });
394
+ throw err;
395
+ }
396
+ };
397
+ return openaiClient;
398
+ }
399
+ /**
400
+ * Gracefully shut down the client: flush pending events and clear timers.
401
+ */
402
+ async destroy() {
403
+ if (this.flushTimer) {
404
+ clearInterval(this.flushTimer);
405
+ this.flushTimer = null;
406
+ }
407
+ await this.flush();
408
+ }
409
+ async flush() {
410
+ if (this.batchQueue.length === 0)
411
+ return;
412
+ const batch = this.batchQueue.splice(0, this.batchQueue.length);
413
+ try {
414
+ await this.sendWithRetry(batch);
415
+ }
416
+ catch {
417
+ // Errors are already logged inside sendWithRetry
418
+ }
419
+ }
420
+ async ping() {
421
+ const start = Date.now();
422
+ try {
423
+ await this.client.get('/health');
424
+ return { ok: true, latencyMs: Date.now() - start };
425
+ }
426
+ catch {
427
+ return { ok: false, latencyMs: Date.now() - start };
428
+ }
429
+ }
430
+ }
431
+ exports.VantinelClient = VantinelClient;
@@ -0,0 +1,5 @@
1
+ export * from './client';
2
+ export * from './monitor';
3
+ export { validateCollectorUrl, redactApiKey } from './security';
4
+ export { patchOpenAIAgents, VantinelTracingProcessor } from './integrations/openai-agents';
5
+ export { wrapAnthropic } from './integrations/anthropic';
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.wrapAnthropic = exports.VantinelTracingProcessor = exports.patchOpenAIAgents = exports.redactApiKey = exports.validateCollectorUrl = void 0;
18
+ __exportStar(require("./client"), exports);
19
+ __exportStar(require("./monitor"), exports);
20
+ var security_1 = require("./security");
21
+ Object.defineProperty(exports, "validateCollectorUrl", { enumerable: true, get: function () { return security_1.validateCollectorUrl; } });
22
+ Object.defineProperty(exports, "redactApiKey", { enumerable: true, get: function () { return security_1.redactApiKey; } });
23
+ // Framework integrations
24
+ var openai_agents_1 = require("./integrations/openai-agents");
25
+ Object.defineProperty(exports, "patchOpenAIAgents", { enumerable: true, get: function () { return openai_agents_1.patchOpenAIAgents; } });
26
+ Object.defineProperty(exports, "VantinelTracingProcessor", { enumerable: true, get: function () { return openai_agents_1.VantinelTracingProcessor; } });
27
+ var anthropic_1 = require("./integrations/anthropic");
28
+ Object.defineProperty(exports, "wrapAnthropic", { enumerable: true, get: function () { return anthropic_1.wrapAnthropic; } });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Anthropic SDK integration for Vantinel.
3
+ *
4
+ * Usage:
5
+ * import Anthropic from '@anthropic-ai/sdk';
6
+ * import { VantinelClient } from '@vantinelai/node-sdk';
7
+ * import { wrapAnthropic } from '@vantinelai/node-sdk/integrations/anthropic';
8
+ *
9
+ * const client = new VantinelClient({ apiKey: '...', clientId: '...' });
10
+ * const anthropic = wrapAnthropic(client, new Anthropic());
11
+ *
12
+ * const response = await anthropic.messages.create({
13
+ * model: 'claude-sonnet-4-6',
14
+ * max_tokens: 1024,
15
+ * messages: [{ role: 'user', content: 'Hello' }],
16
+ * });
17
+ */
18
+ import type { VantinelClient } from '../client';
19
+ interface AnthropicUsage {
20
+ input_tokens?: number;
21
+ output_tokens?: number;
22
+ cache_creation_input_tokens?: number;
23
+ cache_read_input_tokens?: number;
24
+ }
25
+ interface AnthropicMessage {
26
+ role: string;
27
+ content: unknown;
28
+ }
29
+ interface AnthropicCreateParams {
30
+ model: string;
31
+ messages: AnthropicMessage[];
32
+ stream?: boolean;
33
+ max_tokens?: number;
34
+ [key: string]: unknown;
35
+ }
36
+ interface AnthropicResponse {
37
+ usage?: AnthropicUsage;
38
+ stop_reason?: string;
39
+ content?: Array<{
40
+ type: string;
41
+ name?: string;
42
+ }>;
43
+ [key: string]: unknown;
44
+ }
45
+ interface AnthropicClient {
46
+ messages: {
47
+ create(params: AnthropicCreateParams): Promise<AnthropicResponse>;
48
+ [key: string]: unknown;
49
+ };
50
+ [key: string]: unknown;
51
+ }
52
+ /**
53
+ * Wrap an Anthropic client to auto-monitor all messages.create() calls.
54
+ *
55
+ * @param vantinelClient - VantinelClient instance
56
+ * @param anthropicClient - anthropic.Anthropic() instance
57
+ * @param options - Optional session/agent configuration
58
+ * @returns The patched Anthropic client
59
+ */
60
+ export declare function wrapAnthropic(vantinelClient: VantinelClient, anthropicClient: AnthropicClient, options?: {
61
+ sessionId?: string;
62
+ agentId?: string;
63
+ }): AnthropicClient;
64
+ export {};
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * Anthropic SDK integration for Vantinel.
4
+ *
5
+ * Usage:
6
+ * import Anthropic from '@anthropic-ai/sdk';
7
+ * import { VantinelClient } from '@vantinelai/node-sdk';
8
+ * import { wrapAnthropic } from '@vantinelai/node-sdk/integrations/anthropic';
9
+ *
10
+ * const client = new VantinelClient({ apiKey: '...', clientId: '...' });
11
+ * const anthropic = wrapAnthropic(client, new Anthropic());
12
+ *
13
+ * const response = await anthropic.messages.create({
14
+ * model: 'claude-sonnet-4-6',
15
+ * max_tokens: 1024,
16
+ * messages: [{ role: 'user', content: 'Hello' }],
17
+ * });
18
+ */
19
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ var desc = Object.getOwnPropertyDescriptor(m, k);
22
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
23
+ desc = { enumerable: true, get: function() { return m[k]; } };
24
+ }
25
+ Object.defineProperty(o, k2, desc);
26
+ }) : (function(o, m, k, k2) {
27
+ if (k2 === undefined) k2 = k;
28
+ o[k2] = m[k];
29
+ }));
30
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
31
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
32
+ }) : function(o, v) {
33
+ o["default"] = v;
34
+ });
35
+ var __importStar = (this && this.__importStar) || (function () {
36
+ var ownKeys = function(o) {
37
+ ownKeys = Object.getOwnPropertyNames || function (o) {
38
+ var ar = [];
39
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
40
+ return ar;
41
+ };
42
+ return ownKeys(o);
43
+ };
44
+ return function (mod) {
45
+ if (mod && mod.__esModule) return mod;
46
+ var result = {};
47
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
48
+ __setModuleDefault(result, mod);
49
+ return result;
50
+ };
51
+ })();
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.wrapAnthropic = wrapAnthropic;
54
+ const crypto = __importStar(require("crypto"));
55
+ const client_1 = require("../client");
56
+ function buildArgsHash(model, messages) {
57
+ return crypto
58
+ .createHash('sha256')
59
+ .update(JSON.stringify({ model, messages_count: messages.length, first_msg: messages[0]?.content }))
60
+ .digest('hex')
61
+ .slice(0, 32);
62
+ }
63
+ function extractCost(model, usage) {
64
+ if (!usage)
65
+ return undefined;
66
+ const inputTokens = (usage.input_tokens ?? 0) + (usage.cache_creation_input_tokens ?? 0);
67
+ const outputTokens = usage.output_tokens ?? 0;
68
+ const cachedTokens = usage.cache_read_input_tokens ?? 0;
69
+ return (0, client_1.estimateCostFromTokens)(model, inputTokens + cachedTokens, outputTokens, cachedTokens);
70
+ }
71
+ /**
72
+ * Wrap an Anthropic client to auto-monitor all messages.create() calls.
73
+ *
74
+ * @param vantinelClient - VantinelClient instance
75
+ * @param anthropicClient - anthropic.Anthropic() instance
76
+ * @param options - Optional session/agent configuration
77
+ * @returns The patched Anthropic client
78
+ */
79
+ function wrapAnthropic(vantinelClient, anthropicClient, options) {
80
+ const originalCreate = anthropicClient.messages.create.bind(anthropicClient.messages);
81
+ const sessionId = options?.sessionId ?? `anthropic_${Date.now()}`;
82
+ anthropicClient.messages.create = async (params) => {
83
+ const { model, messages } = params;
84
+ const toolName = `anthropic_messages_${model}`;
85
+ const argsHash = buildArgsHash(model, messages);
86
+ // Pre-call: send event to get decision
87
+ let decision;
88
+ try {
89
+ const resp = await vantinelClient.sendEvent({
90
+ event_type: 'tool_call',
91
+ tool_name: toolName,
92
+ tool_args_hash: argsHash,
93
+ session_id: sessionId,
94
+ agent_id: options?.agentId,
95
+ timestamp: Date.now(),
96
+ metadata: { model, messages_count: messages.length, framework: 'anthropic' },
97
+ });
98
+ decision = resp?.decision;
99
+ }
100
+ catch {
101
+ // Fail open: don't block the call if Vantinel is unavailable
102
+ }
103
+ if (decision === 'block') {
104
+ throw new Error(`Vantinel blocked Anthropic call: ${toolName}`);
105
+ }
106
+ const startTime = Date.now();
107
+ const result = await originalCreate(params);
108
+ const latencyMs = Date.now() - startTime;
109
+ const usage = result.usage;
110
+ const cost = extractCost(model, usage);
111
+ const toolUses = result.content
112
+ ?.filter((b) => b.type === 'tool_use')
113
+ .map((b) => b.name)
114
+ .filter(Boolean) ?? [];
115
+ // Post-call telemetry (fire-and-forget)
116
+ void vantinelClient.sendEvent({
117
+ event_type: 'tool_result',
118
+ tool_name: toolName,
119
+ tool_args_hash: argsHash,
120
+ session_id: sessionId,
121
+ agent_id: options?.agentId,
122
+ timestamp: Date.now(),
123
+ estimated_cost: cost,
124
+ latency_ms: latencyMs,
125
+ metadata: {
126
+ model,
127
+ messages_count: messages.length,
128
+ framework: 'anthropic',
129
+ stop_reason: result.stop_reason,
130
+ tool_uses: toolUses,
131
+ input_tokens: usage?.input_tokens,
132
+ output_tokens: usage?.output_tokens,
133
+ },
134
+ }).catch(() => { });
135
+ return result;
136
+ };
137
+ return anthropicClient;
138
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Vantinel integrations for major AI frameworks.
3
+ */
4
+ export { patchOpenAIAgents, VantinelTracingProcessor } from './openai-agents';
5
+ export { wrapAnthropic } from './anthropic';