@signalvaultio/node 0.2.0 → 0.4.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/LICENSE +21 -0
- package/README.md +108 -9
- package/dist/index.d.ts +166 -17
- package/dist/index.js +588 -189
- package/dist/tools.d.ts +69 -0
- package/dist/tools.js +207 -0
- package/package.json +13 -9
- package/dist/__tests__/index.test.d.ts +0 -1
- package/dist/__tests__/index.test.js +0 -99
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SignalVault
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
AI audit logs and guardrails for your OpenAI and Anthropic applications.
|
|
4
4
|
|
|
5
|
+
> **Server-side only.** This SDK uses Node APIs (`async_hooks`, `fetch`) and
|
|
6
|
+
> requires Node.js **18+**. Do **not** bundle it for the browser — your
|
|
7
|
+
> `apiKey`, `openaiApiKey`, and `anthropicApiKey` would be exposed to end
|
|
8
|
+
> users. Always call SignalVault from your backend.
|
|
9
|
+
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
7
12
|
```bash
|
|
@@ -57,7 +62,7 @@ console.log(response.content[0].text);
|
|
|
57
62
|
|
|
58
63
|
## Streaming
|
|
59
64
|
|
|
60
|
-
Streaming is fully supported for both providers. SignalVault logs the
|
|
65
|
+
Streaming is fully supported for both providers. SignalVault logs the response when the stream ends — including a partial response if you stop iterating early or the stream fails. A stream you never iterate is not logged.
|
|
61
66
|
|
|
62
67
|
```typescript
|
|
63
68
|
// OpenAI streaming
|
|
@@ -86,6 +91,72 @@ for await (const event of stream) {
|
|
|
86
91
|
}
|
|
87
92
|
```
|
|
88
93
|
|
|
94
|
+
For token counts on OpenAI streams, pass `stream_options: { include_usage: true }`. The SDK does not add it for you, because OpenAI then sends a final chunk with an empty `choices` array.
|
|
95
|
+
|
|
96
|
+
## Agent Tool-Use Capture
|
|
97
|
+
|
|
98
|
+
Log every tool invocation made by your agents — name, input, output, duration, and any error — as auditable events alongside your LLM calls.
|
|
99
|
+
|
|
100
|
+
### Wrapper API (recommended)
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const fetchWeather = client.tool('fetch_weather', async (city: string) => {
|
|
104
|
+
const res = await fetch(`https://api.example.com/weather?city=${city}`);
|
|
105
|
+
return res.json();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Call it like the original — SignalVault auto-times and audits
|
|
109
|
+
const weather = await fetchWeather('London');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The wrapper records the call asynchronously (no impact on your tool's latency) and passes the result through unchanged. Errors are recorded and rethrown.
|
|
113
|
+
|
|
114
|
+
### Manual API
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
await client.tools.record({
|
|
118
|
+
toolName: 'fetch_weather',
|
|
119
|
+
toolInput: { city: 'London' },
|
|
120
|
+
toolOutput: { temp: 12.3 },
|
|
121
|
+
durationMs: 142,
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### What gets captured (and what to keep out)
|
|
126
|
+
|
|
127
|
+
When you wrap a tool or call `tools.record()`, SignalVault captures:
|
|
128
|
+
|
|
129
|
+
- `tool_name` (truncated to 200 bytes)
|
|
130
|
+
- `tool_input` — the function arguments, JSON-serialized (capped at 256 KB; oversize values are truncated with a marker)
|
|
131
|
+
- `tool_output` — the function return value, JSON-serialized (same 256 KB cap)
|
|
132
|
+
- `error.message` if the tool throws (truncated to 1900 bytes)
|
|
133
|
+
- `duration_ms`, `started_at`, and any `metadata` you attach
|
|
134
|
+
|
|
135
|
+
`tool_input` and `tool_output` are encrypted at rest server-side. `tool_name`,
|
|
136
|
+
`error`, `started_at` and `metadata` are stored as sent, **not encrypted**. All of
|
|
137
|
+
it goes on the wire to SignalVault's API: **if you pass user PII, secrets, or API
|
|
138
|
+
keys as tool arguments, those values will leave your process and be stored in
|
|
139
|
+
SignalVault.** Recommendations:
|
|
140
|
+
|
|
141
|
+
- Sanitize sensitive arguments before invoking the wrapped tool, or use the
|
|
142
|
+
manual `tools.record()` API and pass a redacted copy.
|
|
143
|
+
- Don't put secrets in error messages — they end up in `error` verbatim and unencrypted.
|
|
144
|
+
- Use `metadata` for non-sensitive identifiers (`user_id`, `feature`,
|
|
145
|
+
`workspace_id`); avoid putting raw user content in metadata.
|
|
146
|
+
|
|
147
|
+
### Linking tool calls to a parent LLM turn
|
|
148
|
+
|
|
149
|
+
Wrap your agent loop in `withContext` and tool calls inside auto-correlate to the given `requestId`:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
await client.withContext({ requestId: 'agent-turn-abc' }, async () => {
|
|
153
|
+
const llmResponse = await client.chat.completions.create({...});
|
|
154
|
+
await fetchWeather('London'); // auto-linked to 'agent-turn-abc'
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Without `withContext`, tool calls are recorded as orphans (no parent request).
|
|
159
|
+
|
|
89
160
|
## Metadata
|
|
90
161
|
|
|
91
162
|
Attach contextual metadata to every event — perfect for audit trails, user attribution, and analytics:
|
|
@@ -105,19 +176,37 @@ const response = await client.chat.completions.create(
|
|
|
105
176
|
);
|
|
106
177
|
```
|
|
107
178
|
|
|
108
|
-
##
|
|
179
|
+
## When SignalVault Is Unavailable
|
|
180
|
+
|
|
181
|
+
The pre-flight guardrail check is in your request's critical path. If it cannot return a decision — timeout, network error, invalid or revoked API key (401), inactive subscription (402), access denied (403), rate or trial limit (429), server error, or an invalid response — `failMode` decides what happens:
|
|
182
|
+
|
|
183
|
+
- `'open'` (default): the request goes to the provider **without** guardrails, and the SDK prints a warning (at most once a minute per cause, whether or not `debug` is on). If SignalVault was unreachable, timed out or errored, the request is still recorded in the background, marked `preflight_unavailable`, so it appears in your audit log.
|
|
184
|
+
- `'closed'`: the SDK throws `SignalVaultUnavailableError` and the provider is never called. Use this if every request must be checked.
|
|
109
185
|
|
|
110
|
-
|
|
186
|
+
`failMode` applies to the pre-flight check, so it has no effect in mirror mode.
|
|
187
|
+
|
|
188
|
+
**Rate limit.** The ingest API allows 120 events per minute per app, and each call sends two (request and response), plus one per tool call. Above roughly 60 LLM calls a minute, pre-flight checks are rate limited: with `failMode: 'open'` those requests go through unchecked. If you run at that volume and need enforcement, use `failMode: 'closed'`.
|
|
111
189
|
|
|
112
190
|
```typescript
|
|
113
191
|
const client = new SignalVaultClient({
|
|
114
192
|
apiKey: 'sk_live_...',
|
|
115
193
|
openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
116
|
-
|
|
194
|
+
failMode: 'closed', // 'open' | 'closed'. Default: 'open'
|
|
195
|
+
preflightTimeout: 2000, // ms — pre-flight check timeout. Default: 2000
|
|
117
196
|
timeout: 10000, // ms — background/post-flight calls. Default: 10000
|
|
118
197
|
});
|
|
119
198
|
```
|
|
120
199
|
|
|
200
|
+
Audit events (responses, mirror-mode events, tool calls) are sent in the background. Each carries an `event_id`, so a retry is never double-counted. The SDK retries once on connection errors and 5xx (not on timeouts). A rate-limited (429) event is retried only if `Retry-After` is 5 seconds or less; the SignalVault API currently asks for 60, so in practice those events are dropped, with a warning. `tools.record()` does not retry.
|
|
201
|
+
|
|
202
|
+
## Shutting Down
|
|
203
|
+
|
|
204
|
+
Background events are queued in memory. Before a short-lived process exits (a script, a serverless handler), wait for them:
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
await client.flush(); // or client.close(); both wait up to 5s by default
|
|
208
|
+
```
|
|
209
|
+
|
|
121
210
|
## Mirror Mode
|
|
122
211
|
|
|
123
212
|
In mirror mode, requests go directly to the AI provider first and SignalVault audits them asynchronously — no latency added, never blocks:
|
|
@@ -133,7 +222,8 @@ const client = new SignalVaultClient({
|
|
|
133
222
|
## Features
|
|
134
223
|
|
|
135
224
|
- **Automatic Logging** — Every request and response is recorded in your SignalVault dashboard
|
|
136
|
-
- **Pre-flight Guardrails** — Block
|
|
225
|
+
- **Pre-flight Guardrails** — Block requests that break your rules before they reach the AI provider
|
|
226
|
+
- **Redaction at rest** — Rules with the `redact` action remove matches from what SignalVault stores. The request sent to the AI provider is **not** modified.
|
|
137
227
|
- **PII Detection** — Automatically detect emails, phone numbers, SSNs in prompts
|
|
138
228
|
- **Secret Detection** — Block API keys and tokens in prompts
|
|
139
229
|
- **Token Limits** — Enforce cost controls per request
|
|
@@ -150,10 +240,11 @@ const client = new SignalVaultClient({
|
|
|
150
240
|
apiKey: 'sk_live_...', // Your SignalVault API key (required)
|
|
151
241
|
openaiApiKey: 'sk-...', // OpenAI API key (required for chat.completions)
|
|
152
242
|
anthropicApiKey: 'sk-ant-...', // Anthropic API key (required for messages)
|
|
153
|
-
baseUrl: 'https://api.signalvault.io',
|
|
154
|
-
environment: 'production', //
|
|
243
|
+
baseUrl: 'https://api.signalvault.io', // default; plain http:// only allowed for localhost
|
|
244
|
+
environment: 'production', // sent with events; the server uses the API key's environment
|
|
155
245
|
debug: false,
|
|
156
246
|
mirrorMode: false,
|
|
247
|
+
failMode: 'open', // 'open' | 'closed' — see "When SignalVault Is Unavailable"
|
|
157
248
|
preflightTimeout: 2000, // ms
|
|
158
249
|
timeout: 10000, // ms
|
|
159
250
|
metadata: {}, // Default metadata for all events
|
|
@@ -163,14 +254,22 @@ const client = new SignalVaultClient({
|
|
|
163
254
|
## Error Handling
|
|
164
255
|
|
|
165
256
|
```typescript
|
|
257
|
+
import { SignalVaultBlockedError, SignalVaultUnavailableError } from '@signalvaultio/node';
|
|
258
|
+
|
|
166
259
|
try {
|
|
167
260
|
const response = await client.chat.completions.create({
|
|
168
261
|
model: 'gpt-4',
|
|
169
262
|
messages: [{ role: 'user', content: 'my SSN is 123-45-6789' }],
|
|
170
263
|
});
|
|
171
264
|
} catch (error) {
|
|
172
|
-
if (error
|
|
173
|
-
console.log('
|
|
265
|
+
if (error instanceof SignalVaultBlockedError) {
|
|
266
|
+
console.log('Blocked by guardrails:', error.violations.map((v) => v.type));
|
|
267
|
+
console.log('Details:', error.dashboardUrl);
|
|
268
|
+
} else if (error instanceof SignalVaultUnavailableError) {
|
|
269
|
+
// Only thrown with failMode: 'closed'
|
|
270
|
+
console.log('Guardrail check unavailable:', error.status);
|
|
271
|
+
} else {
|
|
272
|
+
throw error;
|
|
174
273
|
}
|
|
175
274
|
}
|
|
176
275
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
import OpenAI from 'openai';
|
|
2
|
+
import { ToolsAPI } from './tools';
|
|
3
|
+
export type { ToolRecordOptions, ToolsAPI } from './tools';
|
|
4
|
+
/** Kept in sync with package.json by a unit test. */
|
|
5
|
+
export declare const SDK_VERSION = "0.4.0";
|
|
6
|
+
/**
|
|
7
|
+
* Normalizes the base URL and refuses plaintext HTTP to anything but the local
|
|
8
|
+
* machine. The API key and full prompts travel in every request, so an
|
|
9
|
+
* `http://` URL would expose both before the server could redirect to HTTPS.
|
|
10
|
+
*/
|
|
11
|
+
export declare function normalizeBaseUrl(raw: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* What to do when SignalVault cannot return a guardrail decision (unreachable,
|
|
14
|
+
* timed out, rate limited, invalid key, inactive subscription, ...).
|
|
15
|
+
*
|
|
16
|
+
* - `'open'` (default): allow the request and print a warning.
|
|
17
|
+
* - `'closed'`: throw {@link SignalVaultUnavailableError} instead of calling
|
|
18
|
+
* the provider unchecked.
|
|
19
|
+
*/
|
|
20
|
+
export type FailMode = 'open' | 'closed';
|
|
2
21
|
export interface SignalVaultConfig {
|
|
3
22
|
apiKey: string;
|
|
4
23
|
/** Required for OpenAI (chat.completions). */
|
|
5
24
|
openaiApiKey?: string;
|
|
6
25
|
/** Required for Anthropic (messages). Install @anthropic-ai/sdk separately. */
|
|
7
26
|
anthropicApiKey?: string;
|
|
27
|
+
/** Default: https://api.signalvault.io. Plain http:// is only accepted for localhost. */
|
|
8
28
|
baseUrl?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Sent with every event for backwards compatibility. The server takes the
|
|
31
|
+
* environment from the API key, so this value does not select rules.
|
|
32
|
+
*/
|
|
9
33
|
environment?: 'development' | 'staging' | 'production';
|
|
10
34
|
debug?: boolean;
|
|
11
35
|
mirrorMode?: boolean;
|
|
36
|
+
/** Behaviour when no guardrail decision can be obtained. Default: 'open'. */
|
|
37
|
+
failMode?: FailMode;
|
|
12
38
|
/**
|
|
13
39
|
* Timeout (ms) for the pre-flight /v1/events call in normal mode.
|
|
14
|
-
* This is in the critical path
|
|
15
|
-
* Default: 2000
|
|
40
|
+
* This is in the critical path. Default: 2000
|
|
16
41
|
*/
|
|
17
42
|
preflightTimeout?: number;
|
|
18
43
|
/**
|
|
@@ -26,25 +51,45 @@ export interface SignalVaultConfig {
|
|
|
26
51
|
*/
|
|
27
52
|
metadata?: Record<string, unknown>;
|
|
28
53
|
}
|
|
54
|
+
export interface SignalVaultViolation {
|
|
55
|
+
rule_id?: string;
|
|
56
|
+
type: string;
|
|
57
|
+
severity: number;
|
|
58
|
+
action: string;
|
|
59
|
+
details: Record<string, unknown>;
|
|
60
|
+
}
|
|
29
61
|
export interface SignalVaultDecision {
|
|
30
62
|
decision: 'allow' | 'warn' | 'block' | 'redact';
|
|
31
|
-
violations:
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}>;
|
|
63
|
+
violations: SignalVaultViolation[];
|
|
64
|
+
/**
|
|
65
|
+
* Rule types whose redact action matched, with match counts. Redaction
|
|
66
|
+
* applies to what SignalVault stores; the request sent to the provider is
|
|
67
|
+
* not modified. Check `.length`, never truthiness.
|
|
68
|
+
*/
|
|
38
69
|
redactions: Array<{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
replacement: string;
|
|
70
|
+
type: string;
|
|
71
|
+
count: number;
|
|
42
72
|
}>;
|
|
73
|
+
/** Present on block decisions. */
|
|
74
|
+
dashboard_url?: string;
|
|
43
75
|
}
|
|
44
76
|
export interface CreateOptions {
|
|
45
77
|
/** Per-call metadata, merged over config-level metadata. */
|
|
46
78
|
metadata?: Record<string, unknown>;
|
|
47
79
|
}
|
|
80
|
+
/** Thrown when a guardrail rule blocks the request. */
|
|
81
|
+
export declare class SignalVaultBlockedError extends Error {
|
|
82
|
+
readonly requestId: string;
|
|
83
|
+
readonly violations: SignalVaultViolation[];
|
|
84
|
+
readonly dashboardUrl?: string;
|
|
85
|
+
constructor(requestId: string, violations: SignalVaultViolation[], dashboardUrl?: string);
|
|
86
|
+
}
|
|
87
|
+
/** Thrown in `failMode: 'closed'` when no guardrail decision could be obtained. */
|
|
88
|
+
export declare class SignalVaultUnavailableError extends Error {
|
|
89
|
+
readonly requestId: string;
|
|
90
|
+
readonly status?: number;
|
|
91
|
+
constructor(requestId: string, reason: string, status?: number);
|
|
92
|
+
}
|
|
48
93
|
/**
|
|
49
94
|
* SignalVault Client — wraps OpenAI and/or Anthropic with guardrails and audit logging.
|
|
50
95
|
*
|
|
@@ -53,10 +98,9 @@ export interface CreateOptions {
|
|
|
53
98
|
* const client = new SignalVaultClient({
|
|
54
99
|
* apiKey: 'sk_live_...',
|
|
55
100
|
* openaiApiKey: process.env.OPENAI_API_KEY!,
|
|
56
|
-
* baseUrl: 'https://api.signalvault.io',
|
|
57
101
|
* metadata: { user_id: '123' },
|
|
58
102
|
* });
|
|
59
|
-
* const res = await client.chat.completions.create({ model: 'gpt-
|
|
103
|
+
* const res = await client.chat.completions.create({ model: 'gpt-4o', messages: [...] });
|
|
60
104
|
* ```
|
|
61
105
|
*
|
|
62
106
|
* @example Anthropic
|
|
@@ -64,9 +108,8 @@ export interface CreateOptions {
|
|
|
64
108
|
* const client = new SignalVaultClient({
|
|
65
109
|
* apiKey: 'sk_live_...',
|
|
66
110
|
* anthropicApiKey: process.env.ANTHROPIC_API_KEY!,
|
|
67
|
-
* baseUrl: 'https://api.signalvault.io',
|
|
68
111
|
* });
|
|
69
|
-
* const res = await client.messages.create({ model: 'claude-
|
|
112
|
+
* const res = await client.messages.create({ model: 'claude-sonnet-4-5', messages: [...], max_tokens: 1024 });
|
|
70
113
|
* ```
|
|
71
114
|
*/
|
|
72
115
|
export declare class SignalVaultClient {
|
|
@@ -75,12 +118,81 @@ export declare class SignalVaultClient {
|
|
|
75
118
|
private readonly svEnvironment;
|
|
76
119
|
private readonly debugMode;
|
|
77
120
|
private readonly mirrorMode;
|
|
121
|
+
private readonly failMode;
|
|
78
122
|
private readonly preflightTimeout;
|
|
79
123
|
private readonly bgTimeout;
|
|
80
124
|
private readonly defaultMetadata;
|
|
81
125
|
private readonly openai;
|
|
82
126
|
private readonly anthropic;
|
|
127
|
+
private readonly pending;
|
|
128
|
+
private readonly lastWarned;
|
|
83
129
|
constructor(config: SignalVaultConfig);
|
|
130
|
+
/**
|
|
131
|
+
* Waits for in-flight audit events to finish sending, up to `timeoutMs`.
|
|
132
|
+
* Call before a short-lived process exits (scripts, serverless handlers),
|
|
133
|
+
* otherwise queued events are lost.
|
|
134
|
+
*/
|
|
135
|
+
flush(timeoutMs?: number): Promise<void>;
|
|
136
|
+
/** Flushes pending events. The client can still be used afterwards. */
|
|
137
|
+
close(timeoutMs?: number): Promise<void>;
|
|
138
|
+
/** Tracks a background promise so flush() can wait for it. Never rejects. */
|
|
139
|
+
private track;
|
|
140
|
+
/** Prints a warning, at most once per minute per key. Not gated on debug. */
|
|
141
|
+
private warn;
|
|
142
|
+
/**
|
|
143
|
+
* Wraps a function so every call records an `agent.tool_call` event with
|
|
144
|
+
* timing, input, output, and any error. The wrapper auto-times, runs the
|
|
145
|
+
* inner function, fires the audit event in the background, and returns the
|
|
146
|
+
* inner result (or rethrows the inner error).
|
|
147
|
+
*
|
|
148
|
+
* Tool calls invoked inside a `withContext({ requestId })` block auto-pick
|
|
149
|
+
* up the requestId for correlation with the parent ai.request.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```typescript
|
|
153
|
+
* const fetchWeather = signalvault.tool('fetch_weather', async (city: string) => {
|
|
154
|
+
* const res = await fetch(`https://api.example.com/weather?city=${city}`);
|
|
155
|
+
* return res.json();
|
|
156
|
+
* });
|
|
157
|
+
*
|
|
158
|
+
* const weather = await fetchWeather('London');
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
tool<TArgs extends readonly unknown[], TReturn>(name: string, fn: (...args: TArgs) => TReturn | Promise<TReturn>, options?: {
|
|
162
|
+
metadata?: Record<string, unknown>;
|
|
163
|
+
}): (...args: TArgs) => Promise<TReturn>;
|
|
164
|
+
/**
|
|
165
|
+
* Manual tool recording API. Use when the wrapper isn't a fit (streaming
|
|
166
|
+
* tools, custom timing, post-hoc capture from logs).
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* ```typescript
|
|
170
|
+
* await signalvault.tools.record({
|
|
171
|
+
* toolName: 'fetch_weather',
|
|
172
|
+
* toolInput: { city: 'London' },
|
|
173
|
+
* toolOutput: { temp: 12.3 },
|
|
174
|
+
* durationMs: 142,
|
|
175
|
+
* });
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
get tools(): ToolsAPI;
|
|
179
|
+
/**
|
|
180
|
+
* Runs the given async function inside a context where tool calls are
|
|
181
|
+
* automatically correlated to the provided `requestId`. Useful for grouping
|
|
182
|
+
* a multi-step agent loop under one logical turn.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```typescript
|
|
186
|
+
* await signalvault.withContext({ requestId: 'agent-turn-abc' }, async () => {
|
|
187
|
+
* const llm = await signalvault.chat.completions.create({...});
|
|
188
|
+
* await fetchWeather('London'); // tool call auto-linked to 'agent-turn-abc'
|
|
189
|
+
* });
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
withContext<T>(ctx: {
|
|
193
|
+
requestId?: string;
|
|
194
|
+
}, fn: () => Promise<T>): Promise<T>;
|
|
195
|
+
private sendToolCallEvent;
|
|
84
196
|
get chat(): {
|
|
85
197
|
completions: {
|
|
86
198
|
create(params: OpenAI.Chat.ChatCompletionCreateParamsNonStreaming, options?: CreateOptions): Promise<OpenAI.Chat.ChatCompletion>;
|
|
@@ -94,11 +206,48 @@ export declare class SignalVaultClient {
|
|
|
94
206
|
private handleOpenAINormal;
|
|
95
207
|
private handleAnthropicMirror;
|
|
96
208
|
private handleAnthropicNormal;
|
|
209
|
+
/**
|
|
210
|
+
* Yields chunks through unchanged and records the response when the stream
|
|
211
|
+
* ends — including when the consumer breaks out early or the stream throws,
|
|
212
|
+
* in which case the partial output is recorded.
|
|
213
|
+
*/
|
|
97
214
|
private wrapStream;
|
|
98
215
|
private wrapAnthropicStream;
|
|
99
|
-
|
|
216
|
+
/** Runs the pre-flight check and throws if the request must not proceed. */
|
|
217
|
+
private enforce;
|
|
218
|
+
/**
|
|
219
|
+
* Called when no decision could be obtained. Throws in failMode 'closed';
|
|
220
|
+
* otherwise warns (never silently) and allows.
|
|
221
|
+
*/
|
|
222
|
+
private unavailable;
|
|
223
|
+
/**
|
|
224
|
+
* The ai.request body to record in the background when the pre-flight got
|
|
225
|
+
* no decision, so an unchecked call is still in the audit log. Undefined
|
|
226
|
+
* when the pre-flight was recorded normally; null when the server refused
|
|
227
|
+
* it and no audit event should be sent.
|
|
228
|
+
*/
|
|
229
|
+
private fallbackRequest;
|
|
230
|
+
private requestBody;
|
|
231
|
+
private describeFailure;
|
|
232
|
+
private headers;
|
|
233
|
+
/** Single POST. Never follows redirects, so the API key is never re-sent elsewhere. */
|
|
234
|
+
private post;
|
|
235
|
+
/**
|
|
236
|
+
* POST for background events. Adds an `event_id` so a retry is never
|
|
237
|
+
* double-counted, and retries once on connection errors, 5xx, and 429 with a
|
|
238
|
+
* short Retry-After. Timeouts are not retried: the server may be slow rather
|
|
239
|
+
* than down, and a retry would double the time the event is held. Returns
|
|
240
|
+
* the last response, or null if nothing was received.
|
|
241
|
+
*/
|
|
242
|
+
private postBackground;
|
|
243
|
+
private logUndelivered;
|
|
100
244
|
private sendRequest;
|
|
101
245
|
private sendResponseEvent;
|
|
246
|
+
/**
|
|
247
|
+
* Mirror mode: records the request, then the response. Sequential on
|
|
248
|
+
* purpose — the server rejects an ai.response whose ai.request it has not
|
|
249
|
+
* stored yet, so sending both at once can lose the response.
|
|
250
|
+
*/
|
|
102
251
|
private sendAuditEvents;
|
|
103
252
|
}
|
|
104
253
|
export default SignalVaultClient;
|