@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/README.md +189 -0
- package/dist/client.d.ts +105 -0
- package/dist/client.js +431 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +28 -0
- package/dist/integrations/anthropic.d.ts +64 -0
- package/dist/integrations/anthropic.js +138 -0
- package/dist/integrations/index.d.ts +5 -0
- package/dist/integrations/index.js +11 -0
- package/dist/integrations/openai-agents.d.ts +70 -0
- package/dist/integrations/openai-agents.js +108 -0
- package/dist/monitor.d.ts +41 -0
- package/dist/monitor.js +308 -0
- package/dist/security.d.ts +17 -0
- package/dist/security.js +82 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @vantinel/node-sdk
|
|
2
|
+
|
|
3
|
+
Node.js / Server-side SDK for [Vantinel](https://vantinel.ai) — real-time AI agent observability & guardrails.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @vantinel/node-sdk
|
|
9
|
+
# or
|
|
10
|
+
yarn add @vantinel/node-sdk
|
|
11
|
+
# or
|
|
12
|
+
pnpm add @vantinel/node-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { VantinelMonitor } from '@vantinel/node-sdk';
|
|
19
|
+
|
|
20
|
+
const monitor = new VantinelMonitor({
|
|
21
|
+
apiKey: process.env.VANTINEL_API_KEY,
|
|
22
|
+
clientId: 'my-company',
|
|
23
|
+
agentId: 'customer-support-bot',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Wrap any tool function — one line
|
|
27
|
+
const search = monitor.monitor('search_database', async (query: string) => {
|
|
28
|
+
return db.query(query);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Use as normal — monitoring is transparent
|
|
32
|
+
const results = await search('SELECT * FROM users');
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
| Option | Type | Default | Description |
|
|
38
|
+
|---|---|---|---|
|
|
39
|
+
| `apiKey` | `string` | `$VANTINEL_API_KEY` | Your Vantinel API key |
|
|
40
|
+
| `clientId` | `string` | `$VANTINEL_CLIENT_ID` | Your organization ID |
|
|
41
|
+
| `agentId` | `string` | `'default-agent'` | Identifier for this agent |
|
|
42
|
+
| `collectorUrl` | `string` | `http://localhost:8000` | Vantinel Collector endpoint |
|
|
43
|
+
| `dryRun` | `boolean` | `false` | Log events without sending (useful in CI) |
|
|
44
|
+
| `shadowMode` | `boolean` | `false` | Detect threats but never block; log what *would* have happened |
|
|
45
|
+
| `batchSize` | `number` | `1` | Buffer N events before sending (reduces HTTP overhead) |
|
|
46
|
+
| `flushInterval` | `number` | `0` | Auto-flush interval in milliseconds (0 = disabled) |
|
|
47
|
+
| `retry.maxRetries` | `number` | `0` | Retry on 5xx/network errors |
|
|
48
|
+
| `retry.backoffMs` | `number` | `100` | Base backoff between retries |
|
|
49
|
+
| `slackWebhookUrl` | `string` | — | Shadow Mode Slack alerts webhook |
|
|
50
|
+
|
|
51
|
+
All options also read from environment variables (`VANTINEL_API_KEY`, `VANTINEL_CLIENT_ID`, `VANTINEL_DRY_RUN`, `VANTINEL_SHADOW_MODE`, etc.).
|
|
52
|
+
|
|
53
|
+
## API
|
|
54
|
+
|
|
55
|
+
### `monitor.monitor(toolName, fn, options?)`
|
|
56
|
+
|
|
57
|
+
Wraps a function for monitoring. Returns the same function, transparently instrumented.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
const wrappedFn = monitor.monitor('send_email', sendEmail, {
|
|
61
|
+
traceId: monitor.startTrace(), // correlate with browser events
|
|
62
|
+
skip: false, // set true to skip monitoring for this call
|
|
63
|
+
costCalculator: (result) => ({ // extract cost from AI API response
|
|
64
|
+
estimated_cost: result.usage.total_tokens * 0.00001,
|
|
65
|
+
metadata: { model: 'gpt-4', tokens: result.usage.total_tokens },
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Decisions:** If the Collector returns `block`, an error is thrown. All other decisions allow execution.
|
|
71
|
+
|
|
72
|
+
### `monitor.wrapOpenAI(openaiClient)`
|
|
73
|
+
|
|
74
|
+
Zero-config monitoring for all OpenAI chat completions:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
import OpenAI from 'openai';
|
|
78
|
+
|
|
79
|
+
const openai = monitor.wrapOpenAI(new OpenAI());
|
|
80
|
+
// All openai.chat.completions.create() calls are now monitored
|
|
81
|
+
const response = await openai.chat.completions.create({ model: 'gpt-4', messages: [...] });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `monitor.wrapLangChain(chain)`
|
|
85
|
+
|
|
86
|
+
Zero-config monitoring for LangChain chains (invoke, call, run, stream):
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const chain = prompt.pipe(llm).pipe(parser);
|
|
90
|
+
const monitored = monitor.wrapLangChain(chain);
|
|
91
|
+
const result = await monitored.invoke({ question: 'What is 2+2?' });
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `monitor.captureError(toolName, error, metadata?)`
|
|
95
|
+
|
|
96
|
+
Report a tool failure to the Collector:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
try {
|
|
100
|
+
await myTool();
|
|
101
|
+
} catch (err) {
|
|
102
|
+
await monitor.captureError('my_tool', err, { retry: 1, context: 'user-flow' });
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### `monitor.setGlobalMetadata(metadata)`
|
|
108
|
+
|
|
109
|
+
Attach key-value metadata to every subsequent event (merge, not replace):
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
monitor.setGlobalMetadata({ userId: 'user_123', tenantId: 'acme-corp' });
|
|
113
|
+
monitor.setGlobalMetadata({ environment: 'production' }); // merged
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### `monitor.startTrace()`
|
|
117
|
+
|
|
118
|
+
Generate a UUID trace ID to correlate frontend and backend events:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const traceId = monitor.startTrace();
|
|
122
|
+
// Pass traceId to browser SDK via X-Vantinel-Trace header
|
|
123
|
+
const fn = monitor.monitor('backend_call', myFn, { traceId });
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `monitor.ping()`
|
|
127
|
+
|
|
128
|
+
Check connectivity to the Collector:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const { ok, latencyMs } = await monitor.ping();
|
|
132
|
+
if (!ok) console.warn('Collector unreachable');
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `monitor.flush()`
|
|
136
|
+
|
|
137
|
+
Drain the event batch queue immediately (useful on graceful shutdown):
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
process.on('SIGTERM', async () => {
|
|
141
|
+
await monitor.flush();
|
|
142
|
+
process.exit(0);
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `VantinelMonitor.getSingleton(config?)`
|
|
147
|
+
|
|
148
|
+
Return a shared instance — safe for Next.js hot-reload and multi-import scenarios:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
// lib/vantinel.ts
|
|
152
|
+
export const monitor = VantinelMonitor.getSingleton({
|
|
153
|
+
apiKey: process.env.VANTINEL_API_KEY,
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Shadow Mode
|
|
158
|
+
|
|
159
|
+
Shadow Mode observes without enforcing — ideal for proving value before enabling hard blocks:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const monitor = new VantinelMonitor({
|
|
163
|
+
shadowMode: true,
|
|
164
|
+
slackWebhookUrl: process.env.SLACK_WEBHOOK, // optional
|
|
165
|
+
});
|
|
166
|
+
// Blocked calls are allowed but logged: "[Vantinel Shadow] Would have blocked `delete_users`"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Enforcement Decisions
|
|
170
|
+
|
|
171
|
+
| Decision | Behavior |
|
|
172
|
+
|---|---|
|
|
173
|
+
| `allow` | Tool executes normally |
|
|
174
|
+
| `block` | SDK throws `Error: [Vantinel] Tool blocked: ...` |
|
|
175
|
+
| `require_approval` | Warning logged, execution continues (approval UI in dashboard) |
|
|
176
|
+
| `warn` | Tool executes, warning logged |
|
|
177
|
+
|
|
178
|
+
If the Collector is unreachable, the SDK **fails open** — the tool executes normally. Collector downtime never breaks your agent.
|
|
179
|
+
|
|
180
|
+
## Security
|
|
181
|
+
|
|
182
|
+
- All requests signed with **HMAC-SHA256** (`timestamp.body` format)
|
|
183
|
+
- Per-request nonces prevent replay attacks
|
|
184
|
+
- Tool arguments are **never sent** — only a SHA-256 hash
|
|
185
|
+
- **Fail-open by default**: any network/5xx error returns `{ decision: 'allow' }`
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
MIT © Vantinel AI
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface VantinelConfig {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
clientId?: string;
|
|
4
|
+
collectorUrl?: string;
|
|
5
|
+
agentId?: string;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
shadowMode?: boolean;
|
|
8
|
+
failMode?: 'open' | 'closed';
|
|
9
|
+
batchSize?: number;
|
|
10
|
+
flushInterval?: number;
|
|
11
|
+
retry?: {
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
backoffMs?: number;
|
|
14
|
+
};
|
|
15
|
+
slackWebhookUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface VantinelEvent {
|
|
18
|
+
client_id?: string;
|
|
19
|
+
session_id: string;
|
|
20
|
+
agent_id?: string;
|
|
21
|
+
tool_name: string;
|
|
22
|
+
tool_args_hash: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
latency_ms?: number;
|
|
25
|
+
estimated_cost?: number;
|
|
26
|
+
event_type?: string;
|
|
27
|
+
trace_id?: string;
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
export interface VantinelDecision {
|
|
31
|
+
decision: 'allow' | 'block' | 'require_approval' | 'warn';
|
|
32
|
+
message?: string;
|
|
33
|
+
session_spend?: number;
|
|
34
|
+
violations?: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Rough token estimation: ~4 characters per token.
|
|
38
|
+
*/
|
|
39
|
+
declare function estimateTokens(text: string): number;
|
|
40
|
+
/**
|
|
41
|
+
* Model pricing per 1k tokens (input, output, cache_read) in USD.
|
|
42
|
+
*/
|
|
43
|
+
declare const MODEL_PRICING: Record<string, {
|
|
44
|
+
input: number;
|
|
45
|
+
output: number;
|
|
46
|
+
cache_read?: number;
|
|
47
|
+
}>;
|
|
48
|
+
declare function estimateCostFromText(text: string): number;
|
|
49
|
+
declare function estimateCostFromTokens(model: string, inputTokens: number, outputTokens: number, cachedTokens?: number): number;
|
|
50
|
+
export declare class VantinelClient {
|
|
51
|
+
private client;
|
|
52
|
+
private config;
|
|
53
|
+
private batchQueue;
|
|
54
|
+
private flushTimer;
|
|
55
|
+
private globalMetadata;
|
|
56
|
+
constructor(config: VantinelConfig);
|
|
57
|
+
setGlobalMetadata(metadata: Record<string, unknown>): void;
|
|
58
|
+
private mergeGlobalMetadata;
|
|
59
|
+
private sendWithRetry;
|
|
60
|
+
/**
|
|
61
|
+
* Send an event to the collector.
|
|
62
|
+
* Cost is only included if explicitly set on the event.
|
|
63
|
+
*/
|
|
64
|
+
sendEvent(event: VantinelEvent): Promise<VantinelDecision>;
|
|
65
|
+
/**
|
|
66
|
+
* Wrap a tool function — automatically measures latency and tracks it.
|
|
67
|
+
* This is the recommended way to use Vantinel.
|
|
68
|
+
*
|
|
69
|
+
* Cost is only reported if you explicitly provide it (e.g., from your LLM provider's usage data).
|
|
70
|
+
* Latency is always automatically measured.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const result = await client.wrap('search_db', '{"query":"test"}', async () => {
|
|
74
|
+
* return await searchDatabase('test');
|
|
75
|
+
* });
|
|
76
|
+
*/
|
|
77
|
+
wrap<T>(toolName: string, toolArgs: string, fn: () => T | Promise<T>, options?: {
|
|
78
|
+
sessionId?: string;
|
|
79
|
+
estimatedCost?: number;
|
|
80
|
+
traceId?: string;
|
|
81
|
+
}): Promise<T>;
|
|
82
|
+
/**
|
|
83
|
+
* Auto-instrument an OpenAI client.
|
|
84
|
+
* This monkey-patches `chat.completions.create` to automatically intercept calls,
|
|
85
|
+
* measure latency, extract exact token usage from the response, and calculate true cost.
|
|
86
|
+
*
|
|
87
|
+
* @param openaiClient - The instantiated OpenAI client (e.g., `new OpenAI()`)
|
|
88
|
+
* @param options - Optional configuration for the intercepted calls
|
|
89
|
+
* @returns The patched OpenAI client
|
|
90
|
+
*/
|
|
91
|
+
wrapOpenAI(openaiClient: any, options?: {
|
|
92
|
+
sessionId?: string;
|
|
93
|
+
traceId?: string;
|
|
94
|
+
}): any;
|
|
95
|
+
/**
|
|
96
|
+
* Gracefully shut down the client: flush pending events and clear timers.
|
|
97
|
+
*/
|
|
98
|
+
destroy(): Promise<void>;
|
|
99
|
+
flush(): Promise<void>;
|
|
100
|
+
ping(): Promise<{
|
|
101
|
+
ok: boolean;
|
|
102
|
+
latencyMs: number;
|
|
103
|
+
}>;
|
|
104
|
+
}
|
|
105
|
+
export { estimateCostFromTokens, estimateCostFromText, estimateTokens, MODEL_PRICING };
|