@signalvaultio/node 0.1.0 → 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 +96 -19
- package/dist/__tests__/index.test.js +89 -39
- package/dist/index.d.ts +60 -27
- package/dist/index.js +240 -212
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -1,26 +1,33 @@
|
|
|
1
1
|
# @signalvaultio/node
|
|
2
2
|
|
|
3
|
-
AI audit logs and guardrails for your OpenAI applications.
|
|
3
|
+
AI audit logs and guardrails for your OpenAI and Anthropic applications.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
+
# OpenAI only
|
|
8
9
|
npm install @signalvaultio/node openai
|
|
10
|
+
|
|
11
|
+
# Anthropic only
|
|
12
|
+
npm install @signalvaultio/node @anthropic-ai/sdk
|
|
13
|
+
|
|
14
|
+
# Both
|
|
15
|
+
npm install @signalvaultio/node openai @anthropic-ai/sdk
|
|
9
16
|
```
|
|
10
17
|
|
|
11
|
-
## Quick Start
|
|
18
|
+
## Quick Start — OpenAI
|
|
12
19
|
|
|
13
20
|
```typescript
|
|
14
21
|
import SignalVaultClient from '@signalvaultio/node';
|
|
15
22
|
|
|
16
23
|
const client = new SignalVaultClient({
|
|
17
|
-
apiKey: 'sk_live_your_signalvault_key',
|
|
18
|
-
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
19
|
-
baseUrl: 'https://api.signalvault.io',
|
|
24
|
+
apiKey: 'sk_live_your_signalvault_key',
|
|
25
|
+
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
26
|
+
baseUrl: 'https://api.signalvault.io',
|
|
20
27
|
environment: 'production',
|
|
21
28
|
});
|
|
22
29
|
|
|
23
|
-
// Use exactly like OpenAI SDK
|
|
30
|
+
// Use exactly like OpenAI SDK
|
|
24
31
|
const response = await client.chat.completions.create({
|
|
25
32
|
model: 'gpt-4',
|
|
26
33
|
messages: [{ role: 'user', content: 'Hello!' }],
|
|
@@ -29,11 +36,31 @@ const response = await client.chat.completions.create({
|
|
|
29
36
|
console.log(response.choices[0].message.content);
|
|
30
37
|
```
|
|
31
38
|
|
|
39
|
+
## Quick Start — Anthropic
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const client = new SignalVaultClient({
|
|
43
|
+
apiKey: 'sk_live_your_signalvault_key',
|
|
44
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
|
|
45
|
+
baseUrl: 'https://api.signalvault.io',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Use exactly like Anthropic SDK
|
|
49
|
+
const response = await client.messages.create({
|
|
50
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
51
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
52
|
+
max_tokens: 1024,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log(response.content[0].text);
|
|
56
|
+
```
|
|
57
|
+
|
|
32
58
|
## Streaming
|
|
33
59
|
|
|
34
|
-
Streaming
|
|
60
|
+
Streaming is fully supported for both providers. SignalVault logs the complete response once the stream finishes:
|
|
35
61
|
|
|
36
62
|
```typescript
|
|
63
|
+
// OpenAI streaming
|
|
37
64
|
const stream = await client.chat.completions.create({
|
|
38
65
|
model: 'gpt-4',
|
|
39
66
|
messages: [{ role: 'user', content: 'Write a poem' }],
|
|
@@ -43,48 +70,98 @@ const stream = await client.chat.completions.create({
|
|
|
43
70
|
for await (const chunk of stream) {
|
|
44
71
|
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
45
72
|
}
|
|
73
|
+
|
|
74
|
+
// Anthropic streaming
|
|
75
|
+
const stream = await client.messages.create({
|
|
76
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
77
|
+
messages: [{ role: 'user', content: 'Write a poem' }],
|
|
78
|
+
max_tokens: 1024,
|
|
79
|
+
stream: true,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
for await (const event of stream) {
|
|
83
|
+
if (event.type === 'content_block_delta') {
|
|
84
|
+
process.stdout.write(event.delta.text || '');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Metadata
|
|
90
|
+
|
|
91
|
+
Attach contextual metadata to every event — perfect for audit trails, user attribution, and analytics:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Set defaults at client level
|
|
95
|
+
const client = new SignalVaultClient({
|
|
96
|
+
apiKey: 'sk_live_...',
|
|
97
|
+
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
98
|
+
metadata: { workspace_id: 'ws_abc', environment: 'production' },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Override per-call
|
|
102
|
+
const response = await client.chat.completions.create(
|
|
103
|
+
{ model: 'gpt-4', messages: [...] },
|
|
104
|
+
{ metadata: { user_id: 'u_123', feature: 'support-chat' } }
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Timeout Configuration
|
|
109
|
+
|
|
110
|
+
The pre-flight guardrail check is in your request's critical path. SignalVaultClient uses a short timeout and **fails open** — your request always goes through even if the SignalVault API is slow or unreachable:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const client = new SignalVaultClient({
|
|
114
|
+
apiKey: 'sk_live_...',
|
|
115
|
+
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
116
|
+
preflightTimeout: 2000, // ms — pre-flight check timeout (fails open). Default: 2000
|
|
117
|
+
timeout: 10000, // ms — background/post-flight calls. Default: 10000
|
|
118
|
+
});
|
|
46
119
|
```
|
|
47
120
|
|
|
48
121
|
## Mirror Mode
|
|
49
122
|
|
|
50
|
-
In mirror mode, requests go directly to
|
|
123
|
+
In mirror mode, requests go directly to the AI provider first and SignalVault audits them asynchronously — no latency added, never blocks:
|
|
51
124
|
|
|
52
125
|
```typescript
|
|
53
126
|
const client = new SignalVaultClient({
|
|
54
127
|
apiKey: 'sk_live_...',
|
|
55
128
|
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
56
|
-
mirrorMode: true,
|
|
129
|
+
mirrorMode: true,
|
|
57
130
|
});
|
|
58
131
|
```
|
|
59
132
|
|
|
60
133
|
## Features
|
|
61
134
|
|
|
62
135
|
- **Automatic Logging** — Every request and response is recorded in your SignalVault dashboard
|
|
63
|
-
- **Pre-flight Guardrails** — Block or redact requests before they reach
|
|
136
|
+
- **Pre-flight Guardrails** — Block or redact requests before they reach the AI provider
|
|
64
137
|
- **PII Detection** — Automatically detect emails, phone numbers, SSNs in prompts
|
|
65
138
|
- **Secret Detection** — Block API keys and tokens in prompts
|
|
66
139
|
- **Token Limits** — Enforce cost controls per request
|
|
67
140
|
- **Model Allowlists** — Restrict which AI models can be used
|
|
68
|
-
- **Streaming Support** — Full support for streaming completions
|
|
141
|
+
- **Streaming Support** — Full support for streaming completions (OpenAI + Anthropic)
|
|
69
142
|
- **Mirror Mode** — Observe without blocking (zero added latency)
|
|
143
|
+
- **Metadata** — Tag every event with user_id, feature, workspace_id, etc.
|
|
144
|
+
- **Multi-provider** — OpenAI and Anthropic/Claude support
|
|
70
145
|
|
|
71
146
|
## Configuration
|
|
72
147
|
|
|
73
148
|
```typescript
|
|
74
149
|
const client = new SignalVaultClient({
|
|
75
|
-
apiKey: 'sk_live_...',
|
|
76
|
-
openaiApiKey: 'sk-...',
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
150
|
+
apiKey: 'sk_live_...', // Your SignalVault API key (required)
|
|
151
|
+
openaiApiKey: 'sk-...', // OpenAI API key (required for chat.completions)
|
|
152
|
+
anthropicApiKey: 'sk-ant-...', // Anthropic API key (required for messages)
|
|
153
|
+
baseUrl: 'https://api.signalvault.io',
|
|
154
|
+
environment: 'production', // 'development' | 'staging' | 'production'
|
|
155
|
+
debug: false,
|
|
156
|
+
mirrorMode: false,
|
|
157
|
+
preflightTimeout: 2000, // ms
|
|
158
|
+
timeout: 10000, // ms
|
|
159
|
+
metadata: {}, // Default metadata for all events
|
|
81
160
|
});
|
|
82
161
|
```
|
|
83
162
|
|
|
84
163
|
## Error Handling
|
|
85
164
|
|
|
86
|
-
If a request is blocked by a guardrail, an error will be thrown:
|
|
87
|
-
|
|
88
165
|
```typescript
|
|
89
166
|
try {
|
|
90
167
|
const response = await client.chat.completions.create({
|
|
@@ -1,49 +1,99 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const index_1 = require("../index");
|
|
4
|
-
// Minimal unit tests for the SDK (no network calls)
|
|
5
4
|
describe('SignalVaultClient', () => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
apiKey: 'sk_test_abc',
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
describe('construction', () => {
|
|
6
|
+
it('constructs with openaiApiKey', () => {
|
|
7
|
+
const client = new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', openaiApiKey: 'sk-fake' });
|
|
8
|
+
expect(client).toBeDefined();
|
|
9
|
+
expect(client.chat).toBeDefined();
|
|
10
|
+
expect(client.chat.completions).toBeDefined();
|
|
11
|
+
expect(typeof client.chat.completions.create).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
it('throws when neither openaiApiKey nor anthropicApiKey is provided', () => {
|
|
14
|
+
expect(() => new index_1.SignalVaultClient({ apiKey: 'sk_test_abc' })).toThrow('At least one of openaiApiKey or anthropicApiKey is required');
|
|
15
|
+
});
|
|
16
|
+
it('defaults to localhost and production', () => {
|
|
17
|
+
const client = new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', openaiApiKey: 'sk-fake' });
|
|
18
|
+
expect(client.svBaseUrl).toBe('http://localhost:4000');
|
|
19
|
+
expect(client.svEnvironment).toBe('production');
|
|
20
|
+
expect(client.debugMode).toBe(false);
|
|
21
|
+
expect(client.mirrorMode).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
it('accepts custom config', () => {
|
|
24
|
+
const client = new index_1.SignalVaultClient({
|
|
25
|
+
apiKey: 'sk_test_abc',
|
|
26
|
+
openaiApiKey: 'sk-fake',
|
|
27
|
+
baseUrl: 'https://api.signalvault.io/',
|
|
28
|
+
environment: 'staging',
|
|
29
|
+
debug: true,
|
|
30
|
+
mirrorMode: true,
|
|
31
|
+
});
|
|
32
|
+
expect(client.svBaseUrl).toBe('https://api.signalvault.io');
|
|
33
|
+
expect(client.svEnvironment).toBe('staging');
|
|
34
|
+
expect(client.debugMode).toBe(true);
|
|
35
|
+
expect(client.mirrorMode).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it('strips trailing slashes from baseUrl', () => {
|
|
38
|
+
const client = new index_1.SignalVaultClient({
|
|
39
|
+
apiKey: 'sk_test_abc',
|
|
40
|
+
openaiApiKey: 'sk-fake',
|
|
41
|
+
baseUrl: 'https://example.com///',
|
|
42
|
+
});
|
|
43
|
+
expect(client.svBaseUrl).toBe('https://example.com');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('timeout configuration', () => {
|
|
47
|
+
it('defaults to 2000ms preflight and 10000ms background', () => {
|
|
48
|
+
const client = new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', openaiApiKey: 'sk-fake' });
|
|
49
|
+
expect(client.preflightTimeout).toBe(2000);
|
|
50
|
+
expect(client.bgTimeout).toBe(10000);
|
|
51
|
+
});
|
|
52
|
+
it('accepts custom timeouts', () => {
|
|
53
|
+
const client = new index_1.SignalVaultClient({
|
|
54
|
+
apiKey: 'sk_test_abc',
|
|
55
|
+
openaiApiKey: 'sk-fake',
|
|
56
|
+
preflightTimeout: 500,
|
|
57
|
+
timeout: 5000,
|
|
58
|
+
});
|
|
59
|
+
expect(client.preflightTimeout).toBe(500);
|
|
60
|
+
expect(client.bgTimeout).toBe(5000);
|
|
61
|
+
});
|
|
15
62
|
});
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
apiKey: 'sk_test_abc',
|
|
19
|
-
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
63
|
+
describe('metadata', () => {
|
|
64
|
+
it('defaults to empty metadata', () => {
|
|
65
|
+
const client = new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', openaiApiKey: 'sk-fake' });
|
|
66
|
+
expect(client.defaultMetadata).toEqual({});
|
|
67
|
+
});
|
|
68
|
+
it('accepts metadata in config', () => {
|
|
69
|
+
const client = new index_1.SignalVaultClient({
|
|
70
|
+
apiKey: 'sk_test_abc',
|
|
71
|
+
openaiApiKey: 'sk-fake',
|
|
72
|
+
metadata: { user_id: 'u_123', feature: 'chat' },
|
|
73
|
+
});
|
|
74
|
+
expect(client.defaultMetadata).toEqual({ user_id: 'u_123', feature: 'chat' });
|
|
75
|
+
});
|
|
26
76
|
});
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
apiKey: 'sk_test_abc',
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
expect(client.signalvaultBaseUrl).toBe('https://api.signalvault.io');
|
|
37
|
-
expect(client.signalvaultEnvironment).toBe('staging');
|
|
38
|
-
expect(client.debugMode).toBe(true);
|
|
39
|
-
expect(client.mirrorMode).toBe(true);
|
|
77
|
+
describe('Anthropic', () => {
|
|
78
|
+
it('throws on messages access when anthropicApiKey not provided', () => {
|
|
79
|
+
const client = new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', openaiApiKey: 'sk-fake' });
|
|
80
|
+
expect(() => client.messages).toThrow('anthropicApiKey not provided');
|
|
81
|
+
});
|
|
82
|
+
it('throws when @anthropic-ai/sdk is not installed', () => {
|
|
83
|
+
expect(() => new index_1.SignalVaultClient({ apiKey: 'sk_test_abc', anthropicApiKey: 'sk-ant-fake' })).toThrow('@anthropic-ai/sdk is not installed');
|
|
84
|
+
});
|
|
40
85
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
86
|
+
describe('fail-open on preflight timeout', () => {
|
|
87
|
+
it('sendRequest returns allow decision on fetch error', async () => {
|
|
88
|
+
const client = new index_1.SignalVaultClient({
|
|
89
|
+
apiKey: 'sk_test_abc',
|
|
90
|
+
openaiApiKey: 'sk-fake',
|
|
91
|
+
baseUrl: 'http://127.0.0.1:1', // unreachable
|
|
92
|
+
preflightTimeout: 100,
|
|
93
|
+
});
|
|
94
|
+
const decision = await client.sendRequest('req-1', 'gpt-4', [], {}, 'openai');
|
|
95
|
+
expect(decision.decision).toBe('allow');
|
|
96
|
+
expect(decision.violations).toEqual([]);
|
|
46
97
|
});
|
|
47
|
-
expect(client.signalvaultBaseUrl).toBe('https://example.com');
|
|
48
98
|
});
|
|
49
99
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
2
|
export interface SignalVaultConfig {
|
|
3
3
|
apiKey: string;
|
|
4
|
-
|
|
4
|
+
/** Required for OpenAI (chat.completions). */
|
|
5
|
+
openaiApiKey?: string;
|
|
6
|
+
/** Required for Anthropic (messages). Install @anthropic-ai/sdk separately. */
|
|
7
|
+
anthropicApiKey?: string;
|
|
5
8
|
baseUrl?: string;
|
|
6
9
|
environment?: 'development' | 'staging' | 'production';
|
|
7
10
|
debug?: boolean;
|
|
8
11
|
mirrorMode?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Timeout (ms) for the pre-flight /v1/events call in normal mode.
|
|
14
|
+
* This is in the critical path — on timeout/error the SDK fails open (allows).
|
|
15
|
+
* Default: 2000
|
|
16
|
+
*/
|
|
17
|
+
preflightTimeout?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Timeout (ms) for all background / post-flight event calls.
|
|
20
|
+
* Default: 10000
|
|
21
|
+
*/
|
|
22
|
+
timeout?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Default metadata attached to every event. Can be overridden per-call.
|
|
25
|
+
* Use for user_id, feature, workspace_id, etc.
|
|
26
|
+
*/
|
|
27
|
+
metadata?: Record<string, unknown>;
|
|
9
28
|
}
|
|
10
29
|
export interface SignalVaultDecision {
|
|
11
30
|
decision: 'allow' | 'warn' | 'block' | 'redact';
|
|
@@ -22,50 +41,64 @@ export interface SignalVaultDecision {
|
|
|
22
41
|
replacement: string;
|
|
23
42
|
}>;
|
|
24
43
|
}
|
|
44
|
+
export interface CreateOptions {
|
|
45
|
+
/** Per-call metadata, merged over config-level metadata. */
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
}
|
|
25
48
|
/**
|
|
26
|
-
* SignalVault Client — wraps OpenAI with guardrails and audit logging.
|
|
49
|
+
* SignalVault Client — wraps OpenAI and/or Anthropic with guardrails and audit logging.
|
|
27
50
|
*
|
|
28
|
-
* @example
|
|
51
|
+
* @example OpenAI
|
|
29
52
|
* ```typescript
|
|
30
53
|
* const client = new SignalVaultClient({
|
|
31
54
|
* apiKey: 'sk_live_...',
|
|
32
55
|
* openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
33
56
|
* baseUrl: 'https://api.signalvault.io',
|
|
57
|
+
* metadata: { user_id: '123' },
|
|
34
58
|
* });
|
|
35
|
-
*
|
|
36
|
-
* // Non-streaming
|
|
37
59
|
* const res = await client.chat.completions.create({ model: 'gpt-4', messages: [...] });
|
|
60
|
+
* ```
|
|
38
61
|
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
62
|
+
* @example Anthropic
|
|
63
|
+
* ```typescript
|
|
64
|
+
* const client = new SignalVaultClient({
|
|
65
|
+
* apiKey: 'sk_live_...',
|
|
66
|
+
* anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
|
|
67
|
+
* baseUrl: 'https://api.signalvault.io',
|
|
68
|
+
* });
|
|
69
|
+
* const res = await client.messages.create({ model: 'claude-3-5-sonnet-20241022', messages: [...], max_tokens: 1024 });
|
|
42
70
|
* ```
|
|
43
71
|
*/
|
|
44
72
|
export declare class SignalVaultClient {
|
|
45
|
-
private
|
|
46
|
-
private
|
|
47
|
-
private
|
|
48
|
-
private debugMode;
|
|
49
|
-
private mirrorMode;
|
|
50
|
-
private
|
|
73
|
+
private readonly svApiKey;
|
|
74
|
+
private readonly svBaseUrl;
|
|
75
|
+
private readonly svEnvironment;
|
|
76
|
+
private readonly debugMode;
|
|
77
|
+
private readonly mirrorMode;
|
|
78
|
+
private readonly preflightTimeout;
|
|
79
|
+
private readonly bgTimeout;
|
|
80
|
+
private readonly defaultMetadata;
|
|
81
|
+
private readonly openai;
|
|
82
|
+
private readonly anthropic;
|
|
51
83
|
constructor(config: SignalVaultConfig);
|
|
52
|
-
/**
|
|
53
|
-
* Chat completions with SignalVault guardrails.
|
|
54
|
-
* Supports both streaming and non-streaming.
|
|
55
|
-
*/
|
|
56
84
|
get chat(): {
|
|
57
85
|
completions: {
|
|
58
|
-
create
|
|
86
|
+
create(params: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming, options?: CreateOptions): Promise<OpenAI.Chat.ChatCompletion>;
|
|
87
|
+
create(params: OpenAI.Chat.ChatCompletionCreateParamsStreaming, options?: CreateOptions): Promise<AsyncGenerator<OpenAI.Chat.Completions.ChatCompletionChunk>>;
|
|
59
88
|
};
|
|
60
89
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
private
|
|
90
|
+
get messages(): {
|
|
91
|
+
create(params: any, options?: CreateOptions): Promise<any>;
|
|
92
|
+
};
|
|
93
|
+
private handleOpenAIMirror;
|
|
94
|
+
private handleOpenAINormal;
|
|
95
|
+
private handleAnthropicMirror;
|
|
96
|
+
private handleAnthropicNormal;
|
|
97
|
+
private wrapStream;
|
|
98
|
+
private wrapAnthropicStream;
|
|
99
|
+
private authHeaders;
|
|
65
100
|
private sendRequest;
|
|
66
|
-
private
|
|
67
|
-
private
|
|
68
|
-
private sendAudit;
|
|
69
|
-
private sendStreamAudit;
|
|
101
|
+
private sendResponseEvent;
|
|
102
|
+
private sendAuditEvents;
|
|
70
103
|
}
|
|
71
104
|
export default SignalVaultClient;
|
package/dist/index.js
CHANGED
|
@@ -6,91 +6,140 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.SignalVaultClient = void 0;
|
|
7
7
|
const openai_1 = __importDefault(require("openai"));
|
|
8
8
|
const uuid_1 = require("uuid");
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// SignalVaultClient
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
9
12
|
/**
|
|
10
|
-
* SignalVault Client — wraps OpenAI with guardrails and audit logging.
|
|
13
|
+
* SignalVault Client — wraps OpenAI and/or Anthropic with guardrails and audit logging.
|
|
11
14
|
*
|
|
12
|
-
* @example
|
|
15
|
+
* @example OpenAI
|
|
13
16
|
* ```typescript
|
|
14
17
|
* const client = new SignalVaultClient({
|
|
15
18
|
* apiKey: 'sk_live_...',
|
|
16
19
|
* openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
17
20
|
* baseUrl: 'https://api.signalvault.io',
|
|
21
|
+
* metadata: { user_id: '123' },
|
|
18
22
|
* });
|
|
19
|
-
*
|
|
20
|
-
* // Non-streaming
|
|
21
23
|
* const res = await client.chat.completions.create({ model: 'gpt-4', messages: [...] });
|
|
24
|
+
* ```
|
|
22
25
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* @example Anthropic
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const client = new SignalVaultClient({
|
|
29
|
+
* apiKey: 'sk_live_...',
|
|
30
|
+
* anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
|
|
31
|
+
* baseUrl: 'https://api.signalvault.io',
|
|
32
|
+
* });
|
|
33
|
+
* const res = await client.messages.create({ model: 'claude-3-5-sonnet-20241022', messages: [...], max_tokens: 1024 });
|
|
26
34
|
* ```
|
|
27
35
|
*/
|
|
28
36
|
class SignalVaultClient {
|
|
29
37
|
constructor(config) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
if (!config.openaiApiKey && !config.anthropicApiKey) {
|
|
39
|
+
throw new Error('[SignalVault] At least one of openaiApiKey or anthropicApiKey is required.');
|
|
40
|
+
}
|
|
41
|
+
this.svApiKey = config.apiKey;
|
|
42
|
+
this.svBaseUrl = (config.baseUrl || 'http://localhost:4000').replace(/\/+$/, '');
|
|
43
|
+
this.svEnvironment = config.environment || 'production';
|
|
33
44
|
this.debugMode = config.debug || false;
|
|
34
45
|
this.mirrorMode = config.mirrorMode || false;
|
|
35
|
-
this.
|
|
36
|
-
|
|
37
|
-
}
|
|
46
|
+
this.preflightTimeout = config.preflightTimeout ?? 2000;
|
|
47
|
+
this.bgTimeout = config.timeout ?? 10000;
|
|
48
|
+
this.defaultMetadata = config.metadata ?? {};
|
|
49
|
+
this.openai = config.openaiApiKey
|
|
50
|
+
? new openai_1.default({ apiKey: config.openaiApiKey })
|
|
51
|
+
: null;
|
|
52
|
+
if (config.anthropicApiKey) {
|
|
53
|
+
try {
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
55
|
+
const mod = require('@anthropic-ai/sdk');
|
|
56
|
+
const Anthropic = mod.default ?? mod;
|
|
57
|
+
this.anthropic = new Anthropic({ apiKey: config.anthropicApiKey });
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
throw new Error('[SignalVault] @anthropic-ai/sdk is not installed. Run: npm install @anthropic-ai/sdk');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.anthropic = null;
|
|
65
|
+
}
|
|
38
66
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
*/
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// OpenAI: chat.completions
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
43
70
|
get chat() {
|
|
71
|
+
if (!this.openai) {
|
|
72
|
+
throw new Error('[SignalVault] openaiApiKey was not provided.');
|
|
73
|
+
}
|
|
74
|
+
const self = this;
|
|
44
75
|
return {
|
|
45
76
|
completions: {
|
|
46
|
-
|
|
77
|
+
async create(params, options) {
|
|
47
78
|
const requestId = (0, uuid_1.v4)();
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
const metadata = { ...self.defaultMetadata, ...(options?.metadata ?? {}) };
|
|
80
|
+
if (self.debugMode) {
|
|
81
|
+
console.log('[SignalVault] Processing request:', requestId, self.mirrorMode ? '(MIRROR MODE)' : '', params.stream ? '(STREAMING)' : '');
|
|
50
82
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
return this.handleMirrorMode(requestId, params);
|
|
83
|
+
if (self.mirrorMode) {
|
|
84
|
+
return self.handleOpenAIMirror(requestId, params, metadata);
|
|
54
85
|
}
|
|
55
|
-
|
|
56
|
-
return this.handleNormalMode(requestId, params);
|
|
86
|
+
return self.handleOpenAINormal(requestId, params, metadata);
|
|
57
87
|
},
|
|
58
88
|
},
|
|
59
89
|
};
|
|
60
90
|
}
|
|
61
91
|
// ---------------------------------------------------------------------------
|
|
62
|
-
//
|
|
92
|
+
// Anthropic: messages
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
get messages() {
|
|
95
|
+
if (!this.anthropic) {
|
|
96
|
+
throw new Error('[SignalVault] anthropicApiKey not provided or @anthropic-ai/sdk not installed.');
|
|
97
|
+
}
|
|
98
|
+
const self = this;
|
|
99
|
+
return {
|
|
100
|
+
async create(params, options) {
|
|
101
|
+
const requestId = (0, uuid_1.v4)();
|
|
102
|
+
const metadata = { ...self.defaultMetadata, ...(options?.metadata ?? {}) };
|
|
103
|
+
if (self.debugMode) {
|
|
104
|
+
console.log('[SignalVault] Anthropic request:', requestId, self.mirrorMode ? '(MIRROR MODE)' : '', params.stream ? '(STREAMING)' : '');
|
|
105
|
+
}
|
|
106
|
+
if (self.mirrorMode) {
|
|
107
|
+
return self.handleAnthropicMirror(requestId, params, metadata);
|
|
108
|
+
}
|
|
109
|
+
return self.handleAnthropicNormal(requestId, params, metadata);
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// OpenAI — mirror mode
|
|
63
115
|
// ---------------------------------------------------------------------------
|
|
64
|
-
async
|
|
116
|
+
async handleOpenAIMirror(requestId, params, metadata) {
|
|
65
117
|
try {
|
|
66
118
|
const response = await this.openai.chat.completions.create(params);
|
|
67
119
|
if (params.stream) {
|
|
68
|
-
|
|
69
|
-
|
|
120
|
+
return this.wrapStream(requestId, params.model, params.messages, response, async (output, promptTokens, completionTokens) => {
|
|
121
|
+
await this.sendAuditEvents(requestId, params.model, params.messages, output, promptTokens, completionTokens, metadata, 'openai');
|
|
122
|
+
});
|
|
70
123
|
}
|
|
71
|
-
// Non-streaming: audit in background
|
|
72
124
|
const completion = response;
|
|
73
|
-
this.
|
|
74
|
-
if (this.debugMode)
|
|
125
|
+
this.sendAuditEvents(requestId, params.model, params.messages, completion.choices[0]?.message?.content || '', completion.usage?.prompt_tokens || 0, completion.usage?.completion_tokens || 0, metadata, 'openai').catch((err) => {
|
|
126
|
+
if (this.debugMode)
|
|
75
127
|
console.error('[SignalVault] Audit failed (non-blocking):', err);
|
|
76
|
-
}
|
|
77
128
|
});
|
|
78
129
|
return completion;
|
|
79
130
|
}
|
|
80
131
|
catch (error) {
|
|
81
|
-
if (this.debugMode)
|
|
132
|
+
if (this.debugMode)
|
|
82
133
|
console.error('[SignalVault] OpenAI error:', error);
|
|
83
|
-
}
|
|
84
134
|
throw error;
|
|
85
135
|
}
|
|
86
136
|
}
|
|
87
137
|
// ---------------------------------------------------------------------------
|
|
88
|
-
//
|
|
138
|
+
// OpenAI — normal mode
|
|
89
139
|
// ---------------------------------------------------------------------------
|
|
90
|
-
async
|
|
140
|
+
async handleOpenAINormal(requestId, params, metadata) {
|
|
91
141
|
try {
|
|
92
|
-
|
|
93
|
-
const decision = await this.sendRequest(requestId, params);
|
|
142
|
+
const decision = await this.sendRequest(requestId, params.model, params.messages, metadata, 'openai');
|
|
94
143
|
if (decision.decision === 'block') {
|
|
95
144
|
throw new Error(`[SignalVault] Request blocked: ${JSON.stringify(decision.violations)}`);
|
|
96
145
|
}
|
|
@@ -99,25 +148,76 @@ class SignalVaultClient {
|
|
|
99
148
|
}
|
|
100
149
|
const response = await this.openai.chat.completions.create(params);
|
|
101
150
|
if (params.stream) {
|
|
102
|
-
|
|
103
|
-
|
|
151
|
+
return this.wrapStream(requestId, params.model, params.messages, response, async (output, promptTokens, completionTokens) => {
|
|
152
|
+
await this.sendResponseEvent(requestId, params.model, output, promptTokens, completionTokens, metadata, 'openai');
|
|
153
|
+
});
|
|
104
154
|
}
|
|
105
|
-
// Non-streaming: post-flight logging
|
|
106
155
|
const completion = response;
|
|
107
|
-
await this.
|
|
156
|
+
await this.sendResponseEvent(requestId, params.model, completion.choices[0]?.message?.content || '', completion.usage?.prompt_tokens || 0, completion.usage?.completion_tokens || 0, metadata, 'openai');
|
|
108
157
|
return completion;
|
|
109
158
|
}
|
|
110
159
|
catch (error) {
|
|
111
|
-
if (this.debugMode)
|
|
160
|
+
if (this.debugMode)
|
|
112
161
|
console.error('[SignalVault] Error:', error);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Anthropic — mirror mode
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
async handleAnthropicMirror(requestId, params, metadata) {
|
|
169
|
+
try {
|
|
170
|
+
const response = await this.anthropic.messages.create(params);
|
|
171
|
+
if (params.stream) {
|
|
172
|
+
return this.wrapAnthropicStream(requestId, params.model, params.messages, response, async (output, inputTokens, outputTokens) => {
|
|
173
|
+
await this.sendAuditEvents(requestId, params.model, params.messages, output, inputTokens, outputTokens, metadata, 'anthropic');
|
|
174
|
+
});
|
|
113
175
|
}
|
|
176
|
+
const output = response.content?.[0]?.text || '';
|
|
177
|
+
this.sendAuditEvents(requestId, params.model, params.messages, output, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, metadata, 'anthropic').catch((err) => {
|
|
178
|
+
if (this.debugMode)
|
|
179
|
+
console.error('[SignalVault] Anthropic audit failed (non-blocking):', err);
|
|
180
|
+
});
|
|
181
|
+
return response;
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
if (this.debugMode)
|
|
185
|
+
console.error('[SignalVault] Anthropic error:', error);
|
|
114
186
|
throw error;
|
|
115
187
|
}
|
|
116
188
|
}
|
|
117
189
|
// ---------------------------------------------------------------------------
|
|
118
|
-
//
|
|
190
|
+
// Anthropic — normal mode
|
|
119
191
|
// ---------------------------------------------------------------------------
|
|
120
|
-
async
|
|
192
|
+
async handleAnthropicNormal(requestId, params, metadata) {
|
|
193
|
+
try {
|
|
194
|
+
const decision = await this.sendRequest(requestId, params.model, params.messages, metadata, 'anthropic');
|
|
195
|
+
if (decision.decision === 'block') {
|
|
196
|
+
throw new Error(`[SignalVault] Request blocked: ${JSON.stringify(decision.violations)}`);
|
|
197
|
+
}
|
|
198
|
+
if (decision.decision === 'warn' && this.debugMode) {
|
|
199
|
+
console.warn('[SignalVault] Warnings:', decision.violations);
|
|
200
|
+
}
|
|
201
|
+
const response = await this.anthropic.messages.create(params);
|
|
202
|
+
if (params.stream) {
|
|
203
|
+
return this.wrapAnthropicStream(requestId, params.model, params.messages, response, async (output, inputTokens, outputTokens) => {
|
|
204
|
+
await this.sendResponseEvent(requestId, params.model, output, inputTokens, outputTokens, metadata, 'anthropic');
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const output = response.content?.[0]?.text || '';
|
|
208
|
+
await this.sendResponseEvent(requestId, params.model, output, response.usage?.input_tokens || 0, response.usage?.output_tokens || 0, metadata, 'anthropic');
|
|
209
|
+
return response;
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (this.debugMode)
|
|
213
|
+
console.error('[SignalVault] Anthropic error:', error);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Unified OpenAI stream wrapper
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
async *wrapStream(requestId, model, messages, stream, onComplete) {
|
|
121
221
|
const chunks = [];
|
|
122
222
|
let promptTokens = 0;
|
|
123
223
|
let completionTokens = 0;
|
|
@@ -125,210 +225,138 @@ class SignalVaultClient {
|
|
|
125
225
|
const content = chunk.choices[0]?.delta?.content || '';
|
|
126
226
|
if (content)
|
|
127
227
|
chunks.push(content);
|
|
128
|
-
// Collect usage if provided in final chunk
|
|
129
228
|
if (chunk.usage) {
|
|
130
229
|
promptTokens = chunk.usage.prompt_tokens || 0;
|
|
131
230
|
completionTokens = chunk.usage.completion_tokens || 0;
|
|
132
231
|
}
|
|
133
232
|
yield chunk;
|
|
134
233
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (this.debugMode) {
|
|
139
|
-
console.error('[SignalVault] Stream audit failed:', err);
|
|
140
|
-
}
|
|
234
|
+
onComplete(chunks.join(''), promptTokens, completionTokens).catch((err) => {
|
|
235
|
+
if (this.debugMode)
|
|
236
|
+
console.error('[SignalVault] Post-stream callback failed:', err);
|
|
141
237
|
});
|
|
142
238
|
}
|
|
143
|
-
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
// Anthropic stream wrapper
|
|
241
|
+
// ---------------------------------------------------------------------------
|
|
242
|
+
async *wrapAnthropicStream(requestId, model, messages, stream, onComplete) {
|
|
144
243
|
const chunks = [];
|
|
145
|
-
let
|
|
146
|
-
let
|
|
147
|
-
for await (const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
chunks.push(content);
|
|
151
|
-
if (chunk.usage) {
|
|
152
|
-
promptTokens = chunk.usage.prompt_tokens || 0;
|
|
153
|
-
completionTokens = chunk.usage.completion_tokens || 0;
|
|
244
|
+
let inputTokens = 0;
|
|
245
|
+
let outputTokens = 0;
|
|
246
|
+
for await (const event of stream) {
|
|
247
|
+
if (event.type === 'content_block_delta' && event.delta?.type === 'text_delta') {
|
|
248
|
+
chunks.push(event.delta.text || '');
|
|
154
249
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
await this.sendStreamResponse(requestId, params.model, fullOutput, promptTokens, completionTokens);
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
if (this.debugMode) {
|
|
164
|
-
console.error('[SignalVault] Post-flight logging failed:', err);
|
|
250
|
+
if (event.type === 'message_start' && event.message?.usage) {
|
|
251
|
+
inputTokens = event.message.usage.input_tokens || 0;
|
|
252
|
+
}
|
|
253
|
+
if (event.type === 'message_delta' && event.usage) {
|
|
254
|
+
outputTokens = event.usage.output_tokens || 0;
|
|
165
255
|
}
|
|
256
|
+
yield event;
|
|
166
257
|
}
|
|
258
|
+
onComplete(chunks.join(''), inputTokens, outputTokens).catch((err) => {
|
|
259
|
+
if (this.debugMode)
|
|
260
|
+
console.error('[SignalVault] Anthropic post-stream callback failed:', err);
|
|
261
|
+
});
|
|
167
262
|
}
|
|
168
263
|
// ---------------------------------------------------------------------------
|
|
169
264
|
// API communication
|
|
170
265
|
// ---------------------------------------------------------------------------
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
266
|
+
authHeaders() {
|
|
267
|
+
return {
|
|
268
|
+
'Content-Type': 'application/json',
|
|
269
|
+
Authorization: `Bearer ${this.svApiKey}`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async sendRequest(requestId, model, messages, metadata, provider) {
|
|
273
|
+
try {
|
|
274
|
+
const response = await fetch(`${this.svBaseUrl}/v1/events`, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: this.authHeaders(),
|
|
277
|
+
signal: AbortSignal.timeout(this.preflightTimeout),
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
type: 'ai.request',
|
|
280
|
+
request_id: requestId,
|
|
281
|
+
environment: this.svEnvironment,
|
|
282
|
+
provider,
|
|
283
|
+
model,
|
|
284
|
+
metadata,
|
|
285
|
+
payload: { messages },
|
|
286
|
+
}),
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) {
|
|
289
|
+
if (this.debugMode)
|
|
290
|
+
console.error('[SignalVault] API error:', response.status);
|
|
291
|
+
return { decision: 'allow', violations: [], redactions: [] };
|
|
191
292
|
}
|
|
293
|
+
return (await response.json());
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
if (this.debugMode)
|
|
297
|
+
console.error('[SignalVault] Pre-flight failed (fail-open):', error);
|
|
192
298
|
return { decision: 'allow', violations: [], redactions: [] };
|
|
193
299
|
}
|
|
194
|
-
return (await response.json());
|
|
195
300
|
}
|
|
196
|
-
async
|
|
301
|
+
async sendResponseEvent(requestId, model, output, promptTokens, completionTokens, metadata, provider) {
|
|
197
302
|
try {
|
|
198
|
-
|
|
199
|
-
await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
|
|
303
|
+
await fetch(`${this.svBaseUrl}/v1/events`, {
|
|
200
304
|
method: 'POST',
|
|
201
|
-
headers:
|
|
202
|
-
|
|
203
|
-
Authorization: `Bearer ${this.signalvaultApiKey}`,
|
|
204
|
-
},
|
|
305
|
+
headers: this.authHeaders(),
|
|
306
|
+
signal: AbortSignal.timeout(this.bgTimeout),
|
|
205
307
|
body: JSON.stringify({
|
|
206
308
|
type: 'ai.response',
|
|
207
309
|
request_id: requestId,
|
|
208
|
-
environment: this.
|
|
209
|
-
provider
|
|
310
|
+
environment: this.svEnvironment,
|
|
311
|
+
provider,
|
|
210
312
|
model,
|
|
211
|
-
metadata
|
|
313
|
+
metadata,
|
|
212
314
|
payload: {
|
|
213
315
|
output,
|
|
214
|
-
usage: {
|
|
215
|
-
prompt_tokens: response.usage?.prompt_tokens || 0,
|
|
216
|
-
completion_tokens: response.usage?.completion_tokens || 0,
|
|
217
|
-
},
|
|
316
|
+
usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
|
|
218
317
|
},
|
|
219
318
|
}),
|
|
220
319
|
});
|
|
221
320
|
}
|
|
222
321
|
catch (error) {
|
|
223
|
-
if (this.debugMode)
|
|
224
|
-
console.error('[SignalVault] Failed to send response:', error);
|
|
225
|
-
}
|
|
322
|
+
if (this.debugMode)
|
|
323
|
+
console.error('[SignalVault] Failed to send response event:', error);
|
|
226
324
|
}
|
|
227
325
|
}
|
|
228
|
-
async
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
},
|
|
246
|
-
}),
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
async sendAudit(requestId, params, response) {
|
|
250
|
-
const output = response.choices[0]?.message?.content || '';
|
|
251
|
-
// Send ai.request event
|
|
252
|
-
await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
|
|
253
|
-
method: 'POST',
|
|
254
|
-
headers: {
|
|
255
|
-
'Content-Type': 'application/json',
|
|
256
|
-
Authorization: `Bearer ${this.signalvaultApiKey}`,
|
|
257
|
-
},
|
|
258
|
-
body: JSON.stringify({
|
|
259
|
-
type: 'ai.request',
|
|
260
|
-
request_id: requestId,
|
|
261
|
-
environment: this.signalvaultEnvironment,
|
|
262
|
-
provider: 'openai',
|
|
263
|
-
model: params.model,
|
|
264
|
-
metadata: {},
|
|
265
|
-
payload: { messages: params.messages, monitor_mode: true },
|
|
326
|
+
async sendAuditEvents(requestId, model, messages, output, promptTokens, completionTokens, metadata, provider) {
|
|
327
|
+
const base = {
|
|
328
|
+
request_id: requestId,
|
|
329
|
+
environment: this.svEnvironment,
|
|
330
|
+
provider,
|
|
331
|
+
model,
|
|
332
|
+
metadata,
|
|
333
|
+
};
|
|
334
|
+
await Promise.all([
|
|
335
|
+
fetch(`${this.svBaseUrl}/v1/events`, {
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: this.authHeaders(),
|
|
338
|
+
signal: AbortSignal.timeout(this.bgTimeout),
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
...base,
|
|
341
|
+
type: 'ai.request',
|
|
342
|
+
payload: { messages, monitor_mode: true },
|
|
343
|
+
}),
|
|
266
344
|
}),
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
environment: this.signalvaultEnvironment,
|
|
279
|
-
provider: 'openai',
|
|
280
|
-
model: params.model,
|
|
281
|
-
metadata: {},
|
|
282
|
-
payload: {
|
|
283
|
-
output,
|
|
284
|
-
usage: {
|
|
285
|
-
prompt_tokens: response.usage?.prompt_tokens || 0,
|
|
286
|
-
completion_tokens: response.usage?.completion_tokens || 0,
|
|
345
|
+
fetch(`${this.svBaseUrl}/v1/events`, {
|
|
346
|
+
method: 'POST',
|
|
347
|
+
headers: this.authHeaders(),
|
|
348
|
+
signal: AbortSignal.timeout(this.bgTimeout),
|
|
349
|
+
body: JSON.stringify({
|
|
350
|
+
...base,
|
|
351
|
+
type: 'ai.response',
|
|
352
|
+
payload: {
|
|
353
|
+
output,
|
|
354
|
+
usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
|
|
355
|
+
monitor_mode: true,
|
|
287
356
|
},
|
|
288
|
-
|
|
289
|
-
},
|
|
290
|
-
}),
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
async sendStreamAudit(requestId, params, output, promptTokens, completionTokens) {
|
|
294
|
-
// Send ai.request event
|
|
295
|
-
await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
|
|
296
|
-
method: 'POST',
|
|
297
|
-
headers: {
|
|
298
|
-
'Content-Type': 'application/json',
|
|
299
|
-
Authorization: `Bearer ${this.signalvaultApiKey}`,
|
|
300
|
-
},
|
|
301
|
-
body: JSON.stringify({
|
|
302
|
-
type: 'ai.request',
|
|
303
|
-
request_id: requestId,
|
|
304
|
-
environment: this.signalvaultEnvironment,
|
|
305
|
-
provider: 'openai',
|
|
306
|
-
model: params.model,
|
|
307
|
-
metadata: {},
|
|
308
|
-
payload: { messages: params.messages, monitor_mode: true },
|
|
309
|
-
}),
|
|
310
|
-
});
|
|
311
|
-
// Send ai.response event
|
|
312
|
-
await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
|
|
313
|
-
method: 'POST',
|
|
314
|
-
headers: {
|
|
315
|
-
'Content-Type': 'application/json',
|
|
316
|
-
Authorization: `Bearer ${this.signalvaultApiKey}`,
|
|
317
|
-
},
|
|
318
|
-
body: JSON.stringify({
|
|
319
|
-
type: 'ai.response',
|
|
320
|
-
request_id: requestId,
|
|
321
|
-
environment: this.signalvaultEnvironment,
|
|
322
|
-
provider: 'openai',
|
|
323
|
-
model: params.model,
|
|
324
|
-
metadata: {},
|
|
325
|
-
payload: {
|
|
326
|
-
output,
|
|
327
|
-
usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
|
|
328
|
-
monitor_mode: true,
|
|
329
|
-
},
|
|
357
|
+
}),
|
|
330
358
|
}),
|
|
331
|
-
|
|
359
|
+
]);
|
|
332
360
|
}
|
|
333
361
|
}
|
|
334
362
|
exports.SignalVaultClient = SignalVaultClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signalvaultio/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "SignalVault SDK - AI audit logs and guardrails for safe production AI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"audit",
|
|
19
19
|
"guardrails",
|
|
20
20
|
"openai",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"claude",
|
|
21
23
|
"llm",
|
|
22
24
|
"compliance",
|
|
23
25
|
"security",
|
|
@@ -27,14 +29,19 @@
|
|
|
27
29
|
"license": "MIT",
|
|
28
30
|
"repository": {
|
|
29
31
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/signalvaultio/signalvault-node"
|
|
32
|
+
"url": "git+https://github.com/signalvaultio/signalvault-node.git"
|
|
31
33
|
},
|
|
32
34
|
"bugs": {
|
|
33
35
|
"url": "https://github.com/signalvaultio/signalvault-node/issues"
|
|
34
36
|
},
|
|
35
37
|
"homepage": "https://signalvault.io/docs",
|
|
36
38
|
"peerDependencies": {
|
|
37
|
-
"openai": "^4.0.0"
|
|
39
|
+
"openai": "^4.0.0",
|
|
40
|
+
"@anthropic-ai/sdk": ">=0.20.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"openai": { "optional": true },
|
|
44
|
+
"@anthropic-ai/sdk": { "optional": true }
|
|
38
45
|
},
|
|
39
46
|
"dependencies": {
|
|
40
47
|
"uuid": "^9.0.0"
|