sovergate 0.0.0-stage → 0.1.0-alpha.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/README.md +64 -2
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +331 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +302 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -4
package/README.md
CHANGED
|
@@ -1,3 +1,65 @@
|
|
|
1
|
-
#
|
|
1
|
+
# sovergate — TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
EU AI Act Article 12 compliance — automatic, non-invasive audit logging for LLM calls.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install sovergate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18+.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import OpenAI from 'openai'
|
|
17
|
+
import { init, instrument } from 'sovergate'
|
|
18
|
+
|
|
19
|
+
const openai = new OpenAI()
|
|
20
|
+
|
|
21
|
+
init({ apiKey: 'sov_prod_xxxx' })
|
|
22
|
+
instrument(openai)
|
|
23
|
+
|
|
24
|
+
// Your existing code is completely unchanged
|
|
25
|
+
const response = await openai.chat.completions.create({
|
|
26
|
+
model: 'gpt-4o',
|
|
27
|
+
messages: [{ role: 'user', content: 'Hello' }],
|
|
28
|
+
})
|
|
29
|
+
// Response returned immediately — logging fires in background
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Anthropic works the same way:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
36
|
+
import { init, instrument } from 'sovergate'
|
|
37
|
+
|
|
38
|
+
const client = new Anthropic()
|
|
39
|
+
init({ apiKey: 'sov_prod_xxxx' })
|
|
40
|
+
instrument(client)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Options
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
init({
|
|
47
|
+
apiKey: 'sov_prod_xxxx', // required
|
|
48
|
+
baseUrl: 'https://...', // default: https://api.sovergate.com
|
|
49
|
+
debug: true, // print SDK internals to console.debug
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Guarantees
|
|
54
|
+
|
|
55
|
+
- Never blocks the event loop — logging runs via `setImmediate`
|
|
56
|
+
- Never throws — if setup fails, the SDK logs a `console.warn` and stays out of the way
|
|
57
|
+
- Leaves your client unchanged — `create()` still returns the SDK's own object, so `.withResponse()` and `.asResponse()` keep working
|
|
58
|
+
- 2 second timeout on every HTTP call to our servers
|
|
59
|
+
- Queues locally and retries with exponential backoff if our servers are down
|
|
60
|
+
- PII is scrubbed **before** any data leaves your infrastructure
|
|
61
|
+
|
|
62
|
+
## Current limitations
|
|
63
|
+
|
|
64
|
+
- Streaming calls (`stream: true`) are passed through untouched but not logged yet.
|
|
65
|
+
- PII placeholders are positional (`[EMAIL_REDACTED_1]`) and not correlatable across logs.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface InitOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare function init({ apiKey, baseUrl, debug }: InitOptions): void;
|
|
7
|
+
declare function instrument(client: any): void;
|
|
8
|
+
declare function shutdown(): void;
|
|
9
|
+
|
|
10
|
+
interface Detection {
|
|
11
|
+
category: string;
|
|
12
|
+
placeholder: string;
|
|
13
|
+
}
|
|
14
|
+
interface ScrubResult {
|
|
15
|
+
text: string;
|
|
16
|
+
detections: Detection[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { type Detection, type InitOptions, type ScrubResult, init, instrument, shutdown };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface InitOptions {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
debug?: boolean;
|
|
5
|
+
}
|
|
6
|
+
declare function init({ apiKey, baseUrl, debug }: InitOptions): void;
|
|
7
|
+
declare function instrument(client: any): void;
|
|
8
|
+
declare function shutdown(): void;
|
|
9
|
+
|
|
10
|
+
interface Detection {
|
|
11
|
+
category: string;
|
|
12
|
+
placeholder: string;
|
|
13
|
+
}
|
|
14
|
+
interface ScrubResult {
|
|
15
|
+
text: string;
|
|
16
|
+
detections: Detection[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { type Detection, type InitOptions, type ScrubResult, init, instrument, shutdown };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
init: () => init,
|
|
24
|
+
instrument: () => instrument,
|
|
25
|
+
shutdown: () => shutdown
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/sender.ts
|
|
30
|
+
var BASE_URL = "https://api.sovergate.com";
|
|
31
|
+
var TIMEOUT_MS = 2e3;
|
|
32
|
+
async function send(apiKey, payload, baseUrl = BASE_URL) {
|
|
33
|
+
try {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
36
|
+
const response = await fetch(`${baseUrl}/ingest`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${apiKey}`,
|
|
40
|
+
"Content-Type": "application/json"
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(payload),
|
|
43
|
+
signal: controller.signal
|
|
44
|
+
});
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
return response.status < 400;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/queue.ts
|
|
53
|
+
var MAX_QUEUE = 1e4;
|
|
54
|
+
var RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60];
|
|
55
|
+
function sleep(ms) {
|
|
56
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
57
|
+
}
|
|
58
|
+
var LogQueue = class {
|
|
59
|
+
sendFn;
|
|
60
|
+
items = [];
|
|
61
|
+
draining = false;
|
|
62
|
+
constructor(sendFn) {
|
|
63
|
+
this.sendFn = sendFn;
|
|
64
|
+
}
|
|
65
|
+
enqueue(payload) {
|
|
66
|
+
if (this.items.length >= MAX_QUEUE) return;
|
|
67
|
+
this.items.push(payload);
|
|
68
|
+
if (!this.draining) {
|
|
69
|
+
setImmediate(() => void this.drain());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async drain() {
|
|
73
|
+
this.draining = true;
|
|
74
|
+
let retryIndex = 0;
|
|
75
|
+
while (this.items.length > 0) {
|
|
76
|
+
let success = false;
|
|
77
|
+
try {
|
|
78
|
+
success = await this.sendFn(this.items[0]);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
if (success) {
|
|
82
|
+
this.items.shift();
|
|
83
|
+
retryIndex = 0;
|
|
84
|
+
} else {
|
|
85
|
+
const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)];
|
|
86
|
+
retryIndex++;
|
|
87
|
+
await sleep(delaySec * 1e3);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
this.draining = false;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// src/scrubber.ts
|
|
95
|
+
var RULES = [
|
|
96
|
+
["email", /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, "EMAIL_REDACTED"],
|
|
97
|
+
["ip", /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "IP_REDACTED"],
|
|
98
|
+
["iban", /\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/g, "IBAN_REDACTED"],
|
|
99
|
+
["phone", /\b(?:\+?\d[\d\s\-(). ]{5,14}\d)\b/g, "PHONE_REDACTED"],
|
|
100
|
+
["dob", /\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})\b/g, "DOB_REDACTED"],
|
|
101
|
+
["id_number", /\b[A-Z]{1,3}\d{6,9}\b/g, "ID_REDACTED"],
|
|
102
|
+
["name", /\b(?:Mr|Mrs|Ms|Dr|Prof)\.?\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\b/g, "NAME_REDACTED"]
|
|
103
|
+
];
|
|
104
|
+
function scrub(text) {
|
|
105
|
+
if (!text) return { text, detections: [] };
|
|
106
|
+
const detections = [];
|
|
107
|
+
const counters = {};
|
|
108
|
+
let result = text;
|
|
109
|
+
for (const [category, pattern, basePlaceholder] of RULES) {
|
|
110
|
+
pattern.lastIndex = 0;
|
|
111
|
+
result = result.replace(pattern, () => {
|
|
112
|
+
counters[category] = (counters[category] ?? 0) + 1;
|
|
113
|
+
const placeholder = `[${basePlaceholder}_${counters[category]}]`;
|
|
114
|
+
detections.push({ category, placeholder });
|
|
115
|
+
return placeholder;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return { text: result, detections };
|
|
119
|
+
}
|
|
120
|
+
function scrubMessages(messages) {
|
|
121
|
+
const allDetections = [];
|
|
122
|
+
const scrubbed = [];
|
|
123
|
+
for (const msg of messages) {
|
|
124
|
+
const content = msg.content;
|
|
125
|
+
if (typeof content === "string") {
|
|
126
|
+
const result = scrub(content);
|
|
127
|
+
scrubbed.push({ ...msg, content: result.text });
|
|
128
|
+
allDetections.push(...result.detections);
|
|
129
|
+
} else if (Array.isArray(content)) {
|
|
130
|
+
const newBlocks = content.map((block) => {
|
|
131
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
132
|
+
const result = scrub(block.text);
|
|
133
|
+
allDetections.push(...result.detections);
|
|
134
|
+
return { ...block, text: result.text };
|
|
135
|
+
}
|
|
136
|
+
return block;
|
|
137
|
+
});
|
|
138
|
+
scrubbed.push({ ...msg, content: newBlocks });
|
|
139
|
+
} else {
|
|
140
|
+
scrubbed.push(msg);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return [scrubbed, allDetections];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/patcher.ts
|
|
147
|
+
var PATCHED = /* @__PURE__ */ Symbol.for("sovergate.patched");
|
|
148
|
+
function utcnow() {
|
|
149
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
150
|
+
}
|
|
151
|
+
function observe(result, onData) {
|
|
152
|
+
if (!result || typeof result.then !== "function") return result;
|
|
153
|
+
let seen = false;
|
|
154
|
+
const notify = (data) => {
|
|
155
|
+
if (seen) return;
|
|
156
|
+
seen = true;
|
|
157
|
+
try {
|
|
158
|
+
onData(data);
|
|
159
|
+
} catch {
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
return new Proxy(result, {
|
|
164
|
+
get(target, prop) {
|
|
165
|
+
const value = Reflect.get(target, prop, target);
|
|
166
|
+
if (typeof value !== "function") return value;
|
|
167
|
+
if (prop === "then") {
|
|
168
|
+
return (onFulfilled, onRejected) => value.call(
|
|
169
|
+
target,
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
171
|
+
(data) => {
|
|
172
|
+
notify(data);
|
|
173
|
+
return typeof onFulfilled === "function" ? onFulfilled(data) : data;
|
|
174
|
+
},
|
|
175
|
+
onRejected
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
if (prop === "withResponse") {
|
|
179
|
+
return (...args) => (
|
|
180
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
181
|
+
value.apply(target, args).then((withResponse) => {
|
|
182
|
+
notify(withResponse?.data);
|
|
183
|
+
return withResponse;
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return value.bind(target);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
} catch {
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function isStream(opts) {
|
|
195
|
+
return opts?.stream === true;
|
|
196
|
+
}
|
|
197
|
+
function patchOpenAI(client, enqueue) {
|
|
198
|
+
const completions = client.chat.completions;
|
|
199
|
+
if (completions[PATCHED]) return;
|
|
200
|
+
const original = completions.create.bind(completions);
|
|
201
|
+
completions.create = (opts, ...rest) => {
|
|
202
|
+
const requestAt = utcnow();
|
|
203
|
+
const t0 = performance.now();
|
|
204
|
+
const result = original(opts, ...rest);
|
|
205
|
+
if (isStream(opts)) return result;
|
|
206
|
+
return observe(result, (response) => {
|
|
207
|
+
const latencyMs = Math.round(performance.now() - t0);
|
|
208
|
+
setImmediate(() => {
|
|
209
|
+
try {
|
|
210
|
+
const messages = opts?.messages ?? [];
|
|
211
|
+
const model = opts?.model ?? "";
|
|
212
|
+
const [scrubbedMessages, detections] = scrubMessages(messages);
|
|
213
|
+
const choice = response?.choices?.[0];
|
|
214
|
+
const responseText = choice?.message?.content ?? "";
|
|
215
|
+
const scrubbedResponse = responseText ? scrub(responseText).text : "";
|
|
216
|
+
const usage = response?.usage;
|
|
217
|
+
enqueue({
|
|
218
|
+
model: response?.model ?? model,
|
|
219
|
+
messages: scrubbedMessages,
|
|
220
|
+
response: scrubbedResponse,
|
|
221
|
+
prompt_tokens: usage?.prompt_tokens ?? 0,
|
|
222
|
+
completion_tokens: usage?.completion_tokens ?? 0,
|
|
223
|
+
latency_ms: latencyMs,
|
|
224
|
+
timestamp: requestAt,
|
|
225
|
+
finish_reason: choice?.finish_reason ?? null,
|
|
226
|
+
pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
|
|
227
|
+
});
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
};
|
|
233
|
+
completions[PATCHED] = true;
|
|
234
|
+
}
|
|
235
|
+
function patchAnthropic(client, enqueue) {
|
|
236
|
+
const messagesApi = client.messages;
|
|
237
|
+
if (messagesApi[PATCHED]) return;
|
|
238
|
+
const original = messagesApi.create.bind(messagesApi);
|
|
239
|
+
messagesApi.create = (opts, ...rest) => {
|
|
240
|
+
const requestAt = utcnow();
|
|
241
|
+
const t0 = performance.now();
|
|
242
|
+
const result = original(opts, ...rest);
|
|
243
|
+
if (isStream(opts)) return result;
|
|
244
|
+
return observe(result, (response) => {
|
|
245
|
+
const latencyMs = Math.round(performance.now() - t0);
|
|
246
|
+
setImmediate(() => {
|
|
247
|
+
try {
|
|
248
|
+
const messages = opts?.messages ?? [];
|
|
249
|
+
const system = opts?.system;
|
|
250
|
+
const model = opts?.model ?? "";
|
|
251
|
+
const allMessages = system ? [{ role: "system", content: system }, ...messages] : messages;
|
|
252
|
+
const [scrubbedMessages, detections] = scrubMessages(allMessages);
|
|
253
|
+
const responseText = (response?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
254
|
+
const scrubbedResponse = responseText ? scrub(responseText).text : "";
|
|
255
|
+
const usage = response?.usage;
|
|
256
|
+
enqueue({
|
|
257
|
+
model: response?.model ?? model,
|
|
258
|
+
messages: scrubbedMessages,
|
|
259
|
+
response: scrubbedResponse,
|
|
260
|
+
prompt_tokens: usage?.input_tokens ?? 0,
|
|
261
|
+
completion_tokens: usage?.output_tokens ?? 0,
|
|
262
|
+
latency_ms: latencyMs,
|
|
263
|
+
timestamp: requestAt,
|
|
264
|
+
finish_reason: response?.stop_reason ?? null,
|
|
265
|
+
pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
|
|
266
|
+
});
|
|
267
|
+
} catch {
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
messagesApi[PATCHED] = true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/client.ts
|
|
276
|
+
var state = {
|
|
277
|
+
apiKey: null,
|
|
278
|
+
baseUrl: "https://api.sovergate.com",
|
|
279
|
+
queue: null
|
|
280
|
+
};
|
|
281
|
+
function init({ apiKey, baseUrl = "https://api.sovergate.com", debug = false }) {
|
|
282
|
+
try {
|
|
283
|
+
state.apiKey = apiKey;
|
|
284
|
+
state.baseUrl = baseUrl.replace(/\/$/, "");
|
|
285
|
+
if (debug) {
|
|
286
|
+
console.debug("[sovergate] init baseUrl=%s", state.baseUrl);
|
|
287
|
+
}
|
|
288
|
+
state.queue = new LogQueue((payload) => send(apiKey, payload, state.baseUrl));
|
|
289
|
+
} catch (err) {
|
|
290
|
+
state.queue = null;
|
|
291
|
+
warn("init failed, logging disabled", err);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function instrument(client) {
|
|
295
|
+
if (!state.queue) {
|
|
296
|
+
warn("instrument() called before init({ apiKey }) \u2014 LLM calls will not be logged");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const enqueue = state.queue.enqueue.bind(state.queue);
|
|
301
|
+
if (client?.chat?.completions) {
|
|
302
|
+
patchOpenAI(client, enqueue);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (client?.messages) {
|
|
306
|
+
patchAnthropic(client, enqueue);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
warn(
|
|
310
|
+
"unsupported client \u2014 LLM calls will not be logged. Pass an OpenAI or Anthropic client instance, or open an issue at https://github.com/sovergate/sdk-js/issues"
|
|
311
|
+
);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
warn("instrument() failed, LLM calls will not be logged", err);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function warn(message, err) {
|
|
317
|
+
try {
|
|
318
|
+
console.warn(`[sovergate] ${message}`, ...err === void 0 ? [] : [err]);
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function shutdown() {
|
|
323
|
+
state.queue = null;
|
|
324
|
+
}
|
|
325
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
326
|
+
0 && (module.exports = {
|
|
327
|
+
init,
|
|
328
|
+
instrument,
|
|
329
|
+
shutdown
|
|
330
|
+
});
|
|
331
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/sender.ts","../src/queue.ts","../src/scrubber.ts","../src/patcher.ts","../src/client.ts"],"sourcesContent":["export { init, instrument, shutdown } from './client'\nexport type { InitOptions } from './client'\nexport type { Detection, ScrubResult } from './scrubber'\n","const BASE_URL = 'https://api.sovergate.com'\nconst TIMEOUT_MS = 2000\n\nexport async function send(\n apiKey: string,\n payload: Record<string, unknown>,\n baseUrl = BASE_URL,\n): Promise<boolean> {\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)\n\n const response = await fetch(`${baseUrl}/ingest`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n return response.status < 400\n } catch {\n return false\n }\n}\n","type Payload = Record<string, unknown>\ntype SendFn = (payload: Payload) => Promise<boolean>\n\nconst MAX_QUEUE = 10_000\nconst RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60]\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport class LogQueue {\n private readonly sendFn: SendFn\n private readonly items: Payload[] = []\n private draining = false\n\n constructor(sendFn: SendFn) {\n this.sendFn = sendFn\n }\n\n enqueue(payload: Payload): void {\n if (this.items.length >= MAX_QUEUE) return\n this.items.push(payload)\n if (!this.draining) {\n setImmediate(() => void this.drain())\n }\n }\n\n private async drain(): Promise<void> {\n this.draining = true\n let retryIndex = 0\n\n while (this.items.length > 0) {\n let success = false\n try {\n success = await this.sendFn(this.items[0])\n } catch {\n // never propagate\n }\n\n if (success) {\n this.items.shift()\n retryIndex = 0\n } else {\n const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)]\n retryIndex++\n await sleep(delaySec * 1000)\n }\n }\n\n this.draining = false\n }\n}\n","export interface Detection {\n category: string\n placeholder: string\n}\n\nexport interface ScrubResult {\n text: string\n detections: Detection[]\n}\n\ntype ContentBlock = { type: string; text?: string } & Record<string, unknown>\nexport type OpenAIMessage = { role: string; content: string | ContentBlock[] } & Record<string, unknown>\n\n// Each rule: [category, regex (must have g flag), placeholder base]\n// Order matters — IP before phone to avoid dotted-quad false matches.\nconst RULES: Array<[string, RegExp, string]> = [\n ['email', /\\b[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}\\b/g, 'EMAIL_REDACTED'],\n ['ip', /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, 'IP_REDACTED'],\n ['iban', /\\b[A-Z]{2}\\d{2}[A-Z0-9]{4,30}\\b/g, 'IBAN_REDACTED'],\n ['phone', /\\b(?:\\+?\\d[\\d\\s\\-(). ]{5,14}\\d)\\b/g, 'PHONE_REDACTED'],\n ['dob', /\\b(?:\\d{1,2}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{2,4}|\\d{4}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{1,2})\\b/g, 'DOB_REDACTED'],\n ['id_number', /\\b[A-Z]{1,3}\\d{6,9}\\b/g, 'ID_REDACTED'],\n ['name', /\\b(?:Mr|Mrs|Ms|Dr|Prof)\\.?\\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\\b/g, 'NAME_REDACTED'],\n]\n\nexport function scrub(text: string): ScrubResult {\n if (!text) return { text, detections: [] }\n\n const detections: Detection[] = []\n const counters: Record<string, number> = {}\n\n let result = text\n for (const [category, pattern, basePlaceholder] of RULES) {\n pattern.lastIndex = 0\n result = result.replace(pattern, () => {\n counters[category] = (counters[category] ?? 0) + 1\n const placeholder = `[${basePlaceholder}_${counters[category]}]`\n detections.push({ category, placeholder })\n return placeholder\n })\n }\n\n return { text: result, detections }\n}\n\nexport function scrubMessages(messages: OpenAIMessage[]): [OpenAIMessage[], Detection[]] {\n const allDetections: Detection[] = []\n const scrubbed: OpenAIMessage[] = []\n\n for (const msg of messages) {\n const content = msg.content\n if (typeof content === 'string') {\n const result = scrub(content)\n scrubbed.push({ ...msg, content: result.text })\n allDetections.push(...result.detections)\n } else if (Array.isArray(content)) {\n const newBlocks = content.map(block => {\n if (block.type === 'text' && typeof block.text === 'string') {\n const result = scrub(block.text)\n allDetections.push(...result.detections)\n return { ...block, text: result.text }\n }\n return block\n })\n scrubbed.push({ ...msg, content: newBlocks })\n } else {\n scrubbed.push(msg)\n }\n }\n\n return [scrubbed, allDetections]\n}\n","import { scrub, scrubMessages } from './scrubber'\n\ntype Enqueue = (payload: Record<string, unknown>) => void\n\nconst PATCHED = Symbol.for('sovergate.patched')\n\nfunction utcnow(): string {\n return new Date().toISOString()\n}\n\n/**\n * Returns the provider's own result unchanged in behaviour, but notifies `onData`\n * the first time the app reads the parsed result (await / .then / .withResponse).\n *\n * The OpenAI and Anthropic SDKs return an APIPromise with extra methods\n * (.withResponse(), .asResponse()) and parse the body lazily. Wrapping create()\n * in an async function would drop those methods, and reading the result eagerly\n * would consume the body before an app calling .asResponse() can. A Proxy keeps\n * the original object, its prototype and its laziness intact.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction observe(result: any, onData: (data: any) => void): any {\n if (!result || typeof result.then !== 'function') return result\n\n let seen = false\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const notify = (data: any): void => {\n if (seen) return\n seen = true\n try {\n onData(data)\n } catch {\n // never let logging errors surface to the caller\n }\n }\n\n try {\n return new Proxy(result, {\n get(target, prop) {\n const value = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return value\n\n if (prop === 'then') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (onFulfilled?: any, onRejected?: any) =>\n value.call(\n target,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (data: any) => {\n notify(data)\n return typeof onFulfilled === 'function' ? onFulfilled(data) : data\n },\n onRejected,\n )\n }\n\n if (prop === 'withResponse') {\n return (...args: unknown[]) =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value.apply(target, args).then((withResponse: any) => {\n notify(withResponse?.data)\n return withResponse\n })\n }\n\n return value.bind(target)\n },\n })\n } catch {\n return result\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction isStream(opts: any): boolean {\n return opts?.stream === true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchOpenAI(client: any, enqueue: Enqueue): void {\n const completions = client.chat.completions\n if (completions[PATCHED]) return\n const original = completions.create.bind(completions)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n completions.create = (opts: any, ...rest: unknown[]) => {\n const requestAt = utcnow()\n const t0 = performance.now()\n\n const result = original(opts, ...rest)\n\n // Streams can only be read once and belong to the app; they are not logged yet.\n if (isStream(opts)) return result\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return observe(result, (response: any) => {\n const latencyMs = Math.round(performance.now() - t0)\n\n setImmediate(() => {\n try {\n const messages = opts?.messages ?? []\n const model = opts?.model ?? ''\n const [scrubbedMessages, detections] = scrubMessages(messages)\n const choice = response?.choices?.[0]\n const responseText: string = choice?.message?.content ?? ''\n const scrubbedResponse = responseText ? scrub(responseText).text : ''\n const usage = response?.usage\n\n enqueue({\n model: response?.model ?? model,\n messages: scrubbedMessages,\n response: scrubbedResponse,\n prompt_tokens: usage?.prompt_tokens ?? 0,\n completion_tokens: usage?.completion_tokens ?? 0,\n latency_ms: latencyMs,\n timestamp: requestAt,\n finish_reason: choice?.finish_reason ?? null,\n pii_detections: detections.map(d => ({ category: d.category, placeholder: d.placeholder })),\n })\n } catch {\n // never let logging errors surface to the caller\n }\n })\n })\n }\n completions[PATCHED] = true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchAnthropic(client: any, enqueue: Enqueue): void {\n const messagesApi = client.messages\n if (messagesApi[PATCHED]) return\n const original = messagesApi.create.bind(messagesApi)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messagesApi.create = (opts: any, ...rest: unknown[]) => {\n const requestAt = utcnow()\n const t0 = performance.now()\n\n const result = original(opts, ...rest)\n\n // Streams can only be read once and belong to the app; they are not logged yet.\n if (isStream(opts)) return result\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return observe(result, (response: any) => {\n const latencyMs = Math.round(performance.now() - t0)\n\n setImmediate(() => {\n try {\n const messages = opts?.messages ?? []\n const system = opts?.system\n const model = opts?.model ?? ''\n\n const allMessages = system\n ? [{ role: 'system', content: system }, ...messages]\n : messages\n\n const [scrubbedMessages, detections] = scrubMessages(allMessages)\n\n const responseText: string = (response?.content ?? [])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .filter((b: any) => b.type === 'text')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .map((b: any) => b.text as string)\n .join('')\n\n const scrubbedResponse = responseText ? scrub(responseText).text : ''\n const usage = response?.usage\n\n enqueue({\n model: response?.model ?? model,\n messages: scrubbedMessages,\n response: scrubbedResponse,\n prompt_tokens: usage?.input_tokens ?? 0,\n completion_tokens: usage?.output_tokens ?? 0,\n latency_ms: latencyMs,\n timestamp: requestAt,\n finish_reason: response?.stop_reason ?? null,\n pii_detections: detections.map(d => ({ category: d.category, placeholder: d.placeholder })),\n })\n } catch {\n // never let logging errors surface to the caller\n }\n })\n })\n }\n messagesApi[PATCHED] = true\n}\n","import { send } from './sender'\nimport { LogQueue } from './queue'\nimport { patchOpenAI, patchAnthropic } from './patcher'\n\nexport interface InitOptions {\n apiKey: string\n baseUrl?: string\n debug?: boolean\n}\n\nconst state: {\n apiKey: string | null\n baseUrl: string\n queue: LogQueue | null\n} = {\n apiKey: null,\n baseUrl: 'https://api.sovergate.com',\n queue: null,\n}\n\nexport function init({ apiKey, baseUrl = 'https://api.sovergate.com', debug = false }: InitOptions): void {\n try {\n state.apiKey = apiKey\n state.baseUrl = baseUrl.replace(/\\/$/, '')\n\n if (debug) {\n console.debug('[sovergate] init baseUrl=%s', state.baseUrl)\n }\n\n state.queue = new LogQueue(payload => send(apiKey, payload, state.baseUrl))\n } catch (err) {\n // Never let SDK setup break the host app; logging just stays disabled.\n state.queue = null\n warn('init failed, logging disabled', err)\n }\n}\n\n// Never throws: if instrumentation isn't possible, warn and leave the client untouched.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrument(client: any): void {\n if (!state.queue) {\n warn('instrument() called before init({ apiKey }) — LLM calls will not be logged')\n return\n }\n\n try {\n const enqueue = state.queue.enqueue.bind(state.queue)\n\n if (client?.chat?.completions) {\n patchOpenAI(client, enqueue)\n return\n }\n\n if (client?.messages) {\n patchAnthropic(client, enqueue)\n return\n }\n\n warn(\n 'unsupported client — LLM calls will not be logged. Pass an OpenAI or Anthropic ' +\n 'client instance, or open an issue at https://github.com/sovergate/sdk-js/issues',\n )\n } catch (err) {\n warn('instrument() failed, LLM calls will not be logged', err)\n }\n}\n\nfunction warn(message: string, err?: unknown): void {\n try {\n console.warn(`[sovergate] ${message}`, ...(err === undefined ? [] : [err]))\n } catch {\n // never propagate\n }\n}\n\nexport function shutdown(): void {\n state.queue = null\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// src/sender.ts
|
|
2
|
+
var BASE_URL = "https://api.sovergate.com";
|
|
3
|
+
var TIMEOUT_MS = 2e3;
|
|
4
|
+
async function send(apiKey, payload, baseUrl = BASE_URL) {
|
|
5
|
+
try {
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
8
|
+
const response = await fetch(`${baseUrl}/ingest`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${apiKey}`,
|
|
12
|
+
"Content-Type": "application/json"
|
|
13
|
+
},
|
|
14
|
+
body: JSON.stringify(payload),
|
|
15
|
+
signal: controller.signal
|
|
16
|
+
});
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
return response.status < 400;
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/queue.ts
|
|
25
|
+
var MAX_QUEUE = 1e4;
|
|
26
|
+
var RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60];
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
var LogQueue = class {
|
|
31
|
+
sendFn;
|
|
32
|
+
items = [];
|
|
33
|
+
draining = false;
|
|
34
|
+
constructor(sendFn) {
|
|
35
|
+
this.sendFn = sendFn;
|
|
36
|
+
}
|
|
37
|
+
enqueue(payload) {
|
|
38
|
+
if (this.items.length >= MAX_QUEUE) return;
|
|
39
|
+
this.items.push(payload);
|
|
40
|
+
if (!this.draining) {
|
|
41
|
+
setImmediate(() => void this.drain());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async drain() {
|
|
45
|
+
this.draining = true;
|
|
46
|
+
let retryIndex = 0;
|
|
47
|
+
while (this.items.length > 0) {
|
|
48
|
+
let success = false;
|
|
49
|
+
try {
|
|
50
|
+
success = await this.sendFn(this.items[0]);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
if (success) {
|
|
54
|
+
this.items.shift();
|
|
55
|
+
retryIndex = 0;
|
|
56
|
+
} else {
|
|
57
|
+
const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)];
|
|
58
|
+
retryIndex++;
|
|
59
|
+
await sleep(delaySec * 1e3);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
this.draining = false;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/scrubber.ts
|
|
67
|
+
var RULES = [
|
|
68
|
+
["email", /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, "EMAIL_REDACTED"],
|
|
69
|
+
["ip", /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "IP_REDACTED"],
|
|
70
|
+
["iban", /\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/g, "IBAN_REDACTED"],
|
|
71
|
+
["phone", /\b(?:\+?\d[\d\s\-(). ]{5,14}\d)\b/g, "PHONE_REDACTED"],
|
|
72
|
+
["dob", /\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})\b/g, "DOB_REDACTED"],
|
|
73
|
+
["id_number", /\b[A-Z]{1,3}\d{6,9}\b/g, "ID_REDACTED"],
|
|
74
|
+
["name", /\b(?:Mr|Mrs|Ms|Dr|Prof)\.?\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\b/g, "NAME_REDACTED"]
|
|
75
|
+
];
|
|
76
|
+
function scrub(text) {
|
|
77
|
+
if (!text) return { text, detections: [] };
|
|
78
|
+
const detections = [];
|
|
79
|
+
const counters = {};
|
|
80
|
+
let result = text;
|
|
81
|
+
for (const [category, pattern, basePlaceholder] of RULES) {
|
|
82
|
+
pattern.lastIndex = 0;
|
|
83
|
+
result = result.replace(pattern, () => {
|
|
84
|
+
counters[category] = (counters[category] ?? 0) + 1;
|
|
85
|
+
const placeholder = `[${basePlaceholder}_${counters[category]}]`;
|
|
86
|
+
detections.push({ category, placeholder });
|
|
87
|
+
return placeholder;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return { text: result, detections };
|
|
91
|
+
}
|
|
92
|
+
function scrubMessages(messages) {
|
|
93
|
+
const allDetections = [];
|
|
94
|
+
const scrubbed = [];
|
|
95
|
+
for (const msg of messages) {
|
|
96
|
+
const content = msg.content;
|
|
97
|
+
if (typeof content === "string") {
|
|
98
|
+
const result = scrub(content);
|
|
99
|
+
scrubbed.push({ ...msg, content: result.text });
|
|
100
|
+
allDetections.push(...result.detections);
|
|
101
|
+
} else if (Array.isArray(content)) {
|
|
102
|
+
const newBlocks = content.map((block) => {
|
|
103
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
104
|
+
const result = scrub(block.text);
|
|
105
|
+
allDetections.push(...result.detections);
|
|
106
|
+
return { ...block, text: result.text };
|
|
107
|
+
}
|
|
108
|
+
return block;
|
|
109
|
+
});
|
|
110
|
+
scrubbed.push({ ...msg, content: newBlocks });
|
|
111
|
+
} else {
|
|
112
|
+
scrubbed.push(msg);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return [scrubbed, allDetections];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/patcher.ts
|
|
119
|
+
var PATCHED = /* @__PURE__ */ Symbol.for("sovergate.patched");
|
|
120
|
+
function utcnow() {
|
|
121
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
122
|
+
}
|
|
123
|
+
function observe(result, onData) {
|
|
124
|
+
if (!result || typeof result.then !== "function") return result;
|
|
125
|
+
let seen = false;
|
|
126
|
+
const notify = (data) => {
|
|
127
|
+
if (seen) return;
|
|
128
|
+
seen = true;
|
|
129
|
+
try {
|
|
130
|
+
onData(data);
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
return new Proxy(result, {
|
|
136
|
+
get(target, prop) {
|
|
137
|
+
const value = Reflect.get(target, prop, target);
|
|
138
|
+
if (typeof value !== "function") return value;
|
|
139
|
+
if (prop === "then") {
|
|
140
|
+
return (onFulfilled, onRejected) => value.call(
|
|
141
|
+
target,
|
|
142
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
143
|
+
(data) => {
|
|
144
|
+
notify(data);
|
|
145
|
+
return typeof onFulfilled === "function" ? onFulfilled(data) : data;
|
|
146
|
+
},
|
|
147
|
+
onRejected
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (prop === "withResponse") {
|
|
151
|
+
return (...args) => (
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
value.apply(target, args).then((withResponse) => {
|
|
154
|
+
notify(withResponse?.data);
|
|
155
|
+
return withResponse;
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return value.bind(target);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
} catch {
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function isStream(opts) {
|
|
167
|
+
return opts?.stream === true;
|
|
168
|
+
}
|
|
169
|
+
function patchOpenAI(client, enqueue) {
|
|
170
|
+
const completions = client.chat.completions;
|
|
171
|
+
if (completions[PATCHED]) return;
|
|
172
|
+
const original = completions.create.bind(completions);
|
|
173
|
+
completions.create = (opts, ...rest) => {
|
|
174
|
+
const requestAt = utcnow();
|
|
175
|
+
const t0 = performance.now();
|
|
176
|
+
const result = original(opts, ...rest);
|
|
177
|
+
if (isStream(opts)) return result;
|
|
178
|
+
return observe(result, (response) => {
|
|
179
|
+
const latencyMs = Math.round(performance.now() - t0);
|
|
180
|
+
setImmediate(() => {
|
|
181
|
+
try {
|
|
182
|
+
const messages = opts?.messages ?? [];
|
|
183
|
+
const model = opts?.model ?? "";
|
|
184
|
+
const [scrubbedMessages, detections] = scrubMessages(messages);
|
|
185
|
+
const choice = response?.choices?.[0];
|
|
186
|
+
const responseText = choice?.message?.content ?? "";
|
|
187
|
+
const scrubbedResponse = responseText ? scrub(responseText).text : "";
|
|
188
|
+
const usage = response?.usage;
|
|
189
|
+
enqueue({
|
|
190
|
+
model: response?.model ?? model,
|
|
191
|
+
messages: scrubbedMessages,
|
|
192
|
+
response: scrubbedResponse,
|
|
193
|
+
prompt_tokens: usage?.prompt_tokens ?? 0,
|
|
194
|
+
completion_tokens: usage?.completion_tokens ?? 0,
|
|
195
|
+
latency_ms: latencyMs,
|
|
196
|
+
timestamp: requestAt,
|
|
197
|
+
finish_reason: choice?.finish_reason ?? null,
|
|
198
|
+
pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
|
|
199
|
+
});
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
};
|
|
205
|
+
completions[PATCHED] = true;
|
|
206
|
+
}
|
|
207
|
+
function patchAnthropic(client, enqueue) {
|
|
208
|
+
const messagesApi = client.messages;
|
|
209
|
+
if (messagesApi[PATCHED]) return;
|
|
210
|
+
const original = messagesApi.create.bind(messagesApi);
|
|
211
|
+
messagesApi.create = (opts, ...rest) => {
|
|
212
|
+
const requestAt = utcnow();
|
|
213
|
+
const t0 = performance.now();
|
|
214
|
+
const result = original(opts, ...rest);
|
|
215
|
+
if (isStream(opts)) return result;
|
|
216
|
+
return observe(result, (response) => {
|
|
217
|
+
const latencyMs = Math.round(performance.now() - t0);
|
|
218
|
+
setImmediate(() => {
|
|
219
|
+
try {
|
|
220
|
+
const messages = opts?.messages ?? [];
|
|
221
|
+
const system = opts?.system;
|
|
222
|
+
const model = opts?.model ?? "";
|
|
223
|
+
const allMessages = system ? [{ role: "system", content: system }, ...messages] : messages;
|
|
224
|
+
const [scrubbedMessages, detections] = scrubMessages(allMessages);
|
|
225
|
+
const responseText = (response?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
226
|
+
const scrubbedResponse = responseText ? scrub(responseText).text : "";
|
|
227
|
+
const usage = response?.usage;
|
|
228
|
+
enqueue({
|
|
229
|
+
model: response?.model ?? model,
|
|
230
|
+
messages: scrubbedMessages,
|
|
231
|
+
response: scrubbedResponse,
|
|
232
|
+
prompt_tokens: usage?.input_tokens ?? 0,
|
|
233
|
+
completion_tokens: usage?.output_tokens ?? 0,
|
|
234
|
+
latency_ms: latencyMs,
|
|
235
|
+
timestamp: requestAt,
|
|
236
|
+
finish_reason: response?.stop_reason ?? null,
|
|
237
|
+
pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
|
|
238
|
+
});
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
};
|
|
244
|
+
messagesApi[PATCHED] = true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/client.ts
|
|
248
|
+
var state = {
|
|
249
|
+
apiKey: null,
|
|
250
|
+
baseUrl: "https://api.sovergate.com",
|
|
251
|
+
queue: null
|
|
252
|
+
};
|
|
253
|
+
function init({ apiKey, baseUrl = "https://api.sovergate.com", debug = false }) {
|
|
254
|
+
try {
|
|
255
|
+
state.apiKey = apiKey;
|
|
256
|
+
state.baseUrl = baseUrl.replace(/\/$/, "");
|
|
257
|
+
if (debug) {
|
|
258
|
+
console.debug("[sovergate] init baseUrl=%s", state.baseUrl);
|
|
259
|
+
}
|
|
260
|
+
state.queue = new LogQueue((payload) => send(apiKey, payload, state.baseUrl));
|
|
261
|
+
} catch (err) {
|
|
262
|
+
state.queue = null;
|
|
263
|
+
warn("init failed, logging disabled", err);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function instrument(client) {
|
|
267
|
+
if (!state.queue) {
|
|
268
|
+
warn("instrument() called before init({ apiKey }) \u2014 LLM calls will not be logged");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
const enqueue = state.queue.enqueue.bind(state.queue);
|
|
273
|
+
if (client?.chat?.completions) {
|
|
274
|
+
patchOpenAI(client, enqueue);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (client?.messages) {
|
|
278
|
+
patchAnthropic(client, enqueue);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
warn(
|
|
282
|
+
"unsupported client \u2014 LLM calls will not be logged. Pass an OpenAI or Anthropic client instance, or open an issue at https://github.com/sovergate/sdk-js/issues"
|
|
283
|
+
);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
warn("instrument() failed, LLM calls will not be logged", err);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function warn(message, err) {
|
|
289
|
+
try {
|
|
290
|
+
console.warn(`[sovergate] ${message}`, ...err === void 0 ? [] : [err]);
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function shutdown() {
|
|
295
|
+
state.queue = null;
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
init,
|
|
299
|
+
instrument,
|
|
300
|
+
shutdown
|
|
301
|
+
};
|
|
302
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sender.ts","../src/queue.ts","../src/scrubber.ts","../src/patcher.ts","../src/client.ts"],"sourcesContent":["const BASE_URL = 'https://api.sovergate.com'\nconst TIMEOUT_MS = 2000\n\nexport async function send(\n apiKey: string,\n payload: Record<string, unknown>,\n baseUrl = BASE_URL,\n): Promise<boolean> {\n try {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)\n\n const response = await fetch(`${baseUrl}/ingest`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(payload),\n signal: controller.signal,\n })\n\n clearTimeout(timer)\n return response.status < 400\n } catch {\n return false\n }\n}\n","type Payload = Record<string, unknown>\ntype SendFn = (payload: Payload) => Promise<boolean>\n\nconst MAX_QUEUE = 10_000\nconst RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60]\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\nexport class LogQueue {\n private readonly sendFn: SendFn\n private readonly items: Payload[] = []\n private draining = false\n\n constructor(sendFn: SendFn) {\n this.sendFn = sendFn\n }\n\n enqueue(payload: Payload): void {\n if (this.items.length >= MAX_QUEUE) return\n this.items.push(payload)\n if (!this.draining) {\n setImmediate(() => void this.drain())\n }\n }\n\n private async drain(): Promise<void> {\n this.draining = true\n let retryIndex = 0\n\n while (this.items.length > 0) {\n let success = false\n try {\n success = await this.sendFn(this.items[0])\n } catch {\n // never propagate\n }\n\n if (success) {\n this.items.shift()\n retryIndex = 0\n } else {\n const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)]\n retryIndex++\n await sleep(delaySec * 1000)\n }\n }\n\n this.draining = false\n }\n}\n","export interface Detection {\n category: string\n placeholder: string\n}\n\nexport interface ScrubResult {\n text: string\n detections: Detection[]\n}\n\ntype ContentBlock = { type: string; text?: string } & Record<string, unknown>\nexport type OpenAIMessage = { role: string; content: string | ContentBlock[] } & Record<string, unknown>\n\n// Each rule: [category, regex (must have g flag), placeholder base]\n// Order matters — IP before phone to avoid dotted-quad false matches.\nconst RULES: Array<[string, RegExp, string]> = [\n ['email', /\\b[A-Za-z0-9._%+\\-]+@[A-Za-z0-9.\\-]+\\.[A-Za-z]{2,}\\b/g, 'EMAIL_REDACTED'],\n ['ip', /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, 'IP_REDACTED'],\n ['iban', /\\b[A-Z]{2}\\d{2}[A-Z0-9]{4,30}\\b/g, 'IBAN_REDACTED'],\n ['phone', /\\b(?:\\+?\\d[\\d\\s\\-(). ]{5,14}\\d)\\b/g, 'PHONE_REDACTED'],\n ['dob', /\\b(?:\\d{1,2}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{2,4}|\\d{4}[\\/\\-.]\\d{1,2}[\\/\\-.]\\d{1,2})\\b/g, 'DOB_REDACTED'],\n ['id_number', /\\b[A-Z]{1,3}\\d{6,9}\\b/g, 'ID_REDACTED'],\n ['name', /\\b(?:Mr|Mrs|Ms|Dr|Prof)\\.?\\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\\b/g, 'NAME_REDACTED'],\n]\n\nexport function scrub(text: string): ScrubResult {\n if (!text) return { text, detections: [] }\n\n const detections: Detection[] = []\n const counters: Record<string, number> = {}\n\n let result = text\n for (const [category, pattern, basePlaceholder] of RULES) {\n pattern.lastIndex = 0\n result = result.replace(pattern, () => {\n counters[category] = (counters[category] ?? 0) + 1\n const placeholder = `[${basePlaceholder}_${counters[category]}]`\n detections.push({ category, placeholder })\n return placeholder\n })\n }\n\n return { text: result, detections }\n}\n\nexport function scrubMessages(messages: OpenAIMessage[]): [OpenAIMessage[], Detection[]] {\n const allDetections: Detection[] = []\n const scrubbed: OpenAIMessage[] = []\n\n for (const msg of messages) {\n const content = msg.content\n if (typeof content === 'string') {\n const result = scrub(content)\n scrubbed.push({ ...msg, content: result.text })\n allDetections.push(...result.detections)\n } else if (Array.isArray(content)) {\n const newBlocks = content.map(block => {\n if (block.type === 'text' && typeof block.text === 'string') {\n const result = scrub(block.text)\n allDetections.push(...result.detections)\n return { ...block, text: result.text }\n }\n return block\n })\n scrubbed.push({ ...msg, content: newBlocks })\n } else {\n scrubbed.push(msg)\n }\n }\n\n return [scrubbed, allDetections]\n}\n","import { scrub, scrubMessages } from './scrubber'\n\ntype Enqueue = (payload: Record<string, unknown>) => void\n\nconst PATCHED = Symbol.for('sovergate.patched')\n\nfunction utcnow(): string {\n return new Date().toISOString()\n}\n\n/**\n * Returns the provider's own result unchanged in behaviour, but notifies `onData`\n * the first time the app reads the parsed result (await / .then / .withResponse).\n *\n * The OpenAI and Anthropic SDKs return an APIPromise with extra methods\n * (.withResponse(), .asResponse()) and parse the body lazily. Wrapping create()\n * in an async function would drop those methods, and reading the result eagerly\n * would consume the body before an app calling .asResponse() can. A Proxy keeps\n * the original object, its prototype and its laziness intact.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction observe(result: any, onData: (data: any) => void): any {\n if (!result || typeof result.then !== 'function') return result\n\n let seen = false\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const notify = (data: any): void => {\n if (seen) return\n seen = true\n try {\n onData(data)\n } catch {\n // never let logging errors surface to the caller\n }\n }\n\n try {\n return new Proxy(result, {\n get(target, prop) {\n const value = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return value\n\n if (prop === 'then') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (onFulfilled?: any, onRejected?: any) =>\n value.call(\n target,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (data: any) => {\n notify(data)\n return typeof onFulfilled === 'function' ? onFulfilled(data) : data\n },\n onRejected,\n )\n }\n\n if (prop === 'withResponse') {\n return (...args: unknown[]) =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n value.apply(target, args).then((withResponse: any) => {\n notify(withResponse?.data)\n return withResponse\n })\n }\n\n return value.bind(target)\n },\n })\n } catch {\n return result\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction isStream(opts: any): boolean {\n return opts?.stream === true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchOpenAI(client: any, enqueue: Enqueue): void {\n const completions = client.chat.completions\n if (completions[PATCHED]) return\n const original = completions.create.bind(completions)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n completions.create = (opts: any, ...rest: unknown[]) => {\n const requestAt = utcnow()\n const t0 = performance.now()\n\n const result = original(opts, ...rest)\n\n // Streams can only be read once and belong to the app; they are not logged yet.\n if (isStream(opts)) return result\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return observe(result, (response: any) => {\n const latencyMs = Math.round(performance.now() - t0)\n\n setImmediate(() => {\n try {\n const messages = opts?.messages ?? []\n const model = opts?.model ?? ''\n const [scrubbedMessages, detections] = scrubMessages(messages)\n const choice = response?.choices?.[0]\n const responseText: string = choice?.message?.content ?? ''\n const scrubbedResponse = responseText ? scrub(responseText).text : ''\n const usage = response?.usage\n\n enqueue({\n model: response?.model ?? model,\n messages: scrubbedMessages,\n response: scrubbedResponse,\n prompt_tokens: usage?.prompt_tokens ?? 0,\n completion_tokens: usage?.completion_tokens ?? 0,\n latency_ms: latencyMs,\n timestamp: requestAt,\n finish_reason: choice?.finish_reason ?? null,\n pii_detections: detections.map(d => ({ category: d.category, placeholder: d.placeholder })),\n })\n } catch {\n // never let logging errors surface to the caller\n }\n })\n })\n }\n completions[PATCHED] = true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchAnthropic(client: any, enqueue: Enqueue): void {\n const messagesApi = client.messages\n if (messagesApi[PATCHED]) return\n const original = messagesApi.create.bind(messagesApi)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n messagesApi.create = (opts: any, ...rest: unknown[]) => {\n const requestAt = utcnow()\n const t0 = performance.now()\n\n const result = original(opts, ...rest)\n\n // Streams can only be read once and belong to the app; they are not logged yet.\n if (isStream(opts)) return result\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return observe(result, (response: any) => {\n const latencyMs = Math.round(performance.now() - t0)\n\n setImmediate(() => {\n try {\n const messages = opts?.messages ?? []\n const system = opts?.system\n const model = opts?.model ?? ''\n\n const allMessages = system\n ? [{ role: 'system', content: system }, ...messages]\n : messages\n\n const [scrubbedMessages, detections] = scrubMessages(allMessages)\n\n const responseText: string = (response?.content ?? [])\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .filter((b: any) => b.type === 'text')\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n .map((b: any) => b.text as string)\n .join('')\n\n const scrubbedResponse = responseText ? scrub(responseText).text : ''\n const usage = response?.usage\n\n enqueue({\n model: response?.model ?? model,\n messages: scrubbedMessages,\n response: scrubbedResponse,\n prompt_tokens: usage?.input_tokens ?? 0,\n completion_tokens: usage?.output_tokens ?? 0,\n latency_ms: latencyMs,\n timestamp: requestAt,\n finish_reason: response?.stop_reason ?? null,\n pii_detections: detections.map(d => ({ category: d.category, placeholder: d.placeholder })),\n })\n } catch {\n // never let logging errors surface to the caller\n }\n })\n })\n }\n messagesApi[PATCHED] = true\n}\n","import { send } from './sender'\nimport { LogQueue } from './queue'\nimport { patchOpenAI, patchAnthropic } from './patcher'\n\nexport interface InitOptions {\n apiKey: string\n baseUrl?: string\n debug?: boolean\n}\n\nconst state: {\n apiKey: string | null\n baseUrl: string\n queue: LogQueue | null\n} = {\n apiKey: null,\n baseUrl: 'https://api.sovergate.com',\n queue: null,\n}\n\nexport function init({ apiKey, baseUrl = 'https://api.sovergate.com', debug = false }: InitOptions): void {\n try {\n state.apiKey = apiKey\n state.baseUrl = baseUrl.replace(/\\/$/, '')\n\n if (debug) {\n console.debug('[sovergate] init baseUrl=%s', state.baseUrl)\n }\n\n state.queue = new LogQueue(payload => send(apiKey, payload, state.baseUrl))\n } catch (err) {\n // Never let SDK setup break the host app; logging just stays disabled.\n state.queue = null\n warn('init failed, logging disabled', err)\n }\n}\n\n// Never throws: if instrumentation isn't possible, warn and leave the client untouched.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function instrument(client: any): void {\n if (!state.queue) {\n warn('instrument() called before init({ apiKey }) — LLM calls will not be logged')\n return\n }\n\n try {\n const enqueue = state.queue.enqueue.bind(state.queue)\n\n if (client?.chat?.completions) {\n patchOpenAI(client, enqueue)\n return\n }\n\n if (client?.messages) {\n patchAnthropic(client, enqueue)\n return\n }\n\n warn(\n 'unsupported client — LLM calls will not be logged. Pass an OpenAI or Anthropic ' +\n 'client instance, or open an issue at https://github.com/sovergate/sdk-js/issues',\n )\n } catch (err) {\n warn('instrument() failed, LLM calls will not be logged', err)\n }\n}\n\nfunction warn(message: string, err?: unknown): void {\n try {\n console.warn(`[sovergate] ${message}`, ...(err === undefined ? [] : [err]))\n } catch {\n // never propagate\n }\n}\n\nexport function shutdown(): void {\n state.queue = null\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,60 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sovergate",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "EU AI Act compliance SDK — automatic, non-invasive audit logging for LLM calls",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "jest --testEnvironment node",
|
|
29
|
+
"test:watch": "jest --watch",
|
|
30
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"eu-ai-act",
|
|
34
|
+
"compliance",
|
|
35
|
+
"gdpr",
|
|
36
|
+
"llm",
|
|
37
|
+
"audit"
|
|
38
|
+
],
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@anthropic-ai/sdk": "^1.3.0",
|
|
45
|
+
"@types/jest": "^29.5.0",
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"jest": "^29.5.0",
|
|
48
|
+
"openai": "^7.43.0",
|
|
49
|
+
"ts-jest": "^29.1.0",
|
|
50
|
+
"tsup": "^8.5.1",
|
|
51
|
+
"typescript": "^5.0.0"
|
|
52
|
+
},
|
|
53
|
+
"jest": {
|
|
54
|
+
"preset": "ts-jest",
|
|
55
|
+
"testEnvironment": "node",
|
|
56
|
+
"testMatch": [
|
|
57
|
+
"**/tests/**/*.test.ts"
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
}
|