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 CHANGED
@@ -1,3 +1,65 @@
1
- # Temporary Holding Version
1
+ # sovergate — TypeScript SDK
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
+ 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.
@@ -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,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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }