sovergate 0.0.0-stage → 0.1.0-alpha.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Roberto Postolovski
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
@@ -1,3 +1,130 @@
1
- # Temporary Holding Version
1
+ # sovergate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript / JavaScript SDK for [Sovergate](https://sovergate.com): EU AI Act
4
+ Article 12 audit logging for LLM calls, with PII scrubbed inside your own
5
+ infrastructure.
6
+
7
+ > **Early access.** The SDK is published as an alpha and the hosted ingest
8
+ > service is not generally available yet. APIs may change before 1.0.
9
+
10
+ ## How it works
11
+
12
+ Your app keeps calling OpenAI or Anthropic directly. Sovergate does not proxy
13
+ or intercept the request. After a call completes, the SDK takes a copy,
14
+ scrubs personal data **locally**, and sends the scrubbed copy to Sovergate in
15
+ the background.
16
+
17
+ ```
18
+ Your app ─────────────────────────────► OpenAI / Anthropic
19
+ │
20
+ └──► scrub PII locally ──► Sovergate (async, fire and forget)
21
+ ```
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npm install sovergate@alpha
27
+ ```
28
+
29
+ Requires Node.js 18+. Ships ESM and CommonJS builds with TypeScript types.
30
+ No runtime dependencies.
31
+
32
+ ## Quick start
33
+
34
+ ```typescript
35
+ import OpenAI from 'openai'
36
+ import { init, instrument } from 'sovergate'
37
+
38
+ const openai = new OpenAI()
39
+
40
+ init({ apiKey: 'sov_prod_xxxx' })
41
+ instrument(openai)
42
+
43
+ // Your existing code is unchanged
44
+ const response = await openai.chat.completions.create({
45
+ model: 'gpt-4o',
46
+ messages: [{ role: 'user', content: 'Hello' }],
47
+ })
48
+ // The response is returned immediately; logging happens in the background
49
+ ```
50
+
51
+ Anthropic works the same way:
52
+
53
+ ```typescript
54
+ import Anthropic from '@anthropic-ai/sdk'
55
+ import { init, instrument } from 'sovergate'
56
+
57
+ const client = new Anthropic()
58
+ init({ apiKey: 'sov_prod_xxxx' })
59
+ instrument(client)
60
+ ```
61
+
62
+ ## Options
63
+
64
+ ```typescript
65
+ init({
66
+ apiKey: 'sov_prod_xxxx', // required
67
+ baseUrl: 'https://...', // default: https://api.sovergate.com
68
+ debug: true, // print SDK internals to console.debug
69
+ })
70
+ ```
71
+
72
+ ## What is sent to Sovergate
73
+
74
+ For each completed call, one JSON record:
75
+
76
+ | Field | Content |
77
+ |---|---|
78
+ | `model` | Model name returned by the provider |
79
+ | `messages` | Your prompt messages, **after** PII scrubbing |
80
+ | `response` | The model's text output, **after** PII scrubbing |
81
+ | `prompt_tokens`, `completion_tokens` | Token counts from the provider |
82
+ | `latency_ms`, `timestamp` | Call duration and start time |
83
+ | `finish_reason` | Why the model stopped |
84
+ | `pii_detections` | Category and placeholder of each scrubbed item, never the original value |
85
+
86
+ Never sent: the raw prompt or response, your LLM provider API key, or request
87
+ headers.
88
+
89
+ ### What the scrubber detects
90
+
91
+ Regex-based, applied to every text part of the prompt and response: email
92
+ addresses, IP addresses, IBANs, phone numbers, dates of birth, ID numbers, and
93
+ names that follow a title (Mr, Mrs, Ms, Dr, Prof). Other personal data, such
94
+ as names without a title, is **not** detected yet, so keep data minimisation in
95
+ mind when you build prompts. The code is in [`src/scrubber.ts`](src/scrubber.ts).
96
+
97
+ ## Guarantees
98
+
99
+ - Never blocks the event loop: logging runs via `setImmediate`
100
+ - Never throws: if setup fails, the SDK logs a `console.warn` and stays out of the way
101
+ - Leaves your client unchanged: `create()` still returns the provider SDK's own
102
+ object, so `.withResponse()` and `.asResponse()` keep working
103
+ - 2 second timeout on every HTTP call to Sovergate
104
+ - If Sovergate is unreachable, records are queued in memory (up to 10,000) and
105
+ retried with exponential backoff
106
+ - A record Sovergate permanently rejects (for example after a key is revoked) is
107
+ dropped with a `console.warn`, so it never blocks the records behind it
108
+
109
+ ## Current limitations
110
+
111
+ - Streaming calls (`stream: true`) are passed through untouched but not logged yet.
112
+ - PII placeholders are positional (`[EMAIL_REDACTED_1]`) and not correlatable across logs.
113
+ - The retry queue is in memory, so queued records are lost if the process exits.
114
+
115
+ ## Development
116
+
117
+ ```bash
118
+ npm ci
119
+ npm run typecheck
120
+ npm test
121
+ npm run build
122
+ ```
123
+
124
+ ## Security
125
+
126
+ Please report vulnerabilities privately. See [SECURITY.md](SECURITY.md).
127
+
128
+ ## License
129
+
130
+ [MIT](LICENSE)
@@ -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 };
@@ -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,358 @@
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
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
33
+ async function send(apiKey, payload, baseUrl = BASE_URL) {
34
+ try {
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
37
+ const response = await fetch(`${baseUrl}/ingest`, {
38
+ method: "POST",
39
+ headers: {
40
+ Authorization: `Bearer ${apiKey}`,
41
+ "Content-Type": "application/json"
42
+ },
43
+ body: JSON.stringify(payload),
44
+ signal: controller.signal
45
+ });
46
+ clearTimeout(timer);
47
+ if (response.status < 400) return "sent";
48
+ if (RETRYABLE_STATUS.has(response.status) || response.status >= 500) return "retry";
49
+ try {
50
+ console.warn(`[sovergate] ingest rejected a record (HTTP ${response.status}); dropping it`);
51
+ } catch {
52
+ }
53
+ return "drop";
54
+ } catch {
55
+ return "retry";
56
+ }
57
+ }
58
+
59
+ // src/queue.ts
60
+ var MAX_QUEUE = 1e4;
61
+ var RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60];
62
+ function sleep(ms) {
63
+ return new Promise((resolve) => setTimeout(resolve, ms));
64
+ }
65
+ var LogQueue = class {
66
+ sendFn;
67
+ items = [];
68
+ draining = false;
69
+ constructor(sendFn) {
70
+ this.sendFn = sendFn;
71
+ }
72
+ enqueue(payload) {
73
+ if (this.items.length >= MAX_QUEUE) return;
74
+ this.items.push(payload);
75
+ if (!this.draining) {
76
+ setImmediate(() => void this.drain());
77
+ }
78
+ }
79
+ async drain() {
80
+ this.draining = true;
81
+ let retryIndex = 0;
82
+ while (this.items.length > 0) {
83
+ let done = false;
84
+ try {
85
+ const outcome = await this.sendFn(this.items[0]);
86
+ done = outcome === true || outcome === "sent" || outcome === "drop";
87
+ } catch {
88
+ }
89
+ if (done) {
90
+ this.items.shift();
91
+ retryIndex = 0;
92
+ } else {
93
+ const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)];
94
+ retryIndex++;
95
+ await sleep(delaySec * 1e3);
96
+ }
97
+ }
98
+ this.draining = false;
99
+ }
100
+ };
101
+
102
+ // src/scrubber.ts
103
+ var RULES = [
104
+ ["email", /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, "EMAIL_REDACTED"],
105
+ ["ip", /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "IP_REDACTED"],
106
+ ["iban", /\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/g, "IBAN_REDACTED"],
107
+ // (?<![\w+]) instead of \b so a leading "+" is part of the match, not left behind
108
+ ["phone", /(?<![\w+])\+?\d[\d\s\-(). ]{5,14}\d\b/g, "PHONE_REDACTED"],
109
+ ["dob", /\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})\b/g, "DOB_REDACTED"],
110
+ ["id_number", /\b[A-Z]{1,3}\d{6,9}\b/g, "ID_REDACTED"],
111
+ ["name", /\b(?:Mr|Mrs|Ms|Dr|Prof)\.?\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\b/g, "NAME_REDACTED"]
112
+ ];
113
+ var ScrubContext = class {
114
+ seen = /* @__PURE__ */ new Map();
115
+ next = /* @__PURE__ */ new Map();
116
+ placeholder(base, value) {
117
+ const key = `${base}\0${value.replace(/[\s-]/g, "").toLowerCase()}`;
118
+ let n = this.seen.get(key);
119
+ if (n === void 0) {
120
+ n = (this.next.get(base) ?? 0) + 1;
121
+ this.next.set(base, n);
122
+ this.seen.set(key, n);
123
+ }
124
+ return `[${base}_${n}]`;
125
+ }
126
+ };
127
+ function scrub(text, context = new ScrubContext()) {
128
+ if (!text) return { text, detections: [] };
129
+ const detections = [];
130
+ let result = text;
131
+ for (const [category, pattern, basePlaceholder] of RULES) {
132
+ pattern.lastIndex = 0;
133
+ result = result.replace(pattern, (match) => {
134
+ const placeholder = context.placeholder(basePlaceholder, match);
135
+ detections.push({ category, placeholder });
136
+ return placeholder;
137
+ });
138
+ }
139
+ return { text: result, detections };
140
+ }
141
+ function scrubMessages(messages, context = new ScrubContext()) {
142
+ const allDetections = [];
143
+ const scrubbed = [];
144
+ for (const msg of messages) {
145
+ const content = msg.content;
146
+ if (typeof content === "string") {
147
+ const result = scrub(content, context);
148
+ scrubbed.push({ ...msg, content: result.text });
149
+ allDetections.push(...result.detections);
150
+ } else if (Array.isArray(content)) {
151
+ const newBlocks = content.map((block) => {
152
+ if (block.type === "text" && typeof block.text === "string") {
153
+ const result = scrub(block.text, context);
154
+ allDetections.push(...result.detections);
155
+ return { ...block, text: result.text };
156
+ }
157
+ return block;
158
+ });
159
+ scrubbed.push({ ...msg, content: newBlocks });
160
+ } else {
161
+ scrubbed.push(msg);
162
+ }
163
+ }
164
+ return [scrubbed, allDetections];
165
+ }
166
+
167
+ // src/patcher.ts
168
+ var PATCHED = /* @__PURE__ */ Symbol.for("sovergate.patched");
169
+ function utcnow() {
170
+ return (/* @__PURE__ */ new Date()).toISOString();
171
+ }
172
+ function observe(result, onData) {
173
+ if (!result || typeof result.then !== "function") return result;
174
+ let seen = false;
175
+ const notify = (data) => {
176
+ if (seen) return;
177
+ seen = true;
178
+ try {
179
+ onData(data);
180
+ } catch {
181
+ }
182
+ };
183
+ try {
184
+ return new Proxy(result, {
185
+ get(target, prop) {
186
+ const value = Reflect.get(target, prop, target);
187
+ if (typeof value !== "function") return value;
188
+ if (prop === "then") {
189
+ return (onFulfilled, onRejected) => value.call(
190
+ target,
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ (data) => {
193
+ notify(data);
194
+ return typeof onFulfilled === "function" ? onFulfilled(data) : data;
195
+ },
196
+ onRejected
197
+ );
198
+ }
199
+ if (prop === "withResponse") {
200
+ return (...args) => (
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ value.apply(target, args).then((withResponse) => {
203
+ notify(withResponse?.data);
204
+ return withResponse;
205
+ })
206
+ );
207
+ }
208
+ return value.bind(target);
209
+ }
210
+ });
211
+ } catch {
212
+ return result;
213
+ }
214
+ }
215
+ function isStream(opts) {
216
+ return opts?.stream === true;
217
+ }
218
+ function patchOpenAI(client, enqueue) {
219
+ const completions = client.chat.completions;
220
+ if (completions[PATCHED]) return;
221
+ const original = completions.create.bind(completions);
222
+ completions.create = (opts, ...rest) => {
223
+ const requestAt = utcnow();
224
+ const t0 = performance.now();
225
+ const result = original(opts, ...rest);
226
+ if (isStream(opts)) return result;
227
+ return observe(result, (response) => {
228
+ const latencyMs = Math.round(performance.now() - t0);
229
+ setImmediate(() => {
230
+ try {
231
+ const messages = opts?.messages ?? [];
232
+ const model = opts?.model ?? "";
233
+ const context = new ScrubContext();
234
+ const [scrubbedMessages, promptDetections] = scrubMessages(messages, context);
235
+ const choice = response?.choices?.[0];
236
+ const responseText = choice?.message?.content ?? "";
237
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
238
+ const scrubbedResponse = scrubbedResult?.text ?? "";
239
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
240
+ const usage = response?.usage;
241
+ enqueue({
242
+ model: response?.model ?? model,
243
+ messages: scrubbedMessages,
244
+ response: scrubbedResponse,
245
+ prompt_tokens: usage?.prompt_tokens ?? 0,
246
+ completion_tokens: usage?.completion_tokens ?? 0,
247
+ latency_ms: latencyMs,
248
+ timestamp: requestAt,
249
+ finish_reason: choice?.finish_reason ?? null,
250
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
251
+ });
252
+ } catch {
253
+ }
254
+ });
255
+ });
256
+ };
257
+ completions[PATCHED] = true;
258
+ }
259
+ function patchAnthropic(client, enqueue) {
260
+ const messagesApi = client.messages;
261
+ if (messagesApi[PATCHED]) return;
262
+ const original = messagesApi.create.bind(messagesApi);
263
+ messagesApi.create = (opts, ...rest) => {
264
+ const requestAt = utcnow();
265
+ const t0 = performance.now();
266
+ const result = original(opts, ...rest);
267
+ if (isStream(opts)) return result;
268
+ return observe(result, (response) => {
269
+ const latencyMs = Math.round(performance.now() - t0);
270
+ setImmediate(() => {
271
+ try {
272
+ const messages = opts?.messages ?? [];
273
+ const system = opts?.system;
274
+ const model = opts?.model ?? "";
275
+ const allMessages = system ? [{ role: "system", content: system }, ...messages] : messages;
276
+ const context = new ScrubContext();
277
+ const [scrubbedMessages, promptDetections] = scrubMessages(allMessages, context);
278
+ const responseText = (response?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
279
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
280
+ const scrubbedResponse = scrubbedResult?.text ?? "";
281
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
282
+ const usage = response?.usage;
283
+ enqueue({
284
+ model: response?.model ?? model,
285
+ messages: scrubbedMessages,
286
+ response: scrubbedResponse,
287
+ prompt_tokens: usage?.input_tokens ?? 0,
288
+ completion_tokens: usage?.output_tokens ?? 0,
289
+ latency_ms: latencyMs,
290
+ timestamp: requestAt,
291
+ finish_reason: response?.stop_reason ?? null,
292
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
293
+ });
294
+ } catch {
295
+ }
296
+ });
297
+ });
298
+ };
299
+ messagesApi[PATCHED] = true;
300
+ }
301
+
302
+ // src/client.ts
303
+ var state = {
304
+ apiKey: null,
305
+ baseUrl: "https://api.sovergate.com",
306
+ queue: null
307
+ };
308
+ function init({ apiKey, baseUrl = "https://api.sovergate.com", debug = false }) {
309
+ try {
310
+ state.apiKey = apiKey;
311
+ state.baseUrl = baseUrl.replace(/\/$/, "");
312
+ if (debug) {
313
+ console.debug("[sovergate] init baseUrl=%s", state.baseUrl);
314
+ }
315
+ state.queue = new LogQueue((payload) => send(apiKey, payload, state.baseUrl));
316
+ } catch (err) {
317
+ state.queue = null;
318
+ warn("init failed, logging disabled", err);
319
+ }
320
+ }
321
+ function instrument(client) {
322
+ if (!state.queue) {
323
+ warn("instrument() called before init({ apiKey }) \u2014 LLM calls will not be logged");
324
+ return;
325
+ }
326
+ try {
327
+ const enqueue = state.queue.enqueue.bind(state.queue);
328
+ if (client?.chat?.completions) {
329
+ patchOpenAI(client, enqueue);
330
+ return;
331
+ }
332
+ if (client?.messages) {
333
+ patchAnthropic(client, enqueue);
334
+ return;
335
+ }
336
+ warn(
337
+ "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"
338
+ );
339
+ } catch (err) {
340
+ warn("instrument() failed, LLM calls will not be logged", err);
341
+ }
342
+ }
343
+ function warn(message, err) {
344
+ try {
345
+ console.warn(`[sovergate] ${message}`, ...err === void 0 ? [] : [err]);
346
+ } catch {
347
+ }
348
+ }
349
+ function shutdown() {
350
+ state.queue = null;
351
+ }
352
+ // Annotate the CommonJS export names for ESM import in node:
353
+ 0 && (module.exports = {
354
+ init,
355
+ instrument,
356
+ shutdown
357
+ });
358
+ //# 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\nconst RETRYABLE_STATUS = new Set([408, 425, 429])\n\n/**\n * sent: accepted by the server\n * retry: temporary failure, keep the record and try again later\n * drop: permanently rejected, retrying the same record can never succeed\n */\nexport type SendOutcome = 'sent' | 'retry' | 'drop'\n\nexport async function send(\n apiKey: string,\n payload: Record<string, unknown>,\n baseUrl = BASE_URL,\n): Promise<SendOutcome> {\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 if (response.status < 400) return 'sent'\n if (RETRYABLE_STATUS.has(response.status) || response.status >= 500) return 'retry'\n\n // 400 (invalid record), 401/403 (bad or revoked key), 413 (too large)...\n // Retrying would block every later record behind this one.\n try {\n console.warn(`[sovergate] ingest rejected a record (HTTP ${response.status}); dropping it`)\n } catch {\n // never propagate\n }\n return 'drop'\n } catch {\n return 'retry'\n }\n}\n","import type { SendOutcome } from './sender'\n\ntype Payload = Record<string, unknown>\ntype SendFn = (payload: Payload) => Promise<SendOutcome | 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 done = false\n try {\n const outcome = await this.sendFn(this.items[0])\n // 'sent' and 'drop' both finish this record; true is accepted as 'sent'.\n done = outcome === true || outcome === 'sent' || outcome === 'drop'\n } catch {\n // never propagate\n }\n\n if (done) {\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 // (?<![\\w+]) instead of \\b so a leading \"+\" is part of the match, not left behind\n ['phone', /(?<![\\w+])\\+?\\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\n/**\n * Numbers placeholders across one whole record (all messages and the response),\n * so the same value always gets the same placeholder and two different values\n * never share one.\n */\nexport class ScrubContext {\n private readonly seen = new Map<string, number>()\n private readonly next = new Map<string, number>()\n\n placeholder(base: string, value: string): string {\n const key = `${base}\\u0000${value.replace(/[\\s-]/g, '').toLowerCase()}`\n let n = this.seen.get(key)\n if (n === undefined) {\n n = (this.next.get(base) ?? 0) + 1\n this.next.set(base, n)\n this.seen.set(key, n)\n }\n return `[${base}_${n}]`\n }\n}\n\nexport function scrub(text: string, context: ScrubContext = new ScrubContext()): ScrubResult {\n if (!text) return { text, detections: [] }\n\n const detections: Detection[] = []\n\n let result = text\n for (const [category, pattern, basePlaceholder] of RULES) {\n pattern.lastIndex = 0\n result = result.replace(pattern, match => {\n const placeholder = context.placeholder(basePlaceholder, match)\n detections.push({ category, placeholder })\n return placeholder\n })\n }\n\n return { text: result, detections }\n}\n\nexport function scrubMessages(\n messages: OpenAIMessage[],\n context: ScrubContext = new ScrubContext(),\n): [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, context)\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, context)\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 { ScrubContext, 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 context = new ScrubContext()\n const [scrubbedMessages, promptDetections] = scrubMessages(messages, context)\n const choice = response?.choices?.[0]\n const responseText: string = choice?.message?.content ?? ''\n const scrubbedResult = responseText ? scrub(responseText, context) : null\n const scrubbedResponse = scrubbedResult?.text ?? ''\n const detections = [...promptDetections, ...(scrubbedResult?.detections ?? [])]\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 context = new ScrubContext()\n const [scrubbedMessages, promptDetections] = scrubMessages(allMessages, context)\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 scrubbedResult = responseText ? scrub(responseText, context) : null\n const scrubbedResponse = scrubbedResult?.text ?? ''\n const detections = [...promptDetections, ...(scrubbedResult?.detections ?? [])]\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,329 @@
1
+ // src/sender.ts
2
+ var BASE_URL = "https://api.sovergate.com";
3
+ var TIMEOUT_MS = 2e3;
4
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
5
+ async function send(apiKey, payload, baseUrl = BASE_URL) {
6
+ try {
7
+ const controller = new AbortController();
8
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
9
+ const response = await fetch(`${baseUrl}/ingest`, {
10
+ method: "POST",
11
+ headers: {
12
+ Authorization: `Bearer ${apiKey}`,
13
+ "Content-Type": "application/json"
14
+ },
15
+ body: JSON.stringify(payload),
16
+ signal: controller.signal
17
+ });
18
+ clearTimeout(timer);
19
+ if (response.status < 400) return "sent";
20
+ if (RETRYABLE_STATUS.has(response.status) || response.status >= 500) return "retry";
21
+ try {
22
+ console.warn(`[sovergate] ingest rejected a record (HTTP ${response.status}); dropping it`);
23
+ } catch {
24
+ }
25
+ return "drop";
26
+ } catch {
27
+ return "retry";
28
+ }
29
+ }
30
+
31
+ // src/queue.ts
32
+ var MAX_QUEUE = 1e4;
33
+ var RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60];
34
+ function sleep(ms) {
35
+ return new Promise((resolve) => setTimeout(resolve, ms));
36
+ }
37
+ var LogQueue = class {
38
+ sendFn;
39
+ items = [];
40
+ draining = false;
41
+ constructor(sendFn) {
42
+ this.sendFn = sendFn;
43
+ }
44
+ enqueue(payload) {
45
+ if (this.items.length >= MAX_QUEUE) return;
46
+ this.items.push(payload);
47
+ if (!this.draining) {
48
+ setImmediate(() => void this.drain());
49
+ }
50
+ }
51
+ async drain() {
52
+ this.draining = true;
53
+ let retryIndex = 0;
54
+ while (this.items.length > 0) {
55
+ let done = false;
56
+ try {
57
+ const outcome = await this.sendFn(this.items[0]);
58
+ done = outcome === true || outcome === "sent" || outcome === "drop";
59
+ } catch {
60
+ }
61
+ if (done) {
62
+ this.items.shift();
63
+ retryIndex = 0;
64
+ } else {
65
+ const delaySec = RETRY_DELAYS_S[Math.min(retryIndex, RETRY_DELAYS_S.length - 1)];
66
+ retryIndex++;
67
+ await sleep(delaySec * 1e3);
68
+ }
69
+ }
70
+ this.draining = false;
71
+ }
72
+ };
73
+
74
+ // src/scrubber.ts
75
+ var RULES = [
76
+ ["email", /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, "EMAIL_REDACTED"],
77
+ ["ip", /\b(?:\d{1,3}\.){3}\d{1,3}\b/g, "IP_REDACTED"],
78
+ ["iban", /\b[A-Z]{2}\d{2}[A-Z0-9]{4,30}\b/g, "IBAN_REDACTED"],
79
+ // (?<![\w+]) instead of \b so a leading "+" is part of the match, not left behind
80
+ ["phone", /(?<![\w+])\+?\d[\d\s\-(). ]{5,14}\d\b/g, "PHONE_REDACTED"],
81
+ ["dob", /\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})\b/g, "DOB_REDACTED"],
82
+ ["id_number", /\b[A-Z]{1,3}\d{6,9}\b/g, "ID_REDACTED"],
83
+ ["name", /\b(?:Mr|Mrs|Ms|Dr|Prof)\.?\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\b/g, "NAME_REDACTED"]
84
+ ];
85
+ var ScrubContext = class {
86
+ seen = /* @__PURE__ */ new Map();
87
+ next = /* @__PURE__ */ new Map();
88
+ placeholder(base, value) {
89
+ const key = `${base}\0${value.replace(/[\s-]/g, "").toLowerCase()}`;
90
+ let n = this.seen.get(key);
91
+ if (n === void 0) {
92
+ n = (this.next.get(base) ?? 0) + 1;
93
+ this.next.set(base, n);
94
+ this.seen.set(key, n);
95
+ }
96
+ return `[${base}_${n}]`;
97
+ }
98
+ };
99
+ function scrub(text, context = new ScrubContext()) {
100
+ if (!text) return { text, detections: [] };
101
+ const detections = [];
102
+ let result = text;
103
+ for (const [category, pattern, basePlaceholder] of RULES) {
104
+ pattern.lastIndex = 0;
105
+ result = result.replace(pattern, (match) => {
106
+ const placeholder = context.placeholder(basePlaceholder, match);
107
+ detections.push({ category, placeholder });
108
+ return placeholder;
109
+ });
110
+ }
111
+ return { text: result, detections };
112
+ }
113
+ function scrubMessages(messages, context = new ScrubContext()) {
114
+ const allDetections = [];
115
+ const scrubbed = [];
116
+ for (const msg of messages) {
117
+ const content = msg.content;
118
+ if (typeof content === "string") {
119
+ const result = scrub(content, context);
120
+ scrubbed.push({ ...msg, content: result.text });
121
+ allDetections.push(...result.detections);
122
+ } else if (Array.isArray(content)) {
123
+ const newBlocks = content.map((block) => {
124
+ if (block.type === "text" && typeof block.text === "string") {
125
+ const result = scrub(block.text, context);
126
+ allDetections.push(...result.detections);
127
+ return { ...block, text: result.text };
128
+ }
129
+ return block;
130
+ });
131
+ scrubbed.push({ ...msg, content: newBlocks });
132
+ } else {
133
+ scrubbed.push(msg);
134
+ }
135
+ }
136
+ return [scrubbed, allDetections];
137
+ }
138
+
139
+ // src/patcher.ts
140
+ var PATCHED = /* @__PURE__ */ Symbol.for("sovergate.patched");
141
+ function utcnow() {
142
+ return (/* @__PURE__ */ new Date()).toISOString();
143
+ }
144
+ function observe(result, onData) {
145
+ if (!result || typeof result.then !== "function") return result;
146
+ let seen = false;
147
+ const notify = (data) => {
148
+ if (seen) return;
149
+ seen = true;
150
+ try {
151
+ onData(data);
152
+ } catch {
153
+ }
154
+ };
155
+ try {
156
+ return new Proxy(result, {
157
+ get(target, prop) {
158
+ const value = Reflect.get(target, prop, target);
159
+ if (typeof value !== "function") return value;
160
+ if (prop === "then") {
161
+ return (onFulfilled, onRejected) => value.call(
162
+ target,
163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
164
+ (data) => {
165
+ notify(data);
166
+ return typeof onFulfilled === "function" ? onFulfilled(data) : data;
167
+ },
168
+ onRejected
169
+ );
170
+ }
171
+ if (prop === "withResponse") {
172
+ return (...args) => (
173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
+ value.apply(target, args).then((withResponse) => {
175
+ notify(withResponse?.data);
176
+ return withResponse;
177
+ })
178
+ );
179
+ }
180
+ return value.bind(target);
181
+ }
182
+ });
183
+ } catch {
184
+ return result;
185
+ }
186
+ }
187
+ function isStream(opts) {
188
+ return opts?.stream === true;
189
+ }
190
+ function patchOpenAI(client, enqueue) {
191
+ const completions = client.chat.completions;
192
+ if (completions[PATCHED]) return;
193
+ const original = completions.create.bind(completions);
194
+ completions.create = (opts, ...rest) => {
195
+ const requestAt = utcnow();
196
+ const t0 = performance.now();
197
+ const result = original(opts, ...rest);
198
+ if (isStream(opts)) return result;
199
+ return observe(result, (response) => {
200
+ const latencyMs = Math.round(performance.now() - t0);
201
+ setImmediate(() => {
202
+ try {
203
+ const messages = opts?.messages ?? [];
204
+ const model = opts?.model ?? "";
205
+ const context = new ScrubContext();
206
+ const [scrubbedMessages, promptDetections] = scrubMessages(messages, context);
207
+ const choice = response?.choices?.[0];
208
+ const responseText = choice?.message?.content ?? "";
209
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
210
+ const scrubbedResponse = scrubbedResult?.text ?? "";
211
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
212
+ const usage = response?.usage;
213
+ enqueue({
214
+ model: response?.model ?? model,
215
+ messages: scrubbedMessages,
216
+ response: scrubbedResponse,
217
+ prompt_tokens: usage?.prompt_tokens ?? 0,
218
+ completion_tokens: usage?.completion_tokens ?? 0,
219
+ latency_ms: latencyMs,
220
+ timestamp: requestAt,
221
+ finish_reason: choice?.finish_reason ?? null,
222
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
223
+ });
224
+ } catch {
225
+ }
226
+ });
227
+ });
228
+ };
229
+ completions[PATCHED] = true;
230
+ }
231
+ function patchAnthropic(client, enqueue) {
232
+ const messagesApi = client.messages;
233
+ if (messagesApi[PATCHED]) return;
234
+ const original = messagesApi.create.bind(messagesApi);
235
+ messagesApi.create = (opts, ...rest) => {
236
+ const requestAt = utcnow();
237
+ const t0 = performance.now();
238
+ const result = original(opts, ...rest);
239
+ if (isStream(opts)) return result;
240
+ return observe(result, (response) => {
241
+ const latencyMs = Math.round(performance.now() - t0);
242
+ setImmediate(() => {
243
+ try {
244
+ const messages = opts?.messages ?? [];
245
+ const system = opts?.system;
246
+ const model = opts?.model ?? "";
247
+ const allMessages = system ? [{ role: "system", content: system }, ...messages] : messages;
248
+ const context = new ScrubContext();
249
+ const [scrubbedMessages, promptDetections] = scrubMessages(allMessages, context);
250
+ const responseText = (response?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
251
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
252
+ const scrubbedResponse = scrubbedResult?.text ?? "";
253
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
254
+ const usage = response?.usage;
255
+ enqueue({
256
+ model: response?.model ?? model,
257
+ messages: scrubbedMessages,
258
+ response: scrubbedResponse,
259
+ prompt_tokens: usage?.input_tokens ?? 0,
260
+ completion_tokens: usage?.output_tokens ?? 0,
261
+ latency_ms: latencyMs,
262
+ timestamp: requestAt,
263
+ finish_reason: response?.stop_reason ?? null,
264
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
265
+ });
266
+ } catch {
267
+ }
268
+ });
269
+ });
270
+ };
271
+ messagesApi[PATCHED] = true;
272
+ }
273
+
274
+ // src/client.ts
275
+ var state = {
276
+ apiKey: null,
277
+ baseUrl: "https://api.sovergate.com",
278
+ queue: null
279
+ };
280
+ function init({ apiKey, baseUrl = "https://api.sovergate.com", debug = false }) {
281
+ try {
282
+ state.apiKey = apiKey;
283
+ state.baseUrl = baseUrl.replace(/\/$/, "");
284
+ if (debug) {
285
+ console.debug("[sovergate] init baseUrl=%s", state.baseUrl);
286
+ }
287
+ state.queue = new LogQueue((payload) => send(apiKey, payload, state.baseUrl));
288
+ } catch (err) {
289
+ state.queue = null;
290
+ warn("init failed, logging disabled", err);
291
+ }
292
+ }
293
+ function instrument(client) {
294
+ if (!state.queue) {
295
+ warn("instrument() called before init({ apiKey }) \u2014 LLM calls will not be logged");
296
+ return;
297
+ }
298
+ try {
299
+ const enqueue = state.queue.enqueue.bind(state.queue);
300
+ if (client?.chat?.completions) {
301
+ patchOpenAI(client, enqueue);
302
+ return;
303
+ }
304
+ if (client?.messages) {
305
+ patchAnthropic(client, enqueue);
306
+ return;
307
+ }
308
+ warn(
309
+ "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"
310
+ );
311
+ } catch (err) {
312
+ warn("instrument() failed, LLM calls will not be logged", err);
313
+ }
314
+ }
315
+ function warn(message, err) {
316
+ try {
317
+ console.warn(`[sovergate] ${message}`, ...err === void 0 ? [] : [err]);
318
+ } catch {
319
+ }
320
+ }
321
+ function shutdown() {
322
+ state.queue = null;
323
+ }
324
+ export {
325
+ init,
326
+ instrument,
327
+ shutdown
328
+ };
329
+ //# 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\nconst RETRYABLE_STATUS = new Set([408, 425, 429])\n\n/**\n * sent: accepted by the server\n * retry: temporary failure, keep the record and try again later\n * drop: permanently rejected, retrying the same record can never succeed\n */\nexport type SendOutcome = 'sent' | 'retry' | 'drop'\n\nexport async function send(\n apiKey: string,\n payload: Record<string, unknown>,\n baseUrl = BASE_URL,\n): Promise<SendOutcome> {\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 if (response.status < 400) return 'sent'\n if (RETRYABLE_STATUS.has(response.status) || response.status >= 500) return 'retry'\n\n // 400 (invalid record), 401/403 (bad or revoked key), 413 (too large)...\n // Retrying would block every later record behind this one.\n try {\n console.warn(`[sovergate] ingest rejected a record (HTTP ${response.status}); dropping it`)\n } catch {\n // never propagate\n }\n return 'drop'\n } catch {\n return 'retry'\n }\n}\n","import type { SendOutcome } from './sender'\n\ntype Payload = Record<string, unknown>\ntype SendFn = (payload: Payload) => Promise<SendOutcome | 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 done = false\n try {\n const outcome = await this.sendFn(this.items[0])\n // 'sent' and 'drop' both finish this record; true is accepted as 'sent'.\n done = outcome === true || outcome === 'sent' || outcome === 'drop'\n } catch {\n // never propagate\n }\n\n if (done) {\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 // (?<![\\w+]) instead of \\b so a leading \"+\" is part of the match, not left behind\n ['phone', /(?<![\\w+])\\+?\\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\n/**\n * Numbers placeholders across one whole record (all messages and the response),\n * so the same value always gets the same placeholder and two different values\n * never share one.\n */\nexport class ScrubContext {\n private readonly seen = new Map<string, number>()\n private readonly next = new Map<string, number>()\n\n placeholder(base: string, value: string): string {\n const key = `${base}\\u0000${value.replace(/[\\s-]/g, '').toLowerCase()}`\n let n = this.seen.get(key)\n if (n === undefined) {\n n = (this.next.get(base) ?? 0) + 1\n this.next.set(base, n)\n this.seen.set(key, n)\n }\n return `[${base}_${n}]`\n }\n}\n\nexport function scrub(text: string, context: ScrubContext = new ScrubContext()): ScrubResult {\n if (!text) return { text, detections: [] }\n\n const detections: Detection[] = []\n\n let result = text\n for (const [category, pattern, basePlaceholder] of RULES) {\n pattern.lastIndex = 0\n result = result.replace(pattern, match => {\n const placeholder = context.placeholder(basePlaceholder, match)\n detections.push({ category, placeholder })\n return placeholder\n })\n }\n\n return { text: result, detections }\n}\n\nexport function scrubMessages(\n messages: OpenAIMessage[],\n context: ScrubContext = new ScrubContext(),\n): [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, context)\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, context)\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 { ScrubContext, 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 context = new ScrubContext()\n const [scrubbedMessages, promptDetections] = scrubMessages(messages, context)\n const choice = response?.choices?.[0]\n const responseText: string = choice?.message?.content ?? ''\n const scrubbedResult = responseText ? scrub(responseText, context) : null\n const scrubbedResponse = scrubbedResult?.text ?? ''\n const detections = [...promptDetections, ...(scrubbedResult?.detections ?? [])]\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 context = new ScrubContext()\n const [scrubbedMessages, promptDetections] = scrubMessages(allMessages, context)\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 scrubbedResult = responseText ? scrub(responseText, context) : null\n const scrubbedResponse = scrubbedResult?.text ?? ''\n const detections = [...promptDetections, ...(scrubbedResult?.detections ?? [])]\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,73 @@
1
1
  {
2
2
  "name": "sovergate",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0-alpha.2",
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
+ "openai",
39
+ "anthropic",
40
+ "pii",
41
+ "observability"
42
+ ],
43
+ "license": "MIT",
44
+ "author": "Roberto Postolovski",
45
+ "homepage": "https://sovergate.com",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/sovergate/sdk-js.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/sovergate/sdk-js/issues"
52
+ },
53
+ "engines": {
54
+ "node": ">=18"
55
+ },
56
+ "devDependencies": {
57
+ "@anthropic-ai/sdk": "^1.3.0",
58
+ "@types/jest": "^29.5.0",
59
+ "@types/node": "^20.0.0",
60
+ "jest": "^29.5.0",
61
+ "openai": "^7.43.0",
62
+ "ts-jest": "^29.1.0",
63
+ "tsup": "^8.5.1",
64
+ "typescript": "^5.0.0"
65
+ },
66
+ "jest": {
67
+ "preset": "ts-jest",
68
+ "testEnvironment": "node",
69
+ "testMatch": [
70
+ "**/tests/**/*.test.ts"
71
+ ]
72
+ }
73
+ }