@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/README.md +175 -10
- package/dist/chunk-FQBYUOJB.mjs +316 -0
- package/dist/{chunk-YRMQCX2P.mjs → chunk-YSYKPKKX.mjs} +5 -20
- package/dist/index--b41_E-1.d.mts +440 -0
- package/dist/index--b41_E-1.d.ts +440 -0
- package/dist/{index-DEorAmFu.d.mts → index-Cr-FN-y5.d.mts} +89 -89
- package/dist/{index-DEorAmFu.d.ts → index-Cr-FN-y5.d.ts} +89 -89
- package/dist/index-D4UXMom5.d.mts +429 -0
- package/dist/index-D4UXMom5.d.ts +429 -0
- package/dist/index-IZdiaORv.d.mts +477 -0
- package/dist/index-IZdiaORv.d.ts +477 -0
- package/dist/index.d.mts +4 -37
- package/dist/index.d.ts +4 -37
- package/dist/index.js +665 -349
- package/dist/index.mjs +396 -70
- package/dist/opentelemetry/index.d.mts +2 -1
- package/dist/opentelemetry/index.d.ts +2 -1
- package/dist/opentelemetry/index.js +7 -4
- package/dist/opentelemetry/index.mjs +1 -1
- package/package.json +13 -6
- package/LICENSE +0 -21
package/dist/index.mjs
CHANGED
|
@@ -3,24 +3,43 @@ import {
|
|
|
3
3
|
Span,
|
|
4
4
|
SpanType,
|
|
5
5
|
Trace,
|
|
6
|
-
TraceStatus
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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",
|
|
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",
|
|
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
|
-
|
|
283
|
+
import fs from "fs";
|
|
284
|
+
import path from "path";
|
|
285
|
+
var log3 = debug3("observyze:sdk");
|
|
271
286
|
var DEFAULT_CONFIG = {
|
|
272
|
-
endpoint: "
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
+
log3(`[Observyze SDK] Flushing ${tracesToSend.length} traces`);
|
|
381
433
|
}
|
|
382
434
|
if (this.config.dryRun) {
|
|
383
435
|
if (this.config.debug) {
|
|
384
|
-
|
|
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
|
-
|
|
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
|
-
|
|
451
|
+
log3.extend("warn")(`[Observyze SDK] Queue full, dropped ${tracesToSend.length} traces`);
|
|
400
452
|
}
|
|
401
453
|
}
|
|
402
454
|
if (this.config.debug) {
|
|
403
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
524
|
+
log3.extend("error")("[Observyze SDK] Error during shutdown flush:", error);
|
|
472
525
|
}
|
|
473
526
|
if (this.config.debug) {
|
|
474
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
671
|
+
log3(`[Observyze SDK] Synced batch of ${batch.length} traces from local history`);
|
|
558
672
|
}
|
|
559
673
|
}
|
|
560
674
|
} catch (err) {
|
|
561
|
-
|
|
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-
|
|
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-
|
|
1
|
+
export { a as ObservyzeExporterConfig, b as ObservyzeSpanExporter } from '../index-IZdiaORv.js';
|
|
2
|
+
import '@observyze/types';
|