sovergate 0.1.0-alpha.1 → 0.1.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -29,10 +29,13 @@ module.exports = __toCommonJS(index_exports);
29
29
  // src/sender.ts
30
30
  var BASE_URL = "https://api.sovergate.com";
31
31
  var TIMEOUT_MS = 2e3;
32
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 425, 429]);
32
33
  async function send(apiKey, payload, baseUrl = BASE_URL) {
34
+ let timer;
33
35
  try {
34
36
  const controller = new AbortController();
35
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
37
+ timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
38
+ timer.unref?.();
36
39
  const response = await fetch(`${baseUrl}/ingest`, {
37
40
  method: "POST",
38
41
  headers: {
@@ -42,10 +45,17 @@ async function send(apiKey, payload, baseUrl = BASE_URL) {
42
45
  body: JSON.stringify(payload),
43
46
  signal: controller.signal
44
47
  });
45
- clearTimeout(timer);
46
- return response.status < 400;
48
+ if (response.status < 400) return "sent";
49
+ if (RETRYABLE_STATUS.has(response.status) || response.status >= 500) return "retry";
50
+ try {
51
+ console.warn(`[sovergate] ingest rejected a record (HTTP ${response.status}); dropping it`);
52
+ } catch {
53
+ }
54
+ return "drop";
47
55
  } catch {
48
- return false;
56
+ return "retry";
57
+ } finally {
58
+ clearTimeout(timer);
49
59
  }
50
60
  }
51
61
 
@@ -53,7 +63,10 @@ async function send(apiKey, payload, baseUrl = BASE_URL) {
53
63
  var MAX_QUEUE = 1e4;
54
64
  var RETRY_DELAYS_S = [1, 2, 4, 8, 16, 30, 60];
55
65
  function sleep(ms) {
56
- return new Promise((resolve) => setTimeout(resolve, ms));
66
+ return new Promise((resolve) => {
67
+ const timer = setTimeout(resolve, ms);
68
+ timer.unref?.();
69
+ });
57
70
  }
58
71
  var LogQueue = class {
59
72
  sendFn;
@@ -73,12 +86,13 @@ var LogQueue = class {
73
86
  this.draining = true;
74
87
  let retryIndex = 0;
75
88
  while (this.items.length > 0) {
76
- let success = false;
89
+ let done = false;
77
90
  try {
78
- success = await this.sendFn(this.items[0]);
91
+ const outcome = await this.sendFn(this.items[0]);
92
+ done = outcome === true || outcome === "sent" || outcome === "drop";
79
93
  } catch {
80
94
  }
81
- if (success) {
95
+ if (done) {
82
96
  this.items.shift();
83
97
  retryIndex = 0;
84
98
  } else {
@@ -92,44 +106,77 @@ var LogQueue = class {
92
106
  };
93
107
 
94
108
  // src/scrubber.ts
109
+ var import_node_crypto = require("crypto");
110
+ var TOKEN_HEX_LEN = 12;
95
111
  var RULES = [
96
112
  ["email", /\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b/g, "EMAIL_REDACTED"],
113
+ // IP before phone: the phone rule would otherwise swallow dotted quads
97
114
  ["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"],
115
+ // IBAN: tolerate the usual 4-character grouping ("DE89 3704 0044 ...") so a spaced
116
+ // IBAN can't leak its digits to the phone rule
117
+ ["iban", /\b[A-Z]{2}\d{2}(?:[ ]?[A-Z0-9]){11,30}\b/g, "IBAN_REDACTED"],
118
+ // Card numbers before phone, so the whole grouped number (13–20 digits) is caught.
119
+ // Reported as id_number: the ingest API has no card category.
120
+ ["id_number", /\b\d{4}(?:[ -]?\d{4}){2,4}\b/g, "CARD_REDACTED"],
121
+ // (?<![\w+]) instead of \b so a leading "+" is part of the match, not left behind
122
+ ["phone", /(?<![\w+])\+?\d[\d\s\-().]{5,14}\d\b/g, "PHONE_REDACTED"],
100
123
  ["dob", /\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})\b/g, "DOB_REDACTED"],
101
124
  ["id_number", /\b[A-Z]{1,3}\d{6,9}\b/g, "ID_REDACTED"],
125
+ // Names last: a broad heuristic, applied only after a title
102
126
  ["name", /\b(?:Mr|Mrs|Ms|Dr|Prof)\.?\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\b/g, "NAME_REDACTED"]
103
127
  ];
104
- function scrub(text) {
128
+ function normalize(value) {
129
+ return value.replace(/[\s-]/g, "").toLowerCase();
130
+ }
131
+ var ScrubContext = class {
132
+ constructor(piiKey) {
133
+ this.piiKey = piiKey;
134
+ }
135
+ piiKey;
136
+ seen = /* @__PURE__ */ new Map();
137
+ next = /* @__PURE__ */ new Map();
138
+ placeholder(base, value) {
139
+ if (this.piiKey) {
140
+ const digest = (0, import_node_crypto.createHmac)("sha256", this.piiKey).update(normalize(value), "utf8").digest("hex");
141
+ return `[${base}_${digest.slice(0, TOKEN_HEX_LEN)}]`;
142
+ }
143
+ const key = `${base}\0${normalize(value)}`;
144
+ let n = this.seen.get(key);
145
+ if (n === void 0) {
146
+ n = (this.next.get(base) ?? 0) + 1;
147
+ this.next.set(base, n);
148
+ this.seen.set(key, n);
149
+ }
150
+ return `[${base}_${n}]`;
151
+ }
152
+ };
153
+ function scrub(text, context = new ScrubContext()) {
105
154
  if (!text) return { text, detections: [] };
106
155
  const detections = [];
107
- const counters = {};
108
156
  let result = text;
109
157
  for (const [category, pattern, basePlaceholder] of RULES) {
110
158
  pattern.lastIndex = 0;
111
- result = result.replace(pattern, () => {
112
- counters[category] = (counters[category] ?? 0) + 1;
113
- const placeholder = `[${basePlaceholder}_${counters[category]}]`;
159
+ result = result.replace(pattern, (match) => {
160
+ const placeholder = context.placeholder(basePlaceholder, match);
114
161
  detections.push({ category, placeholder });
115
162
  return placeholder;
116
163
  });
117
164
  }
118
165
  return { text: result, detections };
119
166
  }
120
- function scrubMessages(messages) {
167
+ function scrubMessages(messages, context = new ScrubContext()) {
121
168
  const allDetections = [];
122
169
  const scrubbed = [];
123
170
  for (const msg of messages) {
124
171
  const content = msg.content;
125
172
  if (typeof content === "string") {
126
- const result = scrub(content);
173
+ const result = scrub(content, context);
127
174
  scrubbed.push({ ...msg, content: result.text });
128
175
  allDetections.push(...result.detections);
129
176
  } else if (Array.isArray(content)) {
130
177
  const newBlocks = content.map((block) => {
131
178
  if (block.type === "text" && typeof block.text === "string") {
132
- const result = scrub(block.text);
179
+ const result = scrub(block.text, context);
133
180
  allDetections.push(...result.detections);
134
181
  return { ...block, text: result.text };
135
182
  }
@@ -161,9 +208,15 @@ function observe(result, onData) {
161
208
  };
162
209
  try {
163
210
  return new Proxy(result, {
164
- get(target, prop) {
211
+ get(target, prop, proxy) {
165
212
  const value = Reflect.get(target, prop, target);
166
213
  if (typeof value !== "function") return value;
214
+ if (prop === "catch" || prop === "finally") {
215
+ return (...args) => (
216
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
217
+ Promise.prototype[prop].apply(proxy, args)
218
+ );
219
+ }
167
220
  if (prop === "then") {
168
221
  return (onFulfilled, onRejected) => value.call(
169
222
  target,
@@ -194,96 +247,200 @@ function observe(result, onData) {
194
247
  function isStream(opts) {
195
248
  return opts?.stream === true;
196
249
  }
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(() => {
250
+ function safely(fn) {
251
+ try {
252
+ fn();
253
+ } catch {
254
+ }
255
+ }
256
+ function instrumentStream(stream, collector, done) {
257
+ if (!stream || typeof stream[Symbol.asyncIterator] !== "function") return;
258
+ const iterate = stream[Symbol.asyncIterator];
259
+ let finished = false;
260
+ const finish = () => {
261
+ if (finished) return;
262
+ finished = true;
263
+ safely(() => {
264
+ const response = collector.response();
265
+ if (response) done(response);
266
+ });
267
+ };
268
+ stream[Symbol.asyncIterator] = function() {
269
+ const iterator = iterate.call(this);
270
+ return {
271
+ async next(...args) {
272
+ let result;
209
273
  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 {
274
+ result = await iterator.next(...args);
275
+ } catch (err) {
276
+ finish();
277
+ throw err;
229
278
  }
230
- });
231
- });
279
+ if (result.done) finish();
280
+ else safely(() => collector.add(result.value));
281
+ return result;
282
+ },
283
+ // `break` out of a for await loop lands here.
284
+ async return(value) {
285
+ finish();
286
+ return typeof iterator.return === "function" ? iterator.return(value) : { done: true, value };
287
+ },
288
+ async throw(err) {
289
+ finish();
290
+ if (typeof iterator.throw === "function") return iterator.throw(err);
291
+ throw err;
292
+ },
293
+ [Symbol.asyncIterator]() {
294
+ return this;
295
+ }
296
+ };
297
+ };
298
+ }
299
+ function openAIStreamCollector() {
300
+ let seen = false;
301
+ let model;
302
+ let finishReason = null;
303
+ let usage;
304
+ const parts = [];
305
+ return {
306
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
307
+ add(chunk) {
308
+ seen = true;
309
+ model = chunk?.model ?? model;
310
+ usage = chunk?.usage ?? usage;
311
+ for (const choice of chunk?.choices ?? []) {
312
+ if ((choice?.index ?? 0) !== 0) continue;
313
+ if (choice?.delta?.content) parts.push(choice.delta.content);
314
+ finishReason = choice?.finish_reason ?? finishReason;
315
+ }
316
+ },
317
+ response() {
318
+ if (!seen) return null;
319
+ return { model, choices: [{ message: { content: parts.join("") }, finish_reason: finishReason }], usage };
320
+ }
321
+ };
322
+ }
323
+ function anthropicStreamCollector() {
324
+ let seen = false;
325
+ let model;
326
+ let stopReason = null;
327
+ let inputTokens = 0;
328
+ let outputTokens = 0;
329
+ const parts = [];
330
+ return {
331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
332
+ add(event) {
333
+ seen = true;
334
+ if (event?.type === "message_start") {
335
+ model = event.message?.model;
336
+ inputTokens = event.message?.usage?.input_tokens ?? 0;
337
+ outputTokens = event.message?.usage?.output_tokens ?? 0;
338
+ } else if (event?.type === "content_block_delta" && event.delta?.type === "text_delta") {
339
+ parts.push(event.delta.text);
340
+ } else if (event?.type === "message_delta") {
341
+ stopReason = event.delta?.stop_reason ?? stopReason;
342
+ outputTokens = event.usage?.output_tokens ?? outputTokens;
343
+ inputTokens = event.usage?.input_tokens ?? inputTokens;
344
+ }
345
+ },
346
+ response() {
347
+ if (!seen) return null;
348
+ return {
349
+ model,
350
+ content: [{ type: "text", text: parts.join("") }],
351
+ stop_reason: stopReason,
352
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens }
353
+ };
354
+ }
232
355
  };
233
- completions[PATCHED] = true;
234
356
  }
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) => {
357
+ function wrapCreate(target, enqueue, build, collector, piiKey) {
358
+ if (target[PATCHED]) return;
359
+ const original = target.create.bind(target);
360
+ target.create = (opts, ...rest) => {
240
361
  const requestAt = utcnow();
241
362
  const t0 = performance.now();
242
363
  const result = original(opts, ...rest);
243
- if (isStream(opts)) return result;
244
- return observe(result, (response) => {
364
+ const done = (response) => {
245
365
  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
- });
366
+ setImmediate(() => safely(() => enqueue(build(opts, response, requestAt, latencyMs, piiKey))));
367
+ };
368
+ if (isStream(opts)) return observe(result, (stream) => instrumentStream(stream, collector(), done));
369
+ return observe(result, done);
271
370
  };
272
- messagesApi[PATCHED] = true;
371
+ target[PATCHED] = true;
372
+ }
373
+ function buildOpenAIPayload(opts, response, requestAt, latencyMs, piiKey) {
374
+ const messages = opts?.messages ?? [];
375
+ const model = opts?.model ?? "";
376
+ const context = new ScrubContext(piiKey);
377
+ const [scrubbedMessages, promptDetections] = scrubMessages(messages, context);
378
+ const choice = response?.choices?.[0];
379
+ const responseText = choice?.message?.content ?? "";
380
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
381
+ const scrubbedResponse = scrubbedResult?.text ?? "";
382
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
383
+ const usage = response?.usage;
384
+ return {
385
+ model: response?.model ?? model,
386
+ messages: scrubbedMessages,
387
+ response: scrubbedResponse,
388
+ prompt_tokens: usage?.prompt_tokens ?? 0,
389
+ completion_tokens: usage?.completion_tokens ?? 0,
390
+ latency_ms: latencyMs,
391
+ timestamp: requestAt,
392
+ finish_reason: choice?.finish_reason ?? null,
393
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
394
+ };
395
+ }
396
+ function buildAnthropicPayload(opts, response, requestAt, latencyMs, piiKey) {
397
+ const messages = opts?.messages ?? [];
398
+ const system = opts?.system;
399
+ const model = opts?.model ?? "";
400
+ const allMessages = system ? [{ role: "system", content: system }, ...messages] : messages;
401
+ const context = new ScrubContext(piiKey);
402
+ const [scrubbedMessages, promptDetections] = scrubMessages(allMessages, context);
403
+ const responseText = (response?.content ?? []).filter((b) => b.type === "text").map((b) => b.text).join("");
404
+ const scrubbedResult = responseText ? scrub(responseText, context) : null;
405
+ const scrubbedResponse = scrubbedResult?.text ?? "";
406
+ const detections = [...promptDetections, ...scrubbedResult?.detections ?? []];
407
+ const usage = response?.usage;
408
+ return {
409
+ model: response?.model ?? model,
410
+ messages: scrubbedMessages,
411
+ response: scrubbedResponse,
412
+ prompt_tokens: usage?.input_tokens ?? 0,
413
+ completion_tokens: usage?.output_tokens ?? 0,
414
+ latency_ms: latencyMs,
415
+ timestamp: requestAt,
416
+ finish_reason: response?.stop_reason ?? null,
417
+ pii_detections: detections.map((d) => ({ category: d.category, placeholder: d.placeholder }))
418
+ };
419
+ }
420
+ function patchOpenAI(client, enqueue, piiKey) {
421
+ wrapCreate(client.chat.completions, enqueue, buildOpenAIPayload, openAIStreamCollector, piiKey);
422
+ }
423
+ function patchAnthropic(client, enqueue, piiKey) {
424
+ wrapCreate(client.messages, enqueue, buildAnthropicPayload, anthropicStreamCollector, piiKey);
273
425
  }
274
426
 
275
427
  // src/client.ts
276
428
  var state = {
277
429
  apiKey: null,
278
430
  baseUrl: "https://api.sovergate.com",
431
+ piiKey: void 0,
279
432
  queue: null
280
433
  };
281
- function init({ apiKey, baseUrl = "https://api.sovergate.com", debug = false }) {
434
+ function init({ apiKey, baseUrl = "https://api.sovergate.com", piiKey, debug = false }) {
282
435
  try {
283
436
  state.apiKey = apiKey;
284
437
  state.baseUrl = baseUrl.replace(/\/$/, "");
438
+ state.piiKey = piiKey || void 0;
285
439
  if (debug) {
286
440
  console.debug("[sovergate] init baseUrl=%s", state.baseUrl);
441
+ if (!state.piiKey) {
442
+ console.debug("[sovergate] no piiKey set: PII placeholders are numbered per record and not correlatable across logs");
443
+ }
287
444
  }
288
445
  state.queue = new LogQueue((payload) => send(apiKey, payload, state.baseUrl));
289
446
  } catch (err) {
@@ -299,11 +456,11 @@ function instrument(client) {
299
456
  try {
300
457
  const enqueue = state.queue.enqueue.bind(state.queue);
301
458
  if (client?.chat?.completions) {
302
- patchOpenAI(client, enqueue);
459
+ patchOpenAI(client, enqueue, state.piiKey);
303
460
  return;
304
461
  }
305
462
  if (client?.messages) {
306
- patchAnthropic(client, enqueue);
463
+ patchAnthropic(client, enqueue, state.piiKey);
307
464
  return;
308
465
  }
309
466
  warn(
@@ -321,6 +478,7 @@ function warn(message, err) {
321
478
  }
322
479
  function shutdown() {
323
480
  state.queue = null;
481
+ state.piiKey = void 0;
324
482
  }
325
483
  // Annotate the CommonJS export names for ESM import in node:
326
484
  0 && (module.exports = {
package/dist/index.js.map CHANGED
@@ -1 +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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,IAAM,WAAW;AACjB,IAAM,aAAa;AAEnB,eAAsB,KACpB,QACA,SACA,UAAU,UACQ;AAClB,MAAI;AACF,UAAM,aAAa,IAAI,gBAAgB;AACvC,UAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,UAAU;AAE7D,UAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW;AAAA,MAChD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe,UAAU,MAAM;AAAA,QAC/B,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,QAAQ,WAAW;AAAA,IACrB,CAAC;AAED,iBAAa,KAAK;AAClB,WAAO,SAAS,SAAS;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACxBA,IAAM,YAAY;AAClB,IAAM,iBAAiB,CAAC,GAAG,GAAG,GAAG,GAAG,IAAI,IAAI,EAAE;AAE9C,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAEO,IAAM,WAAN,MAAe;AAAA,EACH;AAAA,EACA,QAAmB,CAAC;AAAA,EAC7B,WAAW;AAAA,EAEnB,YAAY,QAAgB;AAC1B,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,QAAQ,SAAwB;AAC9B,QAAI,KAAK,MAAM,UAAU,UAAW;AACpC,SAAK,MAAM,KAAK,OAAO;AACvB,QAAI,CAAC,KAAK,UAAU;AAClB,mBAAa,MAAM,KAAK,KAAK,MAAM,CAAC;AAAA,IACtC;AAAA,EACF;AAAA,EAEA,MAAc,QAAuB;AACnC,SAAK,WAAW;AAChB,QAAI,aAAa;AAEjB,WAAO,KAAK,MAAM,SAAS,GAAG;AAC5B,UAAI,UAAU;AACd,UAAI;AACF,kBAAU,MAAM,KAAK,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,MAC3C,QAAQ;AAAA,MAER;AAEA,UAAI,SAAS;AACX,aAAK,MAAM,MAAM;AACjB,qBAAa;AAAA,MACf,OAAO;AACL,cAAM,WAAW,eAAe,KAAK,IAAI,YAAY,eAAe,SAAS,CAAC,CAAC;AAC/E;AACA,cAAM,MAAM,WAAW,GAAI;AAAA,MAC7B;AAAA,IACF;AAEA,SAAK,WAAW;AAAA,EAClB;AACF;;;ACpCA,IAAM,QAAyC;AAAA,EAC7C,CAAC,SAAa,yDAAsF,gBAAgB;AAAA,EACpH,CAAC,MAAa,gCAAuF,aAAa;AAAA,EAClH,CAAC,QAAa,oCAAuF,eAAe;AAAA,EACpH,CAAC,SAAa,sCAAuF,gBAAgB;AAAA,EACrH,CAAC,OAAa,kFAAoF,cAAc;AAAA,EAChH,CAAC,aAAa,0BAAuF,aAAa;AAAA,EAClH,CAAC,QAAa,gEAAsF,eAAe;AACrH;AAEO,SAAS,MAAM,MAA2B;AAC/C,MAAI,CAAC,KAAM,QAAO,EAAE,MAAM,YAAY,CAAC,EAAE;AAEzC,QAAM,aAA0B,CAAC;AACjC,QAAM,WAAmC,CAAC;AAE1C,MAAI,SAAS;AACb,aAAW,CAAC,UAAU,SAAS,eAAe,KAAK,OAAO;AACxD,YAAQ,YAAY;AACpB,aAAS,OAAO,QAAQ,SAAS,MAAM;AACrC,eAAS,QAAQ,KAAK,SAAS,QAAQ,KAAK,KAAK;AACjD,YAAM,cAAc,IAAI,eAAe,IAAI,SAAS,QAAQ,CAAC;AAC7D,iBAAW,KAAK,EAAE,UAAU,YAAY,CAAC;AACzC,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,MAAM,QAAQ,WAAW;AACpC;AAEO,SAAS,cAAc,UAA2D;AACvF,QAAM,gBAA6B,CAAC;AACpC,QAAM,WAA4B,CAAC;AAEnC,aAAW,OAAO,UAAU;AAC1B,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,YAAY,UAAU;AAC/B,YAAM,SAAS,MAAM,OAAO;AAC5B,eAAS,KAAK,EAAE,GAAG,KAAK,SAAS,OAAO,KAAK,CAAC;AAC9C,oBAAc,KAAK,GAAG,OAAO,UAAU;AAAA,IACzC,WAAW,MAAM,QAAQ,OAAO,GAAG;AACjC,YAAM,YAAY,QAAQ,IAAI,WAAS;AACrC,YAAI,MAAM,SAAS,UAAU,OAAO,MAAM,SAAS,UAAU;AAC3D,gBAAM,SAAS,MAAM,MAAM,IAAI;AAC/B,wBAAc,KAAK,GAAG,OAAO,UAAU;AACvC,iBAAO,EAAE,GAAG,OAAO,MAAM,OAAO,KAAK;AAAA,QACvC;AACA,eAAO;AAAA,MACT,CAAC;AACD,eAAS,KAAK,EAAE,GAAG,KAAK,SAAS,UAAU,CAAC;AAAA,IAC9C,OAAO;AACL,eAAS,KAAK,GAAG;AAAA,IACnB;AAAA,EACF;AAEA,SAAO,CAAC,UAAU,aAAa;AACjC;;;ACnEA,IAAM,UAAU,uBAAO,IAAI,mBAAmB;AAE9C,SAAS,SAAiB;AACxB,UAAO,oBAAI,KAAK,GAAE,YAAY;AAChC;AAaA,SAAS,QAAQ,QAAa,QAAkC;AAC9D,MAAI,CAAC,UAAU,OAAO,OAAO,SAAS,WAAY,QAAO;AAEzD,MAAI,OAAO;AAEX,QAAM,SAAS,CAAC,SAAoB;AAClC,QAAI,KAAM;AACV,WAAO;AACP,QAAI;AACF,aAAO,IAAI;AAAA,IACb,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,MAAI;AACF,WAAO,IAAI,MAAM,QAAQ;AAAA,MACvB,IAAI,QAAQ,MAAM;AAChB,cAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,MAAM;AAC9C,YAAI,OAAO,UAAU,WAAY,QAAO;AAExC,YAAI,SAAS,QAAQ;AAEnB,iBAAO,CAAC,aAAmB,eACzB,MAAM;AAAA,YACJ;AAAA;AAAA,YAEA,CAAC,SAAc;AACb,qBAAO,IAAI;AACX,qBAAO,OAAO,gBAAgB,aAAa,YAAY,IAAI,IAAI;AAAA,YACjE;AAAA,YACA;AAAA,UACF;AAAA,QACJ;AAEA,YAAI,SAAS,gBAAgB;AAC3B,iBAAO,IAAI;AAAA;AAAA,YAET,MAAM,MAAM,QAAQ,IAAI,EAAE,KAAK,CAAC,iBAAsB;AACpD,qBAAO,cAAc,IAAI;AACzB,qBAAO;AAAA,YACT,CAAC;AAAA;AAAA,QACL;AAEA,eAAO,MAAM,KAAK,MAAM;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,SAAS,MAAoB;AACpC,SAAO,MAAM,WAAW;AAC1B;AAGO,SAAS,YAAY,QAAa,SAAwB;AAC/D,QAAM,cAAc,OAAO,KAAK;AAChC,MAAI,YAAY,OAAO,EAAG;AAC1B,QAAM,WAAW,YAAY,OAAO,KAAK,WAAW;AAGpD,cAAY,SAAS,CAAC,SAAc,SAAoB;AACtD,UAAM,YAAY,OAAO;AACzB,UAAM,KAAK,YAAY,IAAI;AAE3B,UAAM,SAAS,SAAS,MAAM,GAAG,IAAI;AAGrC,QAAI,SAAS,IAAI,EAAG,QAAO;AAG3B,WAAO,QAAQ,QAAQ,CAAC,aAAkB;AACxC,YAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,EAAE;AAEnD,mBAAa,MAAM;AACjB,YAAI;AACF,gBAAM,WAAW,MAAM,YAAY,CAAC;AACpC,gBAAM,QAAQ,MAAM,SAAS;AAC7B,gBAAM,CAAC,kBAAkB,UAAU,IAAI,cAAc,QAAQ;AAC7D,gBAAM,SAAS,UAAU,UAAU,CAAC;AACpC,gBAAM,eAAuB,QAAQ,SAAS,WAAW;AACzD,gBAAM,mBAAmB,eAAe,MAAM,YAAY,EAAE,OAAO;AACnE,gBAAM,QAAQ,UAAU;AAExB,kBAAQ;AAAA,YACN,OAAO,UAAU,SAAS;AAAA,YAC1B,UAAU;AAAA,YACV,UAAU;AAAA,YACV,eAAe,OAAO,iBAAiB;AAAA,YACvC,mBAAmB,OAAO,qBAAqB;AAAA,YAC/C,YAAY;AAAA,YACZ,WAAW;AAAA,YACX,eAAe,QAAQ,iBAAiB;AAAA,YACxC,gBAAgB,WAAW,IAAI,QAAM,EAAE,UAAU,EAAE,UAAU,aAAa,EAAE,YAAY,EAAE;AAAA,UAC5F,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACA,cAAY,OAAO,IAAI;AACzB;AAGO,SAAS,eAAe,QAAa,SAAwB;AAClE,QAAM,cAAc,OAAO;AAC3B,MAAI,YAAY,OAAO,EAAG;AAC1B,QAAM,WAAW,YAAY,OAAO,KAAK,WAAW;AAGpD,cAAY,SAAS,CAAC,SAAc,SAAoB;AACtD,UAAM,YAAY,OAAO;AACzB,UAAM,KAAK,YAAY,IAAI;AAE3B,UAAM,SAAS,SAAS,MAAM,GAAG,IAAI;AAGrC,QAAI,SAAS,IAAI,EAAG,QAAO;AAG3B,WAAO,QAAQ,QAAQ,CAAC,aAAkB;AACxC,YAAM,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,EAAE;AAEnD,mBAAa,MAAM;AACjB,YAAI;AACF,gBAAM,WAAW,MAAM,YAAY,CAAC;AACpC,gBAAM,SAAS,MAAM;AACrB,gBAAM,QAAQ,MAAM,SAAS;AAE7B,gBAAM,cAAc,SAChB,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,GAAG,GAAG,QAAQ,IACjD;AAEJ,gBAAM,CAAC,kBAAkB,UAAU,IAAI,cAAc,WAAW;AAEhE,gBAAM,gBAAwB,UAAU,WAAW,CAAC,GAEjD,OAAO,CAAC,MAAW,EAAE,SAAS,MAAM,EAEpC,IAAI,CAAC,MAAW,EAAE,IAAc,EAChC,KAAK,EAAE;AAEV,gBAAM,mBAAmB,eAAe,MAAM,YAAY,EAAE,OAAO;AACnE,gBAAM,QAAQ,UAAU;AAExB,kBAAQ;AAAA,YACN,OAAO,UAAU,SAAS;AAAA,YAC1B,UAAU;AAAA,YACV,UAAU;AAAA,YACV,eAAe,OAAO,gBAAgB;AAAA,YACtC,mBAAmB,OAAO,iBAAiB;AAAA,YAC3C,YAAY;AAAA,YACZ,WAAW;AAAA,YACX,eAAe,UAAU,eAAe;AAAA,YACxC,gBAAgB,WAAW,IAAI,QAAM,EAAE,UAAU,EAAE,UAAU,aAAa,EAAE,YAAY,EAAE;AAAA,UAC5F,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACA,cAAY,OAAO,IAAI;AACzB;;;AClLA,IAAM,QAIF;AAAA,EACF,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,OAAO;AACT;AAEO,SAAS,KAAK,EAAE,QAAQ,UAAU,6BAA6B,QAAQ,MAAM,GAAsB;AACxG,MAAI;AACF,UAAM,SAAS;AACf,UAAM,UAAU,QAAQ,QAAQ,OAAO,EAAE;AAEzC,QAAI,OAAO;AACT,cAAQ,MAAM,+BAA+B,MAAM,OAAO;AAAA,IAC5D;AAEA,UAAM,QAAQ,IAAI,SAAS,aAAW,KAAK,QAAQ,SAAS,MAAM,OAAO,CAAC;AAAA,EAC5E,SAAS,KAAK;AAEZ,UAAM,QAAQ;AACd,SAAK,iCAAiC,GAAG;AAAA,EAC3C;AACF;AAIO,SAAS,WAAW,QAAmB;AAC5C,MAAI,CAAC,MAAM,OAAO;AAChB,SAAK,iFAA4E;AACjF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,MAAM,QAAQ,KAAK,MAAM,KAAK;AAEpD,QAAI,QAAQ,MAAM,aAAa;AAC7B,kBAAY,QAAQ,OAAO;AAC3B;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,qBAAe,QAAQ,OAAO;AAC9B;AAAA,IACF;AAEA;AAAA,MACE;AAAA,IAEF;AAAA,EACF,SAAS,KAAK;AACZ,SAAK,qDAAqD,GAAG;AAAA,EAC/D;AACF;AAEA,SAAS,KAAK,SAAiB,KAAqB;AAClD,MAAI;AACF,YAAQ,KAAK,eAAe,OAAO,IAAI,GAAI,QAAQ,SAAY,CAAC,IAAI,CAAC,GAAG,CAAE;AAAA,EAC5E,QAAQ;AAAA,EAER;AACF;AAEO,SAAS,WAAiB;AAC/B,QAAM,QAAQ;AAChB;","names":[]}
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 let timer: ReturnType<typeof setTimeout> | undefined\n try {\n const controller = new AbortController()\n timer = setTimeout(() => controller.abort(), TIMEOUT_MS)\n timer.unref?.()\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 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 } finally {\n clearTimeout(timer)\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\n// unref(): a pending retry must never keep the host process alive. Records still\n// queued when the app exits are lost, as documented.\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => {\n const timer = setTimeout(resolve, ms)\n timer.unref?.()\n })\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","import { createHmac } from 'node:crypto'\n\nexport 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// Hex chars of the HMAC digest shown in a token. 12 hex = 48 bits: enough to keep\n// collisions negligible at realistic volumes while staying readable.\nconst TOKEN_HEX_LEN = 12\n\n// Each rule: [category, regex (must have g flag), placeholder base].\n// Kept identical to the Python SDK, so both produce the same tokens for the same key.\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 before phone: the phone rule would otherwise swallow dotted quads\n ['ip', /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g, 'IP_REDACTED'],\n // IBAN: tolerate the usual 4-character grouping (\"DE89 3704 0044 ...\") so a spaced\n // IBAN can't leak its digits to the phone rule\n ['iban', /\\b[A-Z]{2}\\d{2}(?:[ ]?[A-Z0-9]){11,30}\\b/g, 'IBAN_REDACTED'],\n // Card numbers before phone, so the whole grouped number (13–20 digits) is caught.\n // Reported as id_number: the ingest API has no card category.\n ['id_number', /\\b\\d{4}(?:[ -]?\\d{4}){2,4}\\b/g, 'CARD_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 // Names last: a broad heuristic, applied only after a title\n ['name', /\\b(?:Mr|Mrs|Ms|Dr|Prof)\\.?\\s+[A-Z][a-z]+(?: [A-Z][a-z]+)*\\b/g, 'NAME_REDACTED'],\n]\n\n/** Folds formatting differences (spaced IBAN, email case) so one value gets one placeholder. */\nfunction normalize(value: string): string {\n return value.replace(/[\\s-]/g, '').toLowerCase()\n}\n\n/**\n * Chooses the placeholder for each detected value in one record (all messages and\n * the response).\n *\n * With a `piiKey`, each value becomes a deterministic, irreversible HMAC token:\n * the same value gets the same token in every log, so Sovergate can count unique\n * people without ever holding the value. The key never leaves your infrastructure.\n *\n * Without a key, placeholders are numbered within the record: the same value\n * always gets the same number and two values never share one, but numbers can't\n * be matched across records.\n */\nexport class ScrubContext {\n private readonly seen = new Map<string, number>()\n private readonly next = new Map<string, number>()\n\n constructor(private readonly piiKey?: string) {}\n\n placeholder(base: string, value: string): string {\n if (this.piiKey) {\n const digest = createHmac('sha256', this.piiKey).update(normalize(value), 'utf8').digest('hex')\n return `[${base}_${digest.slice(0, TOKEN_HEX_LEN)}]`\n }\n\n const key = `${base}\\u0000${normalize(value)}`\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, proxy) {\n const value = Reflect.get(target, prop, target)\n if (typeof value !== 'function') return value\n\n // The native .catch() and .finally() would call the target's own then(),\n // skipping the hook below; route them through the proxy's then().\n if (prop === 'catch' || prop === 'finally') {\n return (...args: unknown[]) =>\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (Promise.prototype[prop] as (...a: unknown[]) => Promise<any>).apply(proxy, args)\n }\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\nfunction safely(fn: () => void): void {\n try {\n fn()\n } catch {\n // never let logging errors surface to the caller\n }\n}\n\n/** Rebuilds, from a stream's chunks, the parts of the full response that are logged. */\ninterface StreamCollector {\n add(chunk: unknown): void\n /** The response as a non-streamed call would have returned it, or null if nothing arrived. */\n response(): unknown\n}\n\n/**\n * Logs a provider stream once it ends, is abandoned part-way, or fails. Only the\n * stream object's own async iterator is replaced: the app gets the same object\n * (its class, `.controller`, `.tee()` and `.toReadableStream()` keep working) and\n * every chunk unchanged as it arrives. A copy of the text is collected on the side.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction instrumentStream(stream: any, collector: StreamCollector, done: (response: unknown) => void): void {\n if (!stream || typeof stream[Symbol.asyncIterator] !== 'function') return\n const iterate = stream[Symbol.asyncIterator]\n\n let finished = false\n const finish = (): void => {\n if (finished) return\n finished = true\n safely(() => {\n const response = collector.response()\n if (response) done(response)\n })\n }\n\n stream[Symbol.asyncIterator] = function () {\n const iterator = iterate.call(this)\n return {\n async next(...args: unknown[]) {\n let result\n try {\n result = await iterator.next(...args)\n } catch (err) {\n finish()\n throw err\n }\n if (result.done) finish()\n else safely(() => collector.add(result.value))\n return result\n },\n // `break` out of a for await loop lands here.\n async return(value?: unknown) {\n finish()\n return typeof iterator.return === 'function' ? iterator.return(value) : { done: true, value }\n },\n async throw(err?: unknown) {\n finish()\n if (typeof iterator.throw === 'function') return iterator.throw(err)\n throw err\n },\n [Symbol.asyncIterator]() {\n return this\n },\n }\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction openAIStreamCollector(): StreamCollector {\n let seen = false\n let model: string | undefined\n let finishReason: string | null = null\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let usage: any\n const parts: string[] = []\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n add(chunk: any) {\n seen = true\n model = chunk?.model ?? model\n // Only sent when the app asks for it with stream_options: { include_usage: true }.\n usage = chunk?.usage ?? usage\n for (const choice of chunk?.choices ?? []) {\n if ((choice?.index ?? 0) !== 0) continue\n if (choice?.delta?.content) parts.push(choice.delta.content)\n finishReason = choice?.finish_reason ?? finishReason\n }\n },\n response() {\n if (!seen) return null\n return { model, choices: [{ message: { content: parts.join('') }, finish_reason: finishReason }], usage }\n },\n }\n}\n\nfunction anthropicStreamCollector(): StreamCollector {\n let seen = false\n let model: string | undefined\n let stopReason: string | null = null\n let inputTokens = 0\n let outputTokens = 0\n const parts: string[] = []\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n add(event: any) {\n seen = true\n if (event?.type === 'message_start') {\n model = event.message?.model\n inputTokens = event.message?.usage?.input_tokens ?? 0\n outputTokens = event.message?.usage?.output_tokens ?? 0\n } else if (event?.type === 'content_block_delta' && event.delta?.type === 'text_delta') {\n parts.push(event.delta.text)\n } else if (event?.type === 'message_delta') {\n stopReason = event.delta?.stop_reason ?? stopReason\n outputTokens = event.usage?.output_tokens ?? outputTokens\n inputTokens = event.usage?.input_tokens ?? inputTokens\n }\n },\n response() {\n if (!seen) return null\n return {\n model,\n content: [{ type: 'text', text: parts.join('') }],\n stop_reason: stopReason,\n usage: { input_tokens: inputTokens, output_tokens: outputTokens },\n }\n },\n }\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype BuildPayload = (opts: any, response: any, requestAt: string, latencyMs: number, piiKey?: string) => Record<string, unknown>\n\n/**\n * Wraps `target.create`. Plain calls are logged when the app reads the result;\n * streamed calls when the stream ends, so their latency covers the whole response.\n * The provider SDKs' own `.stream()` helpers call `create({ stream: true })`, so\n * they are covered too.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction wrapCreate(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n target: any,\n enqueue: Enqueue,\n build: BuildPayload,\n collector: () => StreamCollector,\n piiKey?: string,\n): void {\n if (target[PATCHED]) return\n const original = target.create.bind(target)\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n target.create = (opts: any, ...rest: unknown[]) => {\n const requestAt = utcnow()\n const t0 = performance.now()\n\n const result = original(opts, ...rest)\n\n const done = (response: unknown): void => {\n const latencyMs = Math.round(performance.now() - t0)\n setImmediate(() => safely(() => enqueue(build(opts, response, requestAt, latencyMs, piiKey))))\n }\n\n if (isStream(opts)) return observe(result, (stream: unknown) => instrumentStream(stream, collector(), done))\n return observe(result, done)\n }\n target[PATCHED] = true\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildOpenAIPayload(opts: any, response: any, requestAt: string, latencyMs: number, piiKey?: string) {\n const messages = opts?.messages ?? []\n const model = opts?.model ?? ''\n const context = new ScrubContext(piiKey)\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 return {\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}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction buildAnthropicPayload(opts: any, response: any, requestAt: string, latencyMs: number, piiKey?: string) {\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(piiKey)\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 return {\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}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchOpenAI(client: any, enqueue: Enqueue, piiKey?: string): void {\n wrapCreate(client.chat.completions, enqueue, buildOpenAIPayload, openAIStreamCollector, piiKey)\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function patchAnthropic(client: any, enqueue: Enqueue, piiKey?: string): void {\n wrapCreate(client.messages, enqueue, buildAnthropicPayload, anthropicStreamCollector, piiKey)\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 /**\n * Secret used to turn detected personal data into stable, irreversible tokens.\n * The same value always maps to the same token, so Sovergate can count unique\n * people without holding the data. Keep it secret and stable: it never leaves\n * your infrastructure. Without it, placeholders are numbered per record.\n */\n piiKey?: string\n debug?: boolean\n}\n\nconst state: {\n apiKey: string | null\n baseUrl: string\n piiKey: string | undefined\n queue: LogQueue | null\n} = {\n apiKey: null,\n baseUrl: 'https://api.sovergate.com',\n piiKey: undefined,\n queue: null,\n}\n\nexport function init({ apiKey, baseUrl = 'https://api.sovergate.com', piiKey, debug = false }: InitOptions): void {\n try {\n state.apiKey = apiKey\n state.baseUrl = baseUrl.replace(/\\/$/, '')\n state.piiKey = piiKey || undefined\n\n if (debug) {\n console.debug('[sovergate] init baseUrl=%s', state.baseUrl)\n if (!state.piiKey) {\n console.debug('[sovergate] no piiKey set: PII placeholders are numbered per record and not correlatable across logs')\n }\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, state.piiKey)\n return\n }\n\n if (client?.messages) {\n patchAnthropic(client, enqueue, state.piiKey)\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 state.piiKey = undefined\n}\n"],"mappings":"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","names":[]}