@observyze/sdk 0.1.2 → 0.1.4

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.mjs CHANGED
@@ -3,24 +3,43 @@ import {
3
3
  Span,
4
4
  SpanType,
5
5
  Trace,
6
- TraceStatus,
7
- __esm,
8
- __export,
9
- __require,
10
- __toCommonJS,
11
- init_types
12
- } from "./chunk-YRMQCX2P.mjs";
6
+ TraceStatus
7
+ } from "./chunk-FQBYUOJB.mjs";
8
+
9
+ // src/client.ts
10
+ import debug3 from "debug";
13
11
 
14
12
  // src/instrumentation/openai.ts
13
+ import debug from "debug";
14
+ var log = debug("observyze:sdk");
15
15
  function wrapOpenAI(client, nwClient) {
16
16
  const anyClient = client;
17
+ if (nwClient.getConfig().enableProxyRedirect && anyClient.baseURL && anyClient.apiKey) {
18
+ const isAlreadyRedirected = anyClient.baseURL.includes("/api/v1/proxy/openai");
19
+ if (!isAlreadyRedirected) {
20
+ const originalApiKey = anyClient.apiKey;
21
+ anyClient.baseURL = `${nwClient.getConfig().endpoint}/api/v1/proxy/openai/v1`;
22
+ anyClient.apiKey = nwClient.getConfig().apiKey;
23
+ anyClient.defaultHeaders = {
24
+ ...anyClient.defaultHeaders,
25
+ "x-provider-key": originalApiKey
26
+ };
27
+ if (nwClient.getConfig().debug) {
28
+ log("[Observyze SDK] Transparently redirected OpenAI client to proxy gateway:", anyClient.baseURL);
29
+ }
30
+ }
31
+ }
17
32
  const originalCreate = client.chat.completions.create.bind(client.chat.completions);
18
33
  client.chat.completions.create = async function(params, options) {
34
+ const isProxyRedirected = nwClient.getConfig().enableProxyRedirect && anyClient.baseURL?.includes("/api/v1/proxy/openai");
35
+ if (isProxyRedirected) {
36
+ return originalCreate(params, options);
37
+ }
19
38
  const trace = nwClient.startTrace(`openai.chat.completions.create`, {
20
39
  provider: "openai",
21
40
  model: params.model
22
41
  });
23
- const span = trace.startSpan("chat.completions.create", "llm" /* LLM */);
42
+ const span = trace.startSpan("chat.completions.create", SpanType.LLM);
24
43
  span.setMetadata("model", params.model);
25
44
  span.setMetadata("provider", "openai");
26
45
  if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
@@ -104,23 +123,38 @@ function wrapOpenAIStream(stream, span, trace, startTime) {
104
123
  }
105
124
  };
106
125
  }
107
- var init_openai = __esm({
108
- "src/instrumentation/openai.ts"() {
109
- "use strict";
110
- init_types();
111
- }
112
- });
113
126
 
114
127
  // src/instrumentation/anthropic.ts
128
+ import debug2 from "debug";
129
+ var log2 = debug2("observyze:sdk");
115
130
  function wrapAnthropic(client, nwClient) {
116
131
  const anyClient = client;
132
+ if (nwClient.getConfig().enableProxyRedirect && anyClient.baseURL && anyClient.apiKey) {
133
+ const isAlreadyRedirected = anyClient.baseURL.includes("/api/v1/proxy/anthropic");
134
+ if (!isAlreadyRedirected) {
135
+ const originalApiKey = anyClient.apiKey;
136
+ anyClient.baseURL = `${nwClient.getConfig().endpoint}/api/v1/proxy/anthropic/v1`;
137
+ anyClient.apiKey = nwClient.getConfig().apiKey;
138
+ anyClient.defaultHeaders = {
139
+ ...anyClient.defaultHeaders,
140
+ "x-provider-key": originalApiKey
141
+ };
142
+ if (nwClient.getConfig().debug) {
143
+ log2("[Observyze SDK] Transparently redirected Anthropic client to proxy gateway:", anyClient.baseURL);
144
+ }
145
+ }
146
+ }
117
147
  const originalCreate = client.messages.create.bind(client.messages);
118
148
  client.messages.create = async function(params, options) {
149
+ const isProxyRedirected = nwClient.getConfig().enableProxyRedirect && anyClient.baseURL?.includes("/api/v1/proxy/anthropic");
150
+ if (isProxyRedirected) {
151
+ return originalCreate(params, options);
152
+ }
119
153
  const trace = nwClient.startTrace(`anthropic.messages.create`, {
120
154
  provider: "anthropic",
121
155
  model: params.model
122
156
  });
123
- const span = trace.startSpan("messages.create", "llm" /* LLM */);
157
+ const span = trace.startSpan("messages.create", SpanType.LLM);
124
158
  span.setMetadata("model", params.model);
125
159
  span.setMetadata("provider", "anthropic");
126
160
  if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
@@ -231,20 +265,8 @@ function wrapAnthropicStream(stream, span, trace, startTime) {
231
265
  }
232
266
  };
233
267
  }
