@signalvaultio/node 0.1.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 ADDED
@@ -0,0 +1,103 @@
1
+ # @signalvaultio/node
2
+
3
+ AI audit logs and guardrails for your OpenAI applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @signalvaultio/node openai
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import SignalVaultClient from '@signalvaultio/node';
15
+
16
+ const client = new SignalVaultClient({
17
+ apiKey: 'sk_live_your_signalvault_key', // Your SignalVault API key
18
+ openaiApiKey: process.env.OPENAI_API_KEY!, // Your OpenAI API key
19
+ baseUrl: 'https://api.signalvault.io', // SignalVault API URL
20
+ environment: 'production',
21
+ });
22
+
23
+ // Use exactly like OpenAI SDK — all calls are logged and guarded
24
+ const response = await client.chat.completions.create({
25
+ model: 'gpt-4',
26
+ messages: [{ role: 'user', content: 'Hello!' }],
27
+ });
28
+
29
+ console.log(response.choices[0].message.content);
30
+ ```
31
+
32
+ ## Streaming
33
+
34
+ Streaming responses are fully supported. SignalVault logs the complete response once the stream finishes:
35
+
36
+ ```typescript
37
+ const stream = await client.chat.completions.create({
38
+ model: 'gpt-4',
39
+ messages: [{ role: 'user', content: 'Write a poem' }],
40
+ stream: true,
41
+ });
42
+
43
+ for await (const chunk of stream) {
44
+ process.stdout.write(chunk.choices[0]?.delta?.content || '');
45
+ }
46
+ ```
47
+
48
+ ## Mirror Mode
49
+
50
+ In mirror mode, requests go directly to OpenAI first and SignalVault audits them asynchronously — no latency added, never blocks:
51
+
52
+ ```typescript
53
+ const client = new SignalVaultClient({
54
+ apiKey: 'sk_live_...',
55
+ openaiApiKey: process.env.OPENAI_API_KEY!,
56
+ mirrorMode: true, // Monitor only, never block
57
+ });
58
+ ```
59
+
60
+ ## Features
61
+
62
+ - **Automatic Logging** — Every request and response is recorded in your SignalVault dashboard
63
+ - **Pre-flight Guardrails** — Block or redact requests before they reach OpenAI
64
+ - **PII Detection** — Automatically detect emails, phone numbers, SSNs in prompts
65
+ - **Secret Detection** — Block API keys and tokens in prompts
66
+ - **Token Limits** — Enforce cost controls per request
67
+ - **Model Allowlists** — Restrict which AI models can be used
68
+ - **Streaming Support** — Full support for streaming completions
69
+ - **Mirror Mode** — Observe without blocking (zero added latency)
70
+
71
+ ## Configuration
72
+
73
+ ```typescript
74
+ const client = new SignalVaultClient({
75
+ apiKey: 'sk_live_...', // Your SignalVault API key
76
+ openaiApiKey: 'sk-...', // Your OpenAI API key
77
+ baseUrl: 'https://api.signalvault.io', // SignalVault API URL
78
+ environment: 'production', // 'development' | 'staging' | 'production'
79
+ debug: false, // Enable debug logging
80
+ mirrorMode: false, // Monitor-only mode
81
+ });
82
+ ```
83
+
84
+ ## Error Handling
85
+
86
+ If a request is blocked by a guardrail, an error will be thrown:
87
+
88
+ ```typescript
89
+ try {
90
+ const response = await client.chat.completions.create({
91
+ model: 'gpt-4',
92
+ messages: [{ role: 'user', content: 'my SSN is 123-45-6789' }],
93
+ });
94
+ } catch (error) {
95
+ if (error.message.includes('[SignalVault]')) {
96
+ console.log('Request blocked by guardrail');
97
+ }
98
+ }
99
+ ```
100
+
101
+ ## License
102
+
103
+ MIT
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const index_1 = require("../index");
4
+ // Minimal unit tests for the SDK (no network calls)
5
+ describe('SignalVaultClient', () => {
6
+ it('should construct with required config', () => {
7
+ const client = new index_1.SignalVaultClient({
8
+ apiKey: 'sk_test_abc',
9
+ openaiApiKey: 'sk-fake',
10
+ });
11
+ expect(client).toBeDefined();
12
+ expect(client.chat).toBeDefined();
13
+ expect(client.chat.completions).toBeDefined();
14
+ expect(typeof client.chat.completions.create).toBe('function');
15
+ });
16
+ it('should default to localhost and production', () => {
17
+ const client = new index_1.SignalVaultClient({
18
+ apiKey: 'sk_test_abc',
19
+ openaiApiKey: 'sk-fake',
20
+ });
21
+ // Access private fields via bracket notation for testing
22
+ expect(client.signalvaultBaseUrl).toBe('http://localhost:4000');
23
+ expect(client.signalvaultEnvironment).toBe('production');
24
+ expect(client.debugMode).toBe(false);
25
+ expect(client.mirrorMode).toBe(false);
26
+ });
27
+ it('should accept custom baseUrl and environment', () => {
28
+ const client = new index_1.SignalVaultClient({
29
+ apiKey: 'sk_test_abc',
30
+ openaiApiKey: 'sk-fake',
31
+ baseUrl: 'https://api.signalvault.io/',
32
+ environment: 'staging',
33
+ debug: true,
34
+ mirrorMode: true,
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);
40
+ });
41
+ it('should strip trailing slashes from baseUrl', () => {
42
+ const client = new index_1.SignalVaultClient({
43
+ apiKey: 'sk_test_abc',
44
+ openaiApiKey: 'sk-fake',
45
+ baseUrl: 'https://example.com///',
46
+ });
47
+ expect(client.signalvaultBaseUrl).toBe('https://example.com');
48
+ });
49
+ });
@@ -0,0 +1,71 @@
1
+ import OpenAI from 'openai';
2
+ export interface SignalVaultConfig {
3
+ apiKey: string;
4
+ openaiApiKey: string;
5
+ baseUrl?: string;
6
+ environment?: 'development' | 'staging' | 'production';
7
+ debug?: boolean;
8
+ mirrorMode?: boolean;
9
+ }
10
+ export interface SignalVaultDecision {
11
+ decision: 'allow' | 'warn' | 'block' | 'redact';
12
+ violations: Array<{
13
+ rule_id?: string;
14
+ type: string;
15
+ severity: number;
16
+ action: string;
17
+ details: Record<string, unknown>;
18
+ }>;
19
+ redactions: Array<{
20
+ start: number;
21
+ end: number;
22
+ replacement: string;
23
+ }>;
24
+ }
25
+ /**
26
+ * SignalVault Client — wraps OpenAI with guardrails and audit logging.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const client = new SignalVaultClient({
31
+ * apiKey: 'sk_live_...',
32
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
33
+ * baseUrl: 'https://api.signalvault.io',
34
+ * });
35
+ *
36
+ * // Non-streaming
37
+ * const res = await client.chat.completions.create({ model: 'gpt-4', messages: [...] });
38
+ *
39
+ * // Streaming
40
+ * const stream = await client.chat.completions.create({ model: 'gpt-4', messages: [...], stream: true });
41
+ * for await (const chunk of stream) { ... }
42
+ * ```
43
+ */
44
+ export declare class SignalVaultClient {
45
+ private signalvaultApiKey;
46
+ private signalvaultBaseUrl;
47
+ private signalvaultEnvironment;
48
+ private debugMode;
49
+ private mirrorMode;
50
+ private openai;
51
+ constructor(config: SignalVaultConfig);
52
+ /**
53
+ * Chat completions with SignalVault guardrails.
54
+ * Supports both streaming and non-streaming.
55
+ */
56
+ get chat(): {
57
+ completions: {
58
+ create: (params: OpenAI.Chat.ChatCompletionCreateParams) => Promise<any>;
59
+ };
60
+ };
61
+ private handleMirrorMode;
62
+ private handleNormalMode;
63
+ private wrapStreamForAudit;
64
+ private wrapStreamForPostFlight;
65
+ private sendRequest;
66
+ private sendResponse;
67
+ private sendStreamResponse;
68
+ private sendAudit;
69
+ private sendStreamAudit;
70
+ }
71
+ export default SignalVaultClient;
package/dist/index.js ADDED
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SignalVaultClient = void 0;
7
+ const openai_1 = __importDefault(require("openai"));
8
+ const uuid_1 = require("uuid");
9
+ /**
10
+ * SignalVault Client — wraps OpenAI with guardrails and audit logging.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const client = new SignalVaultClient({
15
+ * apiKey: 'sk_live_...',
16
+ * openaiApiKey: process.env.OPENAI_API_KEY!,
17
+ * baseUrl: 'https://api.signalvault.io',
18
+ * });
19
+ *
20
+ * // Non-streaming
21
+ * const res = await client.chat.completions.create({ model: 'gpt-4', messages: [...] });
22
+ *
23
+ * // Streaming
24
+ * const stream = await client.chat.completions.create({ model: 'gpt-4', messages: [...], stream: true });
25
+ * for await (const chunk of stream) { ... }
26
+ * ```
27
+ */
28
+ class SignalVaultClient {
29
+ constructor(config) {
30
+ this.signalvaultApiKey = config.apiKey;
31
+ this.signalvaultBaseUrl = (config.baseUrl || 'http://localhost:4000').replace(/\/+$/, '');
32
+ this.signalvaultEnvironment = config.environment || 'production';
33
+ this.debugMode = config.debug || false;
34
+ this.mirrorMode = config.mirrorMode || false;
35
+ this.openai = new openai_1.default({
36
+ apiKey: config.openaiApiKey,
37
+ });
38
+ }
39
+ /**
40
+ * Chat completions with SignalVault guardrails.
41
+ * Supports both streaming and non-streaming.
42
+ */
43
+ get chat() {
44
+ return {
45
+ completions: {
46
+ create: async (params) => {
47
+ const requestId = (0, uuid_1.v4)();
48
+ if (this.debugMode) {
49
+ console.log('[SignalVault] Processing request:', requestId, this.mirrorMode ? '(MIRROR MODE)' : '', params.stream ? '(STREAMING)' : '');
50
+ }
51
+ // Mirror mode: call OpenAI first, then audit
52
+ if (this.mirrorMode) {
53
+ return this.handleMirrorMode(requestId, params);
54
+ }
55
+ // Normal mode: pre-flight checks first
56
+ return this.handleNormalMode(requestId, params);
57
+ },
58
+ },
59
+ };
60
+ }
61
+ // ---------------------------------------------------------------------------
62
+ // Mirror mode — no latency added, audit asynchronously
63
+ // ---------------------------------------------------------------------------
64
+ async handleMirrorMode(requestId, params) {
65
+ try {
66
+ const response = await this.openai.chat.completions.create(params);
67
+ if (params.stream) {
68
+ // Wrap the stream to collect chunks for auditing
69
+ return this.wrapStreamForAudit(requestId, params, response);
70
+ }
71
+ // Non-streaming: audit in background
72
+ const completion = response;
73
+ this.sendAudit(requestId, params, completion).catch((err) => {
74
+ if (this.debugMode) {
75
+ console.error('[SignalVault] Audit failed (non-blocking):', err);
76
+ }
77
+ });
78
+ return completion;
79
+ }
80
+ catch (error) {
81
+ if (this.debugMode) {
82
+ console.error('[SignalVault] OpenAI error:', error);
83
+ }
84
+ throw error;
85
+ }
86
+ }
87
+ // ---------------------------------------------------------------------------
88
+ // Normal mode — pre-flight guardrails, then OpenAI, then post-flight
89
+ // ---------------------------------------------------------------------------
90
+ async handleNormalMode(requestId, params) {
91
+ try {
92
+ // Pre-flight check
93
+ const decision = await this.sendRequest(requestId, params);
94
+ if (decision.decision === 'block') {
95
+ throw new Error(`[SignalVault] Request blocked: ${JSON.stringify(decision.violations)}`);
96
+ }
97
+ if (decision.decision === 'warn' && this.debugMode) {
98
+ console.warn('[SignalVault] Warnings:', decision.violations);
99
+ }
100
+ const response = await this.openai.chat.completions.create(params);
101
+ if (params.stream) {
102
+ // Wrap stream to collect output for post-flight logging
103
+ return this.wrapStreamForPostFlight(requestId, params, response);
104
+ }
105
+ // Non-streaming: post-flight logging
106
+ const completion = response;
107
+ await this.sendResponse(requestId, completion, params.model);
108
+ return completion;
109
+ }
110
+ catch (error) {
111
+ if (this.debugMode) {
112
+ console.error('[SignalVault] Error:', error);
113
+ }
114
+ throw error;
115
+ }
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Stream wrappers — collect chunks and send audit/post-flight events
119
+ // ---------------------------------------------------------------------------
120
+ async *wrapStreamForAudit(requestId, params, stream) {
121
+ const chunks = [];
122
+ let promptTokens = 0;
123
+ let completionTokens = 0;
124
+ for await (const chunk of stream) {
125
+ const content = chunk.choices[0]?.delta?.content || '';
126
+ if (content)
127
+ chunks.push(content);
128
+ // Collect usage if provided in final chunk
129
+ if (chunk.usage) {
130
+ promptTokens = chunk.usage.prompt_tokens || 0;
131
+ completionTokens = chunk.usage.completion_tokens || 0;
132
+ }
133
+ yield chunk;
134
+ }
135
+ // Send audit after stream completes
136
+ const fullOutput = chunks.join('');
137
+ this.sendStreamAudit(requestId, params, fullOutput, promptTokens, completionTokens).catch((err) => {
138
+ if (this.debugMode) {
139
+ console.error('[SignalVault] Stream audit failed:', err);
140
+ }
141
+ });
142
+ }
143
+ async *wrapStreamForPostFlight(requestId, params, stream) {
144
+ const chunks = [];
145
+ let promptTokens = 0;
146
+ let completionTokens = 0;
147
+ for await (const chunk of stream) {
148
+ const content = chunk.choices[0]?.delta?.content || '';
149
+ if (content)
150
+ chunks.push(content);
151
+ if (chunk.usage) {
152
+ promptTokens = chunk.usage.prompt_tokens || 0;
153
+ completionTokens = chunk.usage.completion_tokens || 0;
154
+ }
155
+ yield chunk;
156
+ }
157
+ // Post-flight logging
158
+ const fullOutput = chunks.join('');
159
+ try {
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);
165
+ }
166
+ }
167
+ }
168
+ // ---------------------------------------------------------------------------
169
+ // API communication
170
+ // ---------------------------------------------------------------------------
171
+ async sendRequest(requestId, params) {
172
+ const response = await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
173
+ method: 'POST',
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ Authorization: `Bearer ${this.signalvaultApiKey}`,
177
+ },
178
+ body: JSON.stringify({
179
+ type: 'ai.request',
180
+ request_id: requestId,
181
+ environment: this.signalvaultEnvironment,
182
+ provider: 'openai',
183
+ model: params.model,
184
+ metadata: {},
185
+ payload: { messages: params.messages },
186
+ }),
187
+ });
188
+ if (!response.ok) {
189
+ if (this.debugMode) {
190
+ console.error('[SignalVault] API error:', response.status);
191
+ }
192
+ return { decision: 'allow', violations: [], redactions: [] };
193
+ }
194
+ return (await response.json());
195
+ }
196
+ async sendResponse(requestId, response, model) {
197
+ try {
198
+ const output = response.choices[0]?.message?.content || '';
199
+ await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
200
+ method: 'POST',
201
+ headers: {
202
+ 'Content-Type': 'application/json',
203
+ Authorization: `Bearer ${this.signalvaultApiKey}`,
204
+ },
205
+ body: JSON.stringify({
206
+ type: 'ai.response',
207
+ request_id: requestId,
208
+ environment: this.signalvaultEnvironment,
209
+ provider: 'openai',
210
+ model,
211
+ metadata: {},
212
+ payload: {
213
+ output,
214
+ usage: {
215
+ prompt_tokens: response.usage?.prompt_tokens || 0,
216
+ completion_tokens: response.usage?.completion_tokens || 0,
217
+ },
218
+ },
219
+ }),
220
+ });
221
+ }
222
+ catch (error) {
223
+ if (this.debugMode) {
224
+ console.error('[SignalVault] Failed to send response:', error);
225
+ }
226
+ }
227
+ }
228
+ async sendStreamResponse(requestId, model, output, promptTokens, completionTokens) {
229
+ await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
230
+ method: 'POST',
231
+ headers: {
232
+ 'Content-Type': 'application/json',
233
+ Authorization: `Bearer ${this.signalvaultApiKey}`,
234
+ },
235
+ body: JSON.stringify({
236
+ type: 'ai.response',
237
+ request_id: requestId,
238
+ environment: this.signalvaultEnvironment,
239
+ provider: 'openai',
240
+ model,
241
+ metadata: {},
242
+ payload: {
243
+ output,
244
+ usage: { prompt_tokens: promptTokens, completion_tokens: completionTokens },
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 },
266
+ }),
267
+ });
268
+ // Send ai.response event
269
+ await fetch(`${this.signalvaultBaseUrl}/v1/events`, {
270
+ method: 'POST',
271
+ headers: {
272
+ 'Content-Type': 'application/json',
273
+ Authorization: `Bearer ${this.signalvaultApiKey}`,
274
+ },
275
+ body: JSON.stringify({
276
+ type: 'ai.response',
277
+ request_id: requestId,
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,
287
+ },
288
+ monitor_mode: true,
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
+ },
330
+ }),
331
+ });
332
+ }
333
+ }
334
+ exports.SignalVaultClient = SignalVaultClient;
335
+ exports.default = SignalVaultClient;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@signalvaultio/node",
3
+ "version": "0.1.0",
4
+ "description": "SignalVault SDK - AI audit logs and guardrails for safe production AI",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "prepublishOnly": "npm run build",
14
+ "test": "jest --passWithNoTests"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "audit",
19
+ "guardrails",
20
+ "openai",
21
+ "llm",
22
+ "compliance",
23
+ "security",
24
+ "monitoring"
25
+ ],
26
+ "author": "SignalVault <support@signalvault.io>",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/signalvaultio/signalvault-node"
31
+ },
32
+ "bugs": {
33
+ "url": "https://github.com/signalvaultio/signalvault-node/issues"
34
+ },
35
+ "homepage": "https://signalvault.io/docs",
36
+ "peerDependencies": {
37
+ "openai": "^4.0.0"
38
+ },
39
+ "dependencies": {
40
+ "uuid": "^9.0.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/jest": "^30.0.0",
44
+ "@types/node": "^20.0.0",
45
+ "@types/uuid": "^9.0.0",
46
+ "jest": "^30.2.0",
47
+ "openai": "^4.0.0",
48
+ "ts-jest": "^29.4.6",
49
+ "typescript": "^5.0.0"
50
+ }
51
+ }