@signalvaultio/node 0.5.0 → 0.5.1
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/index.d.ts +1 -1
- package/dist/index.js +58 -12
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type OpenAI from 'openai';
|
|
|
2
2
|
import { ToolsAPI } from './tools';
|
|
3
3
|
export type { ToolRecordOptions, ToolsAPI } from './tools';
|
|
4
4
|
/** Kept in sync with package.json by a unit test. */
|
|
5
|
-
export declare const SDK_VERSION = "0.5.
|
|
5
|
+
export declare const SDK_VERSION = "0.5.1";
|
|
6
6
|
/**
|
|
7
7
|
* Normalizes the base URL and refuses plaintext HTTP to anything but the local
|
|
8
8
|
* machine. The API key and full prompts travel in every request, so an
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.normalizeBaseUrl = normalizeBaseUrl;
|
|
|
5
5
|
const crypto_1 = require("crypto");
|
|
6
6
|
const tools_1 = require("./tools");
|
|
7
7
|
/** Kept in sync with package.json by a unit test. */
|
|
8
|
-
exports.SDK_VERSION = '0.5.
|
|
8
|
+
exports.SDK_VERSION = '0.5.1';
|
|
9
9
|
const DEFAULT_BASE_URL = 'https://api.signalvault.io';
|
|
10
10
|
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
11
11
|
/** Longest Retry-After the SDK will wait out before retrying a background event once. */
|
|
@@ -38,25 +38,67 @@ function assertNodeEnvironment() {
|
|
|
38
38
|
* `http://` URL would expose both before the server could redirect to HTTPS.
|
|
39
39
|
*/
|
|
40
40
|
function normalizeBaseUrl(raw) {
|
|
41
|
+
// Errors never quote `raw`: it may contain credentials.
|
|
41
42
|
let url;
|
|
42
43
|
try {
|
|
43
44
|
url = new URL(raw);
|
|
44
45
|
}
|
|
45
46
|
catch {
|
|
46
|
-
throw new Error(
|
|
47
|
+
throw new Error('[SignalVault] baseUrl is not a valid URL.');
|
|
48
|
+
}
|
|
49
|
+
if (url.username || url.password) {
|
|
50
|
+
throw new Error('[SignalVault] baseUrl must not contain credentials (user:password@). Pass the key as apiKey.');
|
|
47
51
|
}
|
|
48
52
|
if (url.protocol === 'http:' && !LOCAL_HOSTS.has(url.hostname)) {
|
|
49
53
|
throw new Error('[SignalVault] baseUrl must use https:// (plain http:// is only allowed for localhost). ' +
|
|
50
54
|
'Your API key and prompts would otherwise be sent unencrypted.');
|
|
51
55
|
}
|
|
52
56
|
if (url.search || url.hash) {
|
|
53
|
-
throw new Error(
|
|
57
|
+
throw new Error('[SignalVault] baseUrl must not contain a query or fragment.');
|
|
54
58
|
}
|
|
55
59
|
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
56
60
|
throw new Error(`[SignalVault] baseUrl must be an https:// URL, got ${url.protocol}`);
|
|
57
61
|
}
|
|
58
62
|
return raw.replace(/\/+$/, '');
|
|
59
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Strips surrounding whitespace and refuses keys that can't go in a header.
|
|
66
|
+
* A key read from a file or a secret often ends in a newline. Sent as is,
|
|
67
|
+
* every request fails before it leaves the process, so with failMode 'open'
|
|
68
|
+
* every call would go through unchecked. Errors never include the key.
|
|
69
|
+
*/
|
|
70
|
+
function normalizeApiKey(raw) {
|
|
71
|
+
if (typeof raw !== 'string') {
|
|
72
|
+
throw new TypeError(`[SignalVault] apiKey must be a string, got ${raw === null ? 'null' : typeof raw}.`);
|
|
73
|
+
}
|
|
74
|
+
const key = raw.trim();
|
|
75
|
+
if (!key) {
|
|
76
|
+
throw new Error('[SignalVault] apiKey is empty.');
|
|
77
|
+
}
|
|
78
|
+
if (/[^\x21-\x7e]/.test(key)) {
|
|
79
|
+
throw new Error('[SignalVault] apiKey contains whitespace, control or non-ASCII characters. Check how the key is loaded.');
|
|
80
|
+
}
|
|
81
|
+
return key;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* One line describing an error and its causes, for debug output. Never
|
|
85
|
+
* includes `secret`, and never throws.
|
|
86
|
+
*/
|
|
87
|
+
function describeError(error, secret) {
|
|
88
|
+
const parts = [];
|
|
89
|
+
let current = error;
|
|
90
|
+
for (let depth = 0; current !== undefined && current !== null && depth < 5; depth++) {
|
|
91
|
+
try {
|
|
92
|
+
parts.push(current instanceof Error ? `${current.name}: ${current.message}` : String(current));
|
|
93
|
+
current = current instanceof Error ? current.cause : undefined;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
parts.push('<unprintable error>');
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return parts.join(' <- ').split(secret).join('[redacted]');
|
|
101
|
+
}
|
|
60
102
|
/** Thrown when a guardrail rule blocks the request. */
|
|
61
103
|
class SignalVaultBlockedError extends Error {
|
|
62
104
|
constructor(requestId, violations, dashboardUrl) {
|
|
@@ -236,7 +278,7 @@ class SignalVaultClient {
|
|
|
236
278
|
if (config.failMode !== undefined && config.failMode !== 'open' && config.failMode !== 'closed') {
|
|
237
279
|
throw new Error("[SignalVault] failMode must be 'open' or 'closed'.");
|
|
238
280
|
}
|
|
239
|
-
this.svApiKey = config.apiKey;
|
|
281
|
+
this.svApiKey = normalizeApiKey(config.apiKey);
|
|
240
282
|
this.svBaseUrl = normalizeBaseUrl(config.baseUrl || DEFAULT_BASE_URL);
|
|
241
283
|
this.svEnvironment = config.environment || 'production';
|
|
242
284
|
this.debugMode = config.debug || false;
|
|
@@ -282,8 +324,9 @@ class SignalVaultClient {
|
|
|
282
324
|
track(promise) {
|
|
283
325
|
const tracked = promise
|
|
284
326
|
.catch((err) => {
|
|
285
|
-
if (this.debugMode)
|
|
286
|
-
console.error('[SignalVault] Background event failed:', err);
|
|
327
|
+
if (this.debugMode) {
|
|
328
|
+
console.error('[SignalVault] Background event failed:', describeError(err, this.svApiKey));
|
|
329
|
+
}
|
|
287
330
|
})
|
|
288
331
|
.finally(() => this.pending.delete(tracked));
|
|
289
332
|
this.pending.add(tracked);
|
|
@@ -699,8 +742,9 @@ class SignalVaultClient {
|
|
|
699
742
|
}
|
|
700
743
|
}
|
|
701
744
|
catch (error) {
|
|
702
|
-
if (this.debugMode)
|
|
703
|
-
console.error('[SignalVault] Event send failed:', error);
|
|
745
|
+
if (this.debugMode) {
|
|
746
|
+
console.error('[SignalVault] Event send failed:', describeError(error, this.svApiKey));
|
|
747
|
+
}
|
|
704
748
|
if (!isTimeout(error))
|
|
705
749
|
retryDelayMs = 250 + Math.random() * 500;
|
|
706
750
|
}
|
|
@@ -712,8 +756,9 @@ class SignalVaultClient {
|
|
|
712
756
|
return result;
|
|
713
757
|
}
|
|
714
758
|
catch (error) {
|
|
715
|
-
if (this.debugMode)
|
|
716
|
-
console.error('[SignalVault] Event retry failed:', error);
|
|
759
|
+
if (this.debugMode) {
|
|
760
|
+
console.error('[SignalVault] Event retry failed:', describeError(error, this.svApiKey));
|
|
761
|
+
}
|
|
717
762
|
}
|
|
718
763
|
}
|
|
719
764
|
this.logUndelivered(result);
|
|
@@ -734,8 +779,9 @@ class SignalVaultClient {
|
|
|
734
779
|
}
|
|
735
780
|
catch (error) {
|
|
736
781
|
const timedOut = isTimeout(error);
|
|
737
|
-
if (this.debugMode)
|
|
738
|
-
console.error('[SignalVault] Pre-flight failed:', error);
|
|
782
|
+
if (this.debugMode) {
|
|
783
|
+
console.error('[SignalVault] Pre-flight failed:', describeError(error, this.svApiKey));
|
|
784
|
+
}
|
|
739
785
|
return this.unavailable(requestId, timedOut ? 'timeout' : 'network', timedOut
|
|
740
786
|
? `SignalVault pre-flight check timed out after ${this.preflightTimeout}ms`
|
|
741
787
|
: 'SignalVault API is unreachable');
|