234
- var init_anthropic = __esm({
235
- "src/instrumentation/anthropic.ts"() {
236
- "use strict";
237
- init_types();
238
- }
239
- });
240
268
 
241
269
  // src/instrumentation/index.ts
242
- var instrumentation_exports = {};
243
- __export(instrumentation_exports, {
244
- wrap: () => wrap,
245
- wrapAnthropic: () => wrapAnthropic,
246
- wrapOpenAI: () => wrapOpenAI
247
- });
248
270
  function wrap(client, nwClient) {
249
271
  if ("chat" in client && client.chat && "completions" in client.chat) {
250
272
  return wrapOpenAI(client, nwClient);
@@ -256,26 +278,26 @@ function wrap(client, nwClient) {
256
278
  "Observyze SDK: Unsupported client type. Supported clients: OpenAI, Anthropic"
257
279
  );
258
280
  }
259
- var init_instrumentation = __esm({
260
- "src/instrumentation/index.ts"() {
261
- "use strict";
262
- init_openai();
263
- init_anthropic();
264
- init_openai();
265
- init_anthropic();
266
- }
267
- });
268
281
 
269
282
  // src/client.ts
270
- init_types();
283
+ import fs from "fs";
284
+ import path from "path";
285
+ var log3 = debug3("observyze:sdk");
271
286
  var DEFAULT_CONFIG = {
272
- endpoint: "https://api.observyze.com",
287
+ endpoint: "http://localhost:3001",
273
288
  batchSize: 100,
274
289
  flushInterval: 5e3,
275
290
  enableAutoInstrumentation: true,
276
291
  debug: false,
277
292
  dryRun: false,
278
- enablePiiRedaction: true
293
+ enablePiiRedaction: true,
294
+ hallucinationThreshold: 0.8,
295
+ safetyThreshold: 0.9,
296
+ confidenceThreshold: 0.4,
297
+ evalEndpoint: process.env.EVAL_ENDPOINT || (process.env.NODE_ENV === "production" ? "https://api.observyze.com" : "http://localhost:3001"),
298
+ enableCircuitBreaker: true,
299
+ failClosed: true,
300
+ enableProxyRedirect: true
279
301
  };
280
302
  var ObservyzeClient = class _ObservyzeClient {
281
303
  config;
@@ -284,7 +306,37 @@ var ObservyzeClient = class _ObservyzeClient {
284
306
  isShuttingDown = false;
285
307
  MAX_QUEUE_SIZE = 1e3;
286
308
  RETRY_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
287
- // ms: 1s → 2s → 4s → 8s → 16s → 30s
309
+ /**
310
+ * Parse a JSON API error response and extract trace_id, error code, and message.
311
+ * The api-gateway error handler includes these fields in every error response.
312
+ */
313
+ static parseApiError(_response, body) {
314
+ try {
315
+ const parsed = JSON.parse(body);
316
+ const error = parsed.error || parsed;
317
+ return {
318
+ traceId: error.trace_id || "unknown",
319
+ code: error.code || "UNKNOWN_ERROR",
320
+ message: error.message || body.slice(0, 200)
321
+ };
322
+ } catch {
323
+ return {
324
+ traceId: "unknown",
325
+ code: "UNKNOWN_ERROR",
326
+ message: body.slice(0, 200)
327
+ };
328
+ }
329
+ }
330
+ /**
331
+ * Format an API error into a user-friendly message with trace_id for correlation.
332
+ * Example output:
333
+ * "Observyze API error (401 [ref: err_a1b2c3d4]): MISSING_PROVIDER_KEY — No API key configured..."
334
+ */
335
+ static formatApiError(response, body) {
336
+ const { traceId, code, message } = _ObservyzeClient.parseApiError(response, body);
337
+ const prefix = traceId !== "unknown" ? ` [ref: ${traceId}]` : "";
338
+ return `Observyze API error (${response.status}${prefix}): ${code} \u2014 ${message}`;
339
+ }
288
340
  constructor(config) {
289
341
  if (!config.apiKey) {
290
342
  throw new Error("Observyze SDK: apiKey is required");
@@ -297,7 +349,7 @@ var ObservyzeClient = class _ObservyzeClient {
297
349
  };
298
350
  this.startFlushTimer();
299
351
  if (this.config.debug) {
300
- console.log("[Observyze SDK] Initialized with config:", {
352
+ log3("[Observyze SDK] Initialized with config:", {
301
353
  endpoint: this.config.endpoint,
302
354
  batchSize: this.config.batchSize,
303
355
  flushInterval: this.config.flushInterval,
@@ -318,7 +370,7 @@ var ObservyzeClient = class _ObservyzeClient {
318
370
  trace.setMetadataAll(metadata);
319
371
  }
320
372
  const originalEnd = trace.end.bind(trace);
321
- trace.end = (status = "success" /* SUCCESS */) => {
373
+ trace.end = (status = TraceStatus.SUCCESS) => {
322
374
  originalEnd(status);
323
375
  this.bufferTrace(trace);
324
376
  };
@@ -330,23 +382,23 @@ var ObservyzeClient = class _ObservyzeClient {
330
382
  bufferTrace(trace) {
331
383
  if (!trace.isEnded) {
332
384
  if (this.config.debug) {
333
- console.warn("[Observyze SDK] Attempted to buffer a trace that has not ended");
385
+ log3.extend("warn")("[Observyze SDK] Attempted to buffer a trace that has not ended");
334
386
  }
335
387
  return;
336
388
  }
337
389
  if (this.traceBuffer.length >= this.MAX_QUEUE_SIZE) {
338
390
  if (this.config.debug) {
339
- console.warn(`[Observyze SDK] Queue at max capacity (${this.MAX_QUEUE_SIZE}), dropping oldest trace`);
391
+ log3.extend("warn")(`[Observyze SDK] Queue at max capacity (${this.MAX_QUEUE_SIZE}), dropping oldest trace`);
340
392
  }
341
393
  this.traceBuffer.shift();
342
394
  }
343
395
  this.traceBuffer.push(trace);
344
396
  if (this.config.debug) {
345
- console.log(`[Observyze SDK] Buffered trace ${trace.id} (${this.traceBuffer.length}/${this.config.batchSize})`);
397
+ log3(`[Observyze SDK] Buffered trace ${trace.id} (${this.traceBuffer.length}/${this.config.batchSize})`);
346
398
  }
347
399
  if (this.traceBuffer.length >= this.config.batchSize) {
348
400
  this.flush().catch((err) => {
349
- console.error("[Observyze SDK] Error flushing buffer:", err);
401
+ log3.extend("error")("[Observyze SDK] Error flushing buffer:", err);
350
402
  });
351
403
  }
352
404
  }
@@ -360,7 +412,7 @@ var ObservyzeClient = class _ObservyzeClient {
360
412
  this.flushTimer = setInterval(() => {
361
413
  if (this.traceBuffer.length > 0) {
362
414
  this.flush().catch((err) => {
363
- console.error("[Observyze SDK] Error in auto-flush:", err);
415
+ log3.extend("error")("[Observyze SDK] Error in auto-flush:", err);
364
416
  });
365
417
  }
366
418
  }, this.config.flushInterval);
@@ -377,11 +429,11 @@ var ObservyzeClient = class _ObservyzeClient {
377
429
  }
378
430
  const tracesToSend = this.traceBuffer.splice(0, this.config.batchSize);
379
431
  if (this.config.debug) {
380
- console.log(`[Observyze SDK] Flushing ${tracesToSend.length} traces`);
432
+ log3(`[Observyze SDK] Flushing ${tracesToSend.length} traces`);
381
433
  }
382
434
  if (this.config.dryRun) {
383
435
  if (this.config.debug) {
384
- console.log("[Observyze SDK] Dry-run mode: traces not sent");
436
+ log3("[Observyze SDK] Dry-run mode: traces not sent");
385
437
  }
386
438
  return;
387
439
  }
@@ -392,15 +444,15 @@ var ObservyzeClient = class _ObservyzeClient {
392
444
  if (remainingSpace > 0) {
393
445
  this.traceBuffer.unshift(...tracesToSend.slice(0, remainingSpace));
394
446
  if (this.config.debug) {
395
- console.log(`[Observyze SDK] Re-queued ${Math.min(tracesToSend.length, remainingSpace)} traces after failure`);
447
+ log3(`[Observyze SDK] Re-queued ${Math.min(tracesToSend.length, remainingSpace)} traces after failure`);
396
448
  }
397
449
  } else {
398
450
  if (this.config.debug) {
399
- console.warn(`[Observyze SDK] Queue full, dropped ${tracesToSend.length} traces`);
451
+ log3.extend("warn")(`[Observyze SDK] Queue full, dropped ${tracesToSend.length} traces`);
400
452
  }
401
453
  }
402
454
  if (this.config.debug) {
403
- console.error("[Observyze SDK] Failed to send traces after retries:", error);
455
+ log3.extend("error")("[Observyze SDK] Failed to send traces after retries:", error);
404
456
  }
405
457
  throw error;
406
458
  }
@@ -430,10 +482,11 @@ var ObservyzeClient = class _ObservyzeClient {
430
482
  });
431
483
  if (!response.ok) {
432
484
  const errorBody = await response.text();
433
- throw new Error(`Ingestion failed: ${response.status} ${errorBody}`);
485
+ const formatted = _ObservyzeClient.formatApiError(response, errorBody);
486
+ throw new Error(`[Observyze SDK] Trace ingestion failed. ${formatted}`);
434
487
  }
435
488
  if (this.config.debug) {
436
- console.log(`[Observyze SDK] Successfully sent ${traces.length} traces${attempt > 0 ? ` (after ${attempt} retries)` : ""}`);
489
+ log3(`[Observyze SDK] Successfully sent ${traces.length} traces${attempt > 0 ? ` (after ${attempt} retries)` : ""}`);
437
490
  }
438
491
  return;
439
492
  } catch (error) {
@@ -443,7 +496,7 @@ var ObservyzeClient = class _ObservyzeClient {
443
496
  }
444
497
  const delay = this.RETRY_DELAYS[attempt];
445
498
  if (this.config.debug) {
446
- console.warn(`[Observyze SDK] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error);
499
+ log3.extend("warn")(`[Observyze SDK] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error);
447
500
  }
448
501
  await new Promise((resolve) => setTimeout(resolve, delay));
449
502
  }
@@ -459,7 +512,7 @@ var ObservyzeClient = class _ObservyzeClient {
459
512
  }
460
513
  this.isShuttingDown = true;
461
514
  if (this.config.debug) {
462
- console.log("[Observyze SDK] Shutting down...");
515
+ log3("[Observyze SDK] Shutting down...");
463
516
  }
464
517
  if (this.flushTimer) {
465
518
  clearInterval(this.flushTimer);
@@ -468,10 +521,10 @@ var ObservyzeClient = class _ObservyzeClient {
468
521
  try {
469
522
  await this.flush();
470
523
  } catch (error) {
471
- console.error("[Observyze SDK] Error during shutdown flush:", error);
524
+ log3.extend("error")("[Observyze SDK] Error during shutdown flush:", error);
472
525
  }
473
526
  if (this.config.debug) {
474
- console.log("[Observyze SDK] Shutdown complete");
527
+ log3("[Observyze SDK] Shutdown complete");
475
528
  }
476
529
  }
477
530
  /**
@@ -508,8 +561,70 @@ var ObservyzeClient = class _ObservyzeClient {
508
561
  * ```
509
562
  */
510
563
  wrap(client) {
511
- const { wrap: wrapClient } = (init_instrumentation(), __toCommonJS(instrumentation_exports));
512
- return wrapClient(client, this);
564
+ return wrap(client, this);
565
+ }
566
+ /**
567
+ * Verify that the SDK can reach Observyze and send traces end-to-end.
568
+ *
569
+ * This is the definitive "is my integration working?" test for SDK users.
570
+ * It sends a real test trace through the EXACT same pipeline used by
571
+ * `flush()` / the wrapped LLM clients (same endpoint, apiKey, retry logic),
572
+ * so a successful call proves the whole chain works from your code:
573
+ * - apiKey is valid and authorized
574
+ * - endpoint is reachable from your environment
575
+ * - organization / project resolution works
576
+ * - the ingest pipeline accepts and stores traces
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const nw = new ObservyzeClient({ apiKey: process.env.OBSERVYZE_API_KEY })
581
+ * const result = await nw.testConnection()
582
+ * // { ok: true, traceId: 'nw_xxx', message: 'Connection successful...' }
583
+ * ```
584
+ *
585
+ * The returned traceId can be searched in the Observyze dashboard (Traces →
586
+ * search the trace name "Observyze Connection Test") to confirm it landed.
587
+ */
588
+ async testConnection() {
589
+ if (this.config.dryRun) {
590
+ return {
591
+ ok: false,
592
+ message: "Dry-run mode is enabled, so no trace was actually sent. Set dryRun: false to run a real connection test."
593
+ };
594
+ }
595
+ const trace = new Trace(
596
+ "Observyze Connection Test",
597
+ this.config.organizationId,
598
+ this.config.projectId
599
+ );
600
+ const span = trace.startSpan("connection-test", SpanType.LLM);
601
+ span.setInput({ prompt: "Observyze SDK connection test" });
602
+ span.setOutput({ response: "Connection successful" });
603
+ span.setTokens({ input: 5, output: 4, total: 9 });
604
+ span.setMetadata("source", "sdk-test-connection");
605
+ span.end();
606
+ trace.setMetadata("source", "sdk-test-connection");
607
+ trace.addTag("setup-test");
608
+ trace.end(TraceStatus.SUCCESS);
609
+ try {
610
+ await this.sendWithRetry([trace]);
611
+ return {
612
+ ok: true,
613
+ traceId: trace.id,
614
+ message: `Connection successful. Test trace ${trace.id} was sent to Observyze. Search for "Observyze Connection Test" in Dashboard \u2192 Traces to confirm it landed.`
615
+ };
616
+ } catch (error) {
617
+ const rawMessage = error?.message || String(error);
618
+ const statusMatch = rawMessage.match(/\((\d{3})/);
619
+ const isNetworkFailure = !statusMatch && /fetch|network|ENOTFOUND|ECONNREFUSED|ETIMEDOUT/i.test(rawMessage);
620
+ const defaultEndpoint = "http://localhost:3001";
621
+ const hint = isNetworkFailure && this.config.endpoint === defaultEndpoint ? ` You are using the default endpoint (${defaultEndpoint}). For production, set endpoint: "https://api.observyze.com" in the ObservyzeClient config, then re-run.` : isNetworkFailure ? " Check that your endpoint is reachable from this environment (firewalls, proxies, DNS) and that you are not using a local endpoint in production." : "";
622
+ return {
623
+ ok: false,
624
+ ...statusMatch ? { status: parseInt(statusMatch[1], 10) } : {},
625
+ message: rawMessage + hint
626
+ };
627
+ }
513
628
  }
514
629
  /**
515
630
  * Sync local agent .history file to Observyze cloud
@@ -520,8 +635,6 @@ var ObservyzeClient = class _ObservyzeClient {
520
635
  if (typeof process === "undefined" || !process.versions?.node) {
521
636
  throw new Error("syncLocalHistory is only available in Node.js environments");
522
637
  }
523
- const fs = __require("fs");
524
- const path = __require("path");
525
638
  const fullPath = path.resolve(process.cwd(), filePath);
526
639
  if (!fs.existsSync(fullPath)) {
527
640
  throw new Error(`History file not found: ${fullPath}`);
@@ -537,7 +650,7 @@ var ObservyzeClient = class _ObservyzeClient {
537
650
  items = [items];
538
651
  }
539
652
  if (this.config.debug) {
540
- console.log(`[Observyze SDK] Syncing ${items.length} traces from ${filePath}`);
653
+ log3(`[Observyze SDK] Syncing ${items.length} traces from ${filePath}`);
541
654
  }
542
655
  for (let i = 0; i < items.length; i += this.config.batchSize) {
543
656
  const batch = items.slice(i, i + this.config.batchSize);
@@ -551,14 +664,15 @@ var ObservyzeClient = class _ObservyzeClient {
551
664
  });
552
665
  if (!response.ok) {
553
666
  const errorBody = await response.text();
554
- throw new Error(`Batch sync failed: ${response.status} ${errorBody}`);
667
+ const formatted = _ObservyzeClient.formatApiError(response, errorBody);
668
+ throw new Error(`[Observyze SDK] Local history sync failed. ${formatted}`);
555
669
  }
556
670
  if (this.config.debug) {
557
- console.log(`[Observyze SDK] Synced batch of ${batch.length} traces from local history`);
671
+ log3(`[Observyze SDK] Synced batch of ${batch.length} traces from local history`);
558
672
  }
559
673
  }
560
674
  } catch (err) {
561
- console.error("[Observyze SDK] Failed to sync local history:", err);
675
+ log3.extend("error")("[Observyze SDK] Failed to sync local history:", err);
562
676
  throw err;
563
677
  }
564
678
  }
@@ -655,11 +769,223 @@ var ObservyzeClient = class _ObservyzeClient {
655
769
  }
656
770
  return data;
657
771
  }
772
+ /**
773
+ * Phase 4: Autonomous Circuit Breakers (Requirement 4.1)
774
+ * Evaluate a trace or text for hallucination in real-time.
775
+ * If hallucination score > hallucinationThreshold, the SDK blocks execution.
776
+ *
777
+ * Returns GuardrailResult with score: null when evaluation couldn't be performed.
778
+ * In failClosed mode, null scores result in blocked execution.
779
+ * In failOpen mode, null scores allow execution through.
780
+ */
781
+ async checkGuardrails(content) {
782
+ if (!this.config.enableCircuitBreaker) {
783
+ return { pass: true, score: 0, safetyScore: 0, evaluationSource: "disabled" };
784
+ }
785
+ try {
786
+ if (this.config.debug) {
787
+ log3(`[Observyze Guardrail] Analyzing payload for hallucination anomalies...`);
788
+ }
789
+ const evalEndpoint = this.config.evalEndpoint;
790
+ const payload = typeof content === "string" ? { text: content, organization_id: this.config.organizationId } : { trace: content, organization_id: this.config.organizationId };
791
+ const controller = new AbortController();
792
+ const timeout = setTimeout(() => controller.abort(), 5e3);
793
+ let evalResult = null;
794
+ try {
795
+ const response = await fetch(`${evalEndpoint}/api/v1/evaluate/hallucination`, {
796
+ method: "POST",
797
+ headers: {
798
+ "Content-Type": "application/json",
799
+ "Authorization": `Bearer ${this.config.apiKey}`
800
+ },
801
+ body: JSON.stringify(payload),
802
+ signal: controller.signal
803
+ });
804
+ clearTimeout(timeout);
805
+ if (response.ok) {
806
+ evalResult = await response.json();
807
+ } else {
808
+ const errorBody = await response.text();
809
+ const { traceId, code, message } = _ObservyzeClient.parseApiError(response, errorBody);
810
+ if (this.config.debug) {
811
+ log3.extend("warn")(`[Observyze Guardrail] Eval returned ${response.status} [${code}] (ref: ${traceId}): ${message}`);
812
+ }
813
+ }
814
+ } catch (fetchError) {
815
+ clearTimeout(timeout);
816
+ if (fetchError.name === "AbortError") {
817
+ if (this.config.debug) {
818
+ log3.extend("warn")("[Observyze Guardrail] Evaluation timed out after 5s");
819
+ }
820
+ } else if (this.config.debug) {
821
+ log3.extend("warn")("[Observyze Guardrail] Evaluation request failed:", fetchError.message);
822
+ }
823
+ }
824
+ let hallucinationScore = 0;
825
+ let safetyScore = 0;
826
+ let evaluationSource = "live";
827
+ let confidence = null;
828
+ if (evalResult) {
829
+ hallucinationScore = evalResult.score ?? evalResult.hallucination_score ?? null;
830
+ safetyScore = evalResult.safety_score ?? 0;
831
+ evaluationSource = evalResult.evaluation_source ?? "live";
832
+ confidence = evalResult.confidence ?? null;
833
+ if (hallucinationScore === null) {
834
+ if (this.config.failClosed) {
835
+ if (this.config.debug) {
836
+ log3.extend("warn")("[Observyze Guardrail] Eval returned null score \u2014 failing closed (blocking)");
837
+ }
838
+ return {
839
+ pass: false,
840
+ score: null,
841
+ confidence: null,
842
+ safetyScore: null,
843
+ evaluationSource: "error",
844
+ fallbackReason: evalResult.message || "Evaluation failed to produce a score",
845
+ reason: "Evaluation service failed to produce a score. Fail-closed: execution blocked."
846
+ };
847
+ }
848
+ if (this.config.debug) {
849
+ log3("[Observyze Guardrail] Eval returned null score \u2014 allowing (fail-open)");
850
+ }
851
+ return {
852
+ pass: true,
853
+ score: null,
854
+ confidence: null,
855
+ safetyScore: null,
856
+ evaluationSource: "error",
857
+ fallbackReason: evalResult.message || "Evaluation failed to produce a score"
858
+ };
859
+ }
860
+ } else if (this.config.failClosed) {
861
+ if (this.config.debug) {
862
+ log3.extend("warn")("[Observyze Guardrail] Eval unavailable \u2014 failing closed (blocking)");
863
+ }
864
+ return {
865
+ pass: false,
866
+ score: null,
867
+ confidence: null,
868
+ safetyScore: null,
869
+ evaluationSource: "error",
870
+ fallbackReason: "Evaluation service unreachable",
871
+ reason: "Evaluation service unreachable. Fail-closed: execution blocked."
872
+ };
873
+ } else {
874
+ if (this.config.debug) {
875
+ log3("[Observyze Guardrail] Eval unavailable \u2014 allowing (fail-open)");
876
+ }
877
+ return {
878
+ pass: true,
879
+ score: null,
880
+ confidence: null,
881
+ safetyScore: null,
882
+ evaluationSource: "error",
883
+ fallbackReason: "Evaluation service unreachable"
884
+ };
885
+ }
886
+ const hallThreshold = this.config.hallucinationThreshold;
887
+ const safeThreshold = this.config.safetyThreshold;
888
+ const confThreshold = this.config.confidenceThreshold;
889
+ if (confidence !== null && confidence < confThreshold) {
890
+ if (hallucinationScore >= hallThreshold) {
891
+ if (this.config.debug) {
892
+ log3.extend("warn")(`[Observyze Guardrail] High score (${hallucinationScore.toFixed(2)}) but low confidence (${confidence.toFixed(2)}). Alerting only.`);
893
+ }
894
+ return {
895
+ pass: true,
896
+ score: hallucinationScore,
897
+ confidence,
898
+ safetyScore,
899
+ evaluationSource,
900
+ reason: `Score ${hallucinationScore.toFixed(2)} but confidence ${confidence.toFixed(2)} is low. Execution allowed with alert.`
901
+ };
902
+ }
903
+ }
904
+ if (hallucinationScore >= hallThreshold) {
905
+ if (this.config.debug) {
906
+ log3.extend("warn")(`[Observyze Guardrail] Hallucination circuit breached! Score: ${hallucinationScore.toFixed(2)} >= ${hallThreshold}`);
907
+ }
908
+ return {
909
+ pass: false,
910
+ score: hallucinationScore,
911
+ confidence,
912
+ safetyScore,
913
+ evaluationSource,
914
+ reason: `Hallucination score ${hallucinationScore.toFixed(2)} exceeds threshold ${hallThreshold}. Execution blocked for human review.`
915
+ };
916
+ }
917
+ if (safetyScore !== null && safetyScore >= safeThreshold) {
918
+ if (this.config.debug) {
919
+ log3.extend("warn")(`[Observyze Guardrail] Safety circuit breached! Score: ${safetyScore.toFixed(2)} >= ${safeThreshold}`);
920
+ }
921
+ return {
922
+ pass: false,
923
+ score: hallucinationScore,
924
+ confidence,
925
+ safetyScore,
926
+ evaluationSource,
927
+ reason: `Safety score ${safetyScore.toFixed(2)} exceeds threshold ${safeThreshold}. Execution blocked for safety review.`
928
+ };
929
+ }
930
+ return { pass: true, score: hallucinationScore, confidence, safetyScore, evaluationSource };
931
+ } catch (err) {
932
+ log3.extend("error")("[Observyze Guardrail] Failed to evaluate:", err);
933
+ if (this.config.failClosed) {
934
+ return { pass: false, score: null, confidence: null, safetyScore: null, evaluationSource: "error", fallbackReason: "Guardrail exception", reason: "Guardrail error \u2014 fail-closed: execution blocked." };
935
+ }
936
+ return { pass: true, score: null, confidence: null, safetyScore: null, evaluationSource: "error", fallbackReason: "Guardrail exception" };
937
+ }
938
+ }
939
+ /**
940
+ * Phase 4: Autonomous Circuit Breakers
941
+ * Execute an agent action wrapped with the Circuit Breaker.
942
+ * Pauses execution if hallucination score >= hallucinationThreshold and requests human review.
943
+ * @throws Error when execution is blocked by circuit breaker
944
+ */
945
+ async executeWithCircuitBreaker(agentExecution, traceContext) {
946
+ if (!this.config.enableCircuitBreaker) {
947
+ return await agentExecution();
948
+ }
949
+ const guardResult = await this.checkGuardrails(traceContext || "execution context");
950
+ if (!guardResult.pass) {
951
+ const error = new Error(`[Observyze] Execution Blocked by Autonomous Circuit Breaker. Hallucination: ${guardResult.score?.toFixed(2) ?? "N/A"}, Safety: ${(guardResult.safetyScore ?? 0)?.toFixed(2) ?? "N/A"}. Reason: ${guardResult.reason}. Human approval required before agent can continue.`);
952
+ if (this.config.debug) {
953
+ log3.extend("error")("[Observyze CircuitBreaker] Execution blocked:", error.message);
954
+ }
955
+ throw error;
956
+ }
957
+ if (this.config.debug) {
958
+ log3(`[Observyze CircuitBreaker] Execution allowed. Hallucination: ${guardResult.score?.toFixed(2) ?? "N/A"}, Safety: ${(guardResult.safetyScore ?? 0)?.toFixed(2) ?? "N/A"}`);
959
+ }
960
+ return await agentExecution();
961
+ }
962
+ /**
963
+ * Phase 4: Bug Bounty Protocol (Automated) (Requirement 4.2)
964
+ * Automatically shard persistent failure cases to external security researcher endpoints (e.g. HackerOne wrapper)
965
+ */
966
+ async reportBugBounty(traceId, securityEndpoint, failureContext) {
967
+ try {
968
+ if (this.config.debug) {
969
+ log3(`[Observyze SDK] Sharding persistent failure case ${traceId} to Bug Bounty Protocol endpoint...`);
970
+ }
971
+ await fetch(securityEndpoint, {
972
+ method: "POST",
973
+ headers: { "Content-Type": "application/json" },
974
+ body: JSON.stringify({
975
+ alert: "persistent_failure_sharded",
976
+ trace_id: traceId,
977
+ context: failureContext,
978
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
979
+ })
980
+ });
981
+ if (this.config.debug) {
982
+ log3(`[Observyze SDK] Bug Bounty payload successfully transmitted.`);
983
+ }
984
+ } catch (err) {
985
+ log3.extend("error")("[Observyze Bug Bounty] Failed to shard failure case:", err);
986
+ }
987
+ }
658
988
  };
659
-
660
- // src/index.ts
661
- init_types();
662
- init_instrumentation();
663
989
  export {
664
990
  ObservyzeClient,
665
991
  ObservyzeSpanExporter,
@@ -1 +1,2 @@
1
- export { a as ObservyzeExporterConfig, b as ObservyzeSpanExporter } from '../index-DEorAmFu.mjs';
1
+ export { a as ObservyzeExporterConfig, b as ObservyzeSpanExporter } from '../index-IZdiaORv.mjs';
2
+ import '@observyze/types';
@@ -1 +1,2 @@
1
- export { a as ObservyzeExporterConfig, b as ObservyzeSpanExporter } from '../index-DEorAmFu.js';
1
+ export { a as ObservyzeExporterConfig, b as ObservyzeSpanExporter } from '../index-IZdiaORv.js';
2
+ import '@observyze/types';