observa-sdk 0.0.5 → 0.0.7
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.cjs +547 -19
- package/dist/index.d.cts +134 -2
- package/dist/index.d.ts +134 -2
- package/dist/index.js +547 -19
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
+
var contextModule = null;
|
|
3
|
+
try {
|
|
4
|
+
const requireFn = globalThis.require;
|
|
5
|
+
if (typeof requireFn !== "undefined") {
|
|
6
|
+
contextModule = requireFn("./context.js");
|
|
7
|
+
}
|
|
8
|
+
} catch {
|
|
9
|
+
}
|
|
2
10
|
function getNodeEnv() {
|
|
3
11
|
try {
|
|
4
12
|
const proc = globalThis.process;
|
|
@@ -221,6 +229,20 @@ var Observa = class {
|
|
|
221
229
|
isProduction;
|
|
222
230
|
sampleRate;
|
|
223
231
|
maxResponseChars;
|
|
232
|
+
// Buffering and retry (now stores canonical events)
|
|
233
|
+
eventBuffer = [];
|
|
234
|
+
flushPromise = null;
|
|
235
|
+
flushInProgress = false;
|
|
236
|
+
maxBufferSize = 100;
|
|
237
|
+
flushIntervalMs = 5e3;
|
|
238
|
+
// Flush every 5 seconds
|
|
239
|
+
flushIntervalId = null;
|
|
240
|
+
// Span hierarchy tracking (for manual trace management)
|
|
241
|
+
currentTraceId = null;
|
|
242
|
+
rootSpanId = null;
|
|
243
|
+
spanStack = [];
|
|
244
|
+
// Stack for tracking parent-child relationships
|
|
245
|
+
traceStartTime = null;
|
|
224
246
|
constructor(config) {
|
|
225
247
|
this.apiKey = config.apiKey;
|
|
226
248
|
let apiUrlEnv;
|
|
@@ -265,35 +287,465 @@ var Observa = class {
|
|
|
265
287
|
`\u{1F517} [Observa] Auth: ${jwtContext ? "JWT (auto-extracted)" : "Legacy (config)"}`
|
|
266
288
|
);
|
|
267
289
|
}
|
|
290
|
+
try {
|
|
291
|
+
if (typeof setInterval !== "undefined") {
|
|
292
|
+
this.flushIntervalId = setInterval(() => {
|
|
293
|
+
this.flush().catch((err) => {
|
|
294
|
+
console.error("[Observa] Periodic flush failed:", err);
|
|
295
|
+
});
|
|
296
|
+
}, this.flushIntervalMs);
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Flush buffered events to the API
|
|
303
|
+
* Returns a promise that resolves when all events are sent
|
|
304
|
+
*/
|
|
305
|
+
async flush() {
|
|
306
|
+
if (this.flushInProgress || this.eventBuffer.length === 0) {
|
|
307
|
+
return this.flushPromise || Promise.resolve();
|
|
308
|
+
}
|
|
309
|
+
this.flushInProgress = true;
|
|
310
|
+
this.flushPromise = this._doFlush();
|
|
311
|
+
try {
|
|
312
|
+
await this.flushPromise;
|
|
313
|
+
} finally {
|
|
314
|
+
this.flushInProgress = false;
|
|
315
|
+
this.flushPromise = null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Helper: Create base event properties
|
|
320
|
+
*/
|
|
321
|
+
createBaseEventProperties() {
|
|
322
|
+
const traceId = this.currentTraceId || crypto.randomUUID();
|
|
323
|
+
return {
|
|
324
|
+
tenant_id: this.tenantId,
|
|
325
|
+
project_id: this.projectId,
|
|
326
|
+
environment: this.environment,
|
|
327
|
+
trace_id: traceId
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Helper: Add event to buffer with proper span hierarchy
|
|
332
|
+
*/
|
|
333
|
+
addEvent(eventData) {
|
|
334
|
+
const baseProps = this.createBaseEventProperties();
|
|
335
|
+
const parentSpanId = this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null;
|
|
336
|
+
const event = {
|
|
337
|
+
...baseProps,
|
|
338
|
+
span_id: eventData.span_id || crypto.randomUUID(),
|
|
339
|
+
parent_span_id: (eventData.parent_span_id !== void 0 ? eventData.parent_span_id : parentSpanId) ?? null,
|
|
340
|
+
timestamp: eventData.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
341
|
+
event_type: eventData.event_type,
|
|
342
|
+
conversation_id: eventData.conversation_id ?? null,
|
|
343
|
+
session_id: eventData.session_id ?? null,
|
|
344
|
+
user_id: eventData.user_id ?? null,
|
|
345
|
+
agent_name: eventData.agent_name ?? null,
|
|
346
|
+
version: eventData.version ?? null,
|
|
347
|
+
route: eventData.route ?? null,
|
|
348
|
+
attributes: eventData.attributes
|
|
349
|
+
};
|
|
350
|
+
this.eventBuffer.push(event);
|
|
351
|
+
if (this.eventBuffer.length >= this.maxBufferSize) {
|
|
352
|
+
this.flush().catch((err) => {
|
|
353
|
+
console.error("[Observa] Auto-flush failed:", err);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Start a new trace (manual trace management)
|
|
359
|
+
*/
|
|
360
|
+
startTrace(options = {}) {
|
|
361
|
+
if (this.currentTraceId) {
|
|
362
|
+
console.warn("[Observa] Ending previous trace before starting new one");
|
|
363
|
+
this.endTrace().catch(console.error);
|
|
364
|
+
}
|
|
365
|
+
this.currentTraceId = crypto.randomUUID();
|
|
366
|
+
this.rootSpanId = crypto.randomUUID();
|
|
367
|
+
this.spanStack = [this.rootSpanId];
|
|
368
|
+
this.traceStartTime = Date.now();
|
|
369
|
+
this.addEvent({
|
|
370
|
+
event_type: "trace_start",
|
|
371
|
+
span_id: this.rootSpanId,
|
|
372
|
+
parent_span_id: null,
|
|
373
|
+
conversation_id: options.conversationId || null,
|
|
374
|
+
session_id: options.sessionId || null,
|
|
375
|
+
user_id: options.userId || null,
|
|
376
|
+
attributes: {
|
|
377
|
+
trace_start: {
|
|
378
|
+
name: options.name || null,
|
|
379
|
+
metadata: options.metadata || null
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return this.currentTraceId;
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Track a tool call
|
|
387
|
+
*/
|
|
388
|
+
trackToolCall(options) {
|
|
389
|
+
const spanId = crypto.randomUUID();
|
|
390
|
+
this.addEvent({
|
|
391
|
+
event_type: "tool_call",
|
|
392
|
+
span_id: spanId,
|
|
393
|
+
attributes: {
|
|
394
|
+
tool_call: {
|
|
395
|
+
tool_name: options.toolName,
|
|
396
|
+
args: options.args || null,
|
|
397
|
+
result: options.result || null,
|
|
398
|
+
result_status: options.resultStatus,
|
|
399
|
+
latency_ms: options.latencyMs,
|
|
400
|
+
error_message: options.errorMessage || null
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
return spanId;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Track a retrieval operation
|
|
408
|
+
*/
|
|
409
|
+
trackRetrieval(options) {
|
|
410
|
+
const spanId = crypto.randomUUID();
|
|
411
|
+
this.addEvent({
|
|
412
|
+
event_type: "retrieval",
|
|
413
|
+
span_id: spanId,
|
|
414
|
+
attributes: {
|
|
415
|
+
retrieval: {
|
|
416
|
+
retrieval_context_ids: options.contextIds || null,
|
|
417
|
+
retrieval_context_hashes: options.contextHashes || null,
|
|
418
|
+
k: options.k || null,
|
|
419
|
+
top_k: options.k || null,
|
|
420
|
+
similarity_scores: options.similarityScores || null,
|
|
421
|
+
latency_ms: options.latencyMs
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return spanId;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Track an error with stack trace support
|
|
429
|
+
*/
|
|
430
|
+
trackError(options) {
|
|
431
|
+
const spanId = crypto.randomUUID();
|
|
432
|
+
let stackTrace = options.stackTrace;
|
|
433
|
+
if (!stackTrace && options.error instanceof Error && options.error.stack) {
|
|
434
|
+
stackTrace = options.error.stack;
|
|
435
|
+
}
|
|
436
|
+
this.addEvent({
|
|
437
|
+
event_type: "error",
|
|
438
|
+
span_id: spanId,
|
|
439
|
+
attributes: {
|
|
440
|
+
error: {
|
|
441
|
+
error_type: options.errorType,
|
|
442
|
+
error_message: options.errorMessage,
|
|
443
|
+
stack_trace: stackTrace || null,
|
|
444
|
+
context: options.context || null
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
return spanId;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Track user feedback
|
|
452
|
+
*/
|
|
453
|
+
trackFeedback(options) {
|
|
454
|
+
const spanId = options.spanId || crypto.randomUUID();
|
|
455
|
+
const parentSpanId = options.parentSpanId ?? (this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null);
|
|
456
|
+
let rating = options.rating;
|
|
457
|
+
if (rating !== void 0 && rating !== null) {
|
|
458
|
+
rating = Math.max(1, Math.min(5, rating));
|
|
459
|
+
}
|
|
460
|
+
this.addEvent({
|
|
461
|
+
event_type: "feedback",
|
|
462
|
+
span_id: spanId,
|
|
463
|
+
parent_span_id: parentSpanId ?? null,
|
|
464
|
+
conversation_id: options.conversationId ?? null,
|
|
465
|
+
session_id: options.sessionId ?? null,
|
|
466
|
+
user_id: options.userId ?? null,
|
|
467
|
+
agent_name: options.agentName ?? null,
|
|
468
|
+
version: options.version ?? null,
|
|
469
|
+
route: options.route ?? null,
|
|
470
|
+
attributes: {
|
|
471
|
+
feedback: {
|
|
472
|
+
type: options.type,
|
|
473
|
+
rating: rating ?? null,
|
|
474
|
+
comment: options.comment || null,
|
|
475
|
+
outcome: options.outcome || null
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
return spanId;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Track final output
|
|
483
|
+
*/
|
|
484
|
+
trackOutput(options) {
|
|
485
|
+
const spanId = crypto.randomUUID();
|
|
486
|
+
this.addEvent({
|
|
487
|
+
event_type: "output",
|
|
488
|
+
span_id: spanId,
|
|
489
|
+
attributes: {
|
|
490
|
+
output: {
|
|
491
|
+
final_output: options.finalOutput || null,
|
|
492
|
+
output_length: options.outputLength || null
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return spanId;
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Execute a function within a span context (for nested operations)
|
|
500
|
+
* This allows tool calls to be nested under LLM calls, etc.
|
|
501
|
+
*/
|
|
502
|
+
withSpan(spanId, fn) {
|
|
503
|
+
this.spanStack.push(spanId);
|
|
504
|
+
try {
|
|
505
|
+
return fn();
|
|
506
|
+
} finally {
|
|
507
|
+
this.spanStack.pop();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Execute an async function within a span context (for nested operations)
|
|
512
|
+
*/
|
|
513
|
+
async withSpanAsync(spanId, fn) {
|
|
514
|
+
this.spanStack.push(spanId);
|
|
515
|
+
try {
|
|
516
|
+
return await fn();
|
|
517
|
+
} finally {
|
|
518
|
+
this.spanStack.pop();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* End trace and send events (manual trace management)
|
|
523
|
+
*/
|
|
524
|
+
async endTrace(options = {}) {
|
|
525
|
+
if (!this.currentTraceId || !this.rootSpanId) {
|
|
526
|
+
throw new Error("[Observa] No active trace. Call startTrace() first.");
|
|
527
|
+
}
|
|
528
|
+
const traceEvents = this.eventBuffer.filter(
|
|
529
|
+
(e) => e.trace_id === this.currentTraceId
|
|
530
|
+
);
|
|
531
|
+
const llmEvents = traceEvents.filter((e) => e.event_type === "llm_call");
|
|
532
|
+
const totalTokens = llmEvents.reduce(
|
|
533
|
+
(sum, e) => sum + (e.attributes.llm_call?.total_tokens || 0),
|
|
534
|
+
0
|
|
535
|
+
);
|
|
536
|
+
const totalCost = llmEvents.reduce(
|
|
537
|
+
(sum, e) => sum + (e.attributes.llm_call?.cost || 0),
|
|
538
|
+
0
|
|
539
|
+
);
|
|
540
|
+
const totalLatency = this.traceStartTime !== null ? Date.now() - this.traceStartTime : null;
|
|
541
|
+
this.addEvent({
|
|
542
|
+
event_type: "trace_end",
|
|
543
|
+
span_id: this.rootSpanId,
|
|
544
|
+
parent_span_id: null,
|
|
545
|
+
attributes: {
|
|
546
|
+
trace_end: {
|
|
547
|
+
total_latency_ms: totalLatency,
|
|
548
|
+
total_tokens: totalTokens || null,
|
|
549
|
+
total_cost: totalCost || null,
|
|
550
|
+
outcome: options.outcome || "success"
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
const traceEventsToSend = this.eventBuffer.filter(
|
|
555
|
+
(e) => e.trace_id === this.currentTraceId
|
|
556
|
+
);
|
|
557
|
+
if (traceEventsToSend.length > 0) {
|
|
558
|
+
await this._sendEventsWithRetry(traceEventsToSend);
|
|
559
|
+
this.eventBuffer = this.eventBuffer.filter(
|
|
560
|
+
(e) => e.trace_id !== this.currentTraceId
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
const traceId = this.currentTraceId;
|
|
564
|
+
this.currentTraceId = null;
|
|
565
|
+
this.rootSpanId = null;
|
|
566
|
+
this.spanStack = [];
|
|
567
|
+
this.traceStartTime = null;
|
|
568
|
+
return traceId;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Convert legacy TraceData to canonical events
|
|
572
|
+
*/
|
|
573
|
+
traceDataToCanonicalEvents(trace) {
|
|
574
|
+
const events = [];
|
|
575
|
+
const baseEvent = {
|
|
576
|
+
tenant_id: trace.tenantId,
|
|
577
|
+
project_id: trace.projectId,
|
|
578
|
+
environment: trace.environment,
|
|
579
|
+
trace_id: trace.traceId,
|
|
580
|
+
conversation_id: trace.conversationId || null,
|
|
581
|
+
session_id: trace.sessionId || null,
|
|
582
|
+
user_id: trace.userId || null
|
|
583
|
+
};
|
|
584
|
+
events.push({
|
|
585
|
+
...baseEvent,
|
|
586
|
+
span_id: trace.spanId,
|
|
587
|
+
parent_span_id: trace.parentSpanId || null,
|
|
588
|
+
timestamp: trace.timestamp,
|
|
589
|
+
event_type: "trace_start",
|
|
590
|
+
attributes: {
|
|
591
|
+
trace_start: {
|
|
592
|
+
metadata: trace.metadata || null
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
if (trace.model) {
|
|
597
|
+
const llmSpanId = crypto.randomUUID();
|
|
598
|
+
events.push({
|
|
599
|
+
...baseEvent,
|
|
600
|
+
span_id: llmSpanId,
|
|
601
|
+
parent_span_id: trace.spanId,
|
|
602
|
+
timestamp: trace.timestamp,
|
|
603
|
+
event_type: "llm_call",
|
|
604
|
+
attributes: {
|
|
605
|
+
llm_call: {
|
|
606
|
+
model: trace.model,
|
|
607
|
+
input: trace.query || null,
|
|
608
|
+
output: trace.response || null,
|
|
609
|
+
input_tokens: trace.tokensPrompt || null,
|
|
610
|
+
output_tokens: trace.tokensCompletion || null,
|
|
611
|
+
total_tokens: trace.tokensTotal || null,
|
|
612
|
+
latency_ms: trace.latencyMs,
|
|
613
|
+
time_to_first_token_ms: trace.timeToFirstTokenMs || null,
|
|
614
|
+
streaming_duration_ms: trace.streamingDurationMs || null,
|
|
615
|
+
finish_reason: trace.finishReason || null,
|
|
616
|
+
response_id: trace.responseId || null,
|
|
617
|
+
system_fingerprint: trace.systemFingerprint || null,
|
|
618
|
+
cost: null
|
|
619
|
+
// Cost calculation handled by backend
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
events.push({
|
|
625
|
+
...baseEvent,
|
|
626
|
+
span_id: crypto.randomUUID(),
|
|
627
|
+
parent_span_id: trace.spanId,
|
|
628
|
+
timestamp: trace.timestamp,
|
|
629
|
+
event_type: "output",
|
|
630
|
+
attributes: {
|
|
631
|
+
output: {
|
|
632
|
+
final_output: trace.response || null,
|
|
633
|
+
output_length: trace.responseLength || null
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
events.push({
|
|
638
|
+
...baseEvent,
|
|
639
|
+
span_id: trace.spanId,
|
|
640
|
+
parent_span_id: trace.parentSpanId || null,
|
|
641
|
+
timestamp: trace.timestamp,
|
|
642
|
+
event_type: "trace_end",
|
|
643
|
+
attributes: {
|
|
644
|
+
trace_end: {
|
|
645
|
+
total_latency_ms: trace.latencyMs,
|
|
646
|
+
total_tokens: trace.tokensTotal || null,
|
|
647
|
+
total_cost: null,
|
|
648
|
+
// Cost calculation handled by backend
|
|
649
|
+
outcome: trace.status && trace.status >= 200 && trace.status < 300 ? "success" : "error"
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
return events;
|
|
268
654
|
}
|
|
269
|
-
|
|
655
|
+
/**
|
|
656
|
+
* Internal flush implementation
|
|
657
|
+
*/
|
|
658
|
+
async _doFlush() {
|
|
659
|
+
const eventsToFlush = [...this.eventBuffer];
|
|
660
|
+
this.eventBuffer = [];
|
|
661
|
+
if (eventsToFlush.length === 0) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const eventsByTrace = /* @__PURE__ */ new Map();
|
|
665
|
+
for (const event of eventsToFlush) {
|
|
666
|
+
if (!eventsByTrace.has(event.trace_id)) {
|
|
667
|
+
eventsByTrace.set(event.trace_id, []);
|
|
668
|
+
}
|
|
669
|
+
eventsByTrace.get(event.trace_id).push(event);
|
|
670
|
+
}
|
|
671
|
+
for (const [traceId, events] of eventsByTrace.entries()) {
|
|
672
|
+
await this._sendEventsWithRetry(events);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Send canonical events with exponential backoff retry
|
|
677
|
+
*/
|
|
678
|
+
async _sendEventsWithRetry(events, maxRetries = 3) {
|
|
679
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
680
|
+
try {
|
|
681
|
+
await this.sendEvents(events);
|
|
682
|
+
return;
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (attempt === maxRetries) {
|
|
685
|
+
console.error(
|
|
686
|
+
`[Observa] Failed to send events after ${maxRetries + 1} attempts, re-buffering:`,
|
|
687
|
+
error
|
|
688
|
+
);
|
|
689
|
+
this.eventBuffer.push(...events);
|
|
690
|
+
if (this.eventBuffer.length > this.maxBufferSize * 2) {
|
|
691
|
+
const toDrop = this.eventBuffer.length - this.maxBufferSize;
|
|
692
|
+
this.eventBuffer.splice(0, toDrop);
|
|
693
|
+
}
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const delayMs = 100 * Math.pow(2, attempt);
|
|
697
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Cleanup (call when shutting down)
|
|
703
|
+
*/
|
|
704
|
+
async end() {
|
|
705
|
+
if (this.flushIntervalId) {
|
|
706
|
+
clearInterval(this.flushIntervalId);
|
|
707
|
+
this.flushIntervalId = null;
|
|
708
|
+
}
|
|
709
|
+
await this.flush();
|
|
710
|
+
}
|
|
711
|
+
async track(event, action, options) {
|
|
270
712
|
if (this.sampleRate < 1 && Math.random() > this.sampleRate) {
|
|
271
713
|
return action();
|
|
272
714
|
}
|
|
273
715
|
const startTime = Date.now();
|
|
274
716
|
const traceId = crypto.randomUUID();
|
|
275
717
|
const spanId = traceId;
|
|
276
|
-
const
|
|
718
|
+
const runWithContext = contextModule?.runInTraceContextAsync || ((ctx, fn) => fn());
|
|
719
|
+
const context = contextModule?.createSpanContext?.(
|
|
720
|
+
traceId,
|
|
721
|
+
spanId,
|
|
722
|
+
null
|
|
723
|
+
) || { traceId, spanId, parentSpanId: null };
|
|
724
|
+
const originalResponse = await runWithContext(context, action);
|
|
277
725
|
if (!originalResponse.body) return originalResponse;
|
|
278
726
|
const responseHeaders = {};
|
|
279
727
|
originalResponse.headers.forEach((value, key) => {
|
|
280
728
|
responseHeaders[key] = value;
|
|
281
729
|
});
|
|
282
730
|
const [stream1, stream2] = originalResponse.body.tee();
|
|
283
|
-
|
|
284
|
-
this.captureStream({
|
|
731
|
+
const capturePromise = this.captureStream({
|
|
285
732
|
stream: stream2,
|
|
286
733
|
event,
|
|
287
734
|
traceId,
|
|
288
735
|
spanId,
|
|
289
|
-
parentSpanId:
|
|
736
|
+
parentSpanId: context.parentSpanId,
|
|
290
737
|
startTime,
|
|
291
738
|
status: originalResponse.status,
|
|
292
739
|
statusText: originalResponse.statusText,
|
|
293
740
|
headers: responseHeaders
|
|
294
|
-
}).catch((err) => {
|
|
295
|
-
console.error("[Observa] captureStream promise rejected:", err);
|
|
296
741
|
});
|
|
742
|
+
if (options?.trackBlocking) {
|
|
743
|
+
await capturePromise;
|
|
744
|
+
} else {
|
|
745
|
+
capturePromise.catch((err) => {
|
|
746
|
+
console.error("[Observa] captureStream promise rejected:", err);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
297
749
|
return new Response(stream1, {
|
|
298
750
|
headers: originalResponse.headers,
|
|
299
751
|
status: originalResponse.status,
|
|
@@ -393,13 +845,42 @@ var Observa = class {
|
|
|
393
845
|
finishReason: extracted.finishReason ?? null,
|
|
394
846
|
responseId: extracted.responseId ?? null,
|
|
395
847
|
systemFingerprint: extracted.systemFingerprint ?? null,
|
|
396
|
-
...headers !== void 0 && { headers }
|
|
848
|
+
...headers !== void 0 && { headers },
|
|
849
|
+
// Conversation tracking fields
|
|
850
|
+
...event.conversationId !== void 0 && {
|
|
851
|
+
conversationId: event.conversationId
|
|
852
|
+
},
|
|
853
|
+
...event.sessionId !== void 0 && { sessionId: event.sessionId },
|
|
854
|
+
...event.userId !== void 0 && { userId: event.userId },
|
|
855
|
+
...event.messageIndex !== void 0 && {
|
|
856
|
+
messageIndex: event.messageIndex
|
|
857
|
+
},
|
|
858
|
+
...event.parentMessageId !== void 0 && {
|
|
859
|
+
parentMessageId: event.parentMessageId
|
|
860
|
+
}
|
|
397
861
|
};
|
|
398
862
|
console.log(
|
|
399
|
-
`[Observa] Trace data prepared,
|
|
863
|
+
`[Observa] Trace data prepared, converting to canonical events for ${traceId}, response length: ${fullResponse.length}`
|
|
400
864
|
);
|
|
401
|
-
|
|
402
|
-
|
|
865
|
+
const canonicalEvents = this.traceDataToCanonicalEvents(traceData);
|
|
866
|
+
this.eventBuffer.push(...canonicalEvents);
|
|
867
|
+
if (this.eventBuffer.length >= this.maxBufferSize) {
|
|
868
|
+
this.flush().catch((err) => {
|
|
869
|
+
console.error("[Observa] Auto-flush failed:", err);
|
|
870
|
+
});
|
|
871
|
+
} else {
|
|
872
|
+
this._sendEventsWithRetry(canonicalEvents).catch((err) => {
|
|
873
|
+
console.error("[Observa] Failed to send events:", err);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
if (!this.isProduction) {
|
|
877
|
+
const traceUrl = `${this.apiUrl.replace(
|
|
878
|
+
/\/api.*$/,
|
|
879
|
+
""
|
|
880
|
+
)}/traces/${traceId}`;
|
|
881
|
+
console.log(`[Observa] \u{1F54A}\uFE0F Trace captured: ${traceUrl}`);
|
|
882
|
+
}
|
|
883
|
+
console.log(`[Observa] Trace queued for sending: ${traceId}`);
|
|
403
884
|
} catch (err) {
|
|
404
885
|
console.error("[Observa] Error capturing stream:", err);
|
|
405
886
|
if (err instanceof Error) {
|
|
@@ -411,15 +892,57 @@ var Observa = class {
|
|
|
411
892
|
}
|
|
412
893
|
}
|
|
413
894
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
895
|
+
/**
|
|
896
|
+
* Send canonical events to Observa backend
|
|
897
|
+
* (internal method, use _sendEventsWithRetry for retry logic)
|
|
898
|
+
*/
|
|
899
|
+
async sendEvents(events) {
|
|
900
|
+
if (events.length === 0) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const traceId = events[0]?.trace_id;
|
|
904
|
+
if (!this.isProduction && traceId) {
|
|
905
|
+
const llmEvent = events.find((e) => e.event_type === "llm_call");
|
|
906
|
+
const outputEvent = events.find((e) => e.event_type === "output");
|
|
907
|
+
const traceEndEvent = events.find((e) => e.event_type === "trace_end");
|
|
908
|
+
if (llmEvent && outputEvent) {
|
|
909
|
+
const llmAttrs = llmEvent.attributes.llm_call;
|
|
910
|
+
const outputAttrs = outputEvent.attributes.output;
|
|
911
|
+
const traceData = {
|
|
912
|
+
traceId: llmEvent.trace_id,
|
|
913
|
+
spanId: llmEvent.parent_span_id || llmEvent.span_id,
|
|
914
|
+
parentSpanId: llmEvent.parent_span_id || null,
|
|
915
|
+
timestamp: llmEvent.timestamp,
|
|
916
|
+
tenantId: llmEvent.tenant_id,
|
|
917
|
+
projectId: llmEvent.project_id,
|
|
918
|
+
environment: llmEvent.environment,
|
|
919
|
+
query: llmAttrs?.input || "",
|
|
920
|
+
response: outputAttrs?.final_output || "",
|
|
921
|
+
responseLength: outputAttrs?.output_length || 0,
|
|
922
|
+
...llmAttrs?.model && { model: llmAttrs.model },
|
|
923
|
+
tokensPrompt: llmAttrs?.input_tokens ?? null,
|
|
924
|
+
tokensCompletion: llmAttrs?.output_tokens ?? null,
|
|
925
|
+
tokensTotal: llmAttrs?.total_tokens ?? null,
|
|
926
|
+
latencyMs: llmAttrs?.latency_ms || 0,
|
|
927
|
+
timeToFirstTokenMs: llmAttrs?.time_to_first_token_ms ?? null,
|
|
928
|
+
streamingDurationMs: llmAttrs?.streaming_duration_ms ?? null,
|
|
929
|
+
finishReason: llmAttrs?.finish_reason ?? null,
|
|
930
|
+
responseId: llmAttrs?.response_id ?? null,
|
|
931
|
+
systemFingerprint: llmAttrs?.system_fingerprint ?? null,
|
|
932
|
+
...llmEvent.conversation_id && {
|
|
933
|
+
conversationId: llmEvent.conversation_id
|
|
934
|
+
},
|
|
935
|
+
...llmEvent.session_id && { sessionId: llmEvent.session_id },
|
|
936
|
+
...llmEvent.user_id && { userId: llmEvent.user_id }
|
|
937
|
+
};
|
|
938
|
+
formatBeautifulLog(traceData);
|
|
939
|
+
}
|
|
417
940
|
}
|
|
418
941
|
try {
|
|
419
942
|
const baseUrl = this.apiUrl.replace(/\/+$/, "");
|
|
420
|
-
const url = `${baseUrl}/api/v1/
|
|
943
|
+
const url = `${baseUrl}/api/v1/events/ingest`;
|
|
421
944
|
console.log(
|
|
422
|
-
`[Observa] Sending
|
|
945
|
+
`[Observa] Sending ${events.length} canonical events - URL: ${url}, TraceID: ${traceId}, Tenant: ${events[0]?.tenant_id}, Project: ${events[0]?.project_id}, APIKey: ${this.apiKey ? `Yes(${this.apiKey.length} chars)` : "No"}`
|
|
423
946
|
);
|
|
424
947
|
const controller = new AbortController();
|
|
425
948
|
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -430,7 +953,7 @@ var Observa = class {
|
|
|
430
953
|
Authorization: `Bearer ${this.apiKey}`,
|
|
431
954
|
"Content-Type": "application/json"
|
|
432
955
|
},
|
|
433
|
-
body: JSON.stringify(
|
|
956
|
+
body: JSON.stringify(events),
|
|
434
957
|
signal: controller.signal
|
|
435
958
|
});
|
|
436
959
|
clearTimeout(timeoutId);
|
|
@@ -449,9 +972,13 @@ var Observa = class {
|
|
|
449
972
|
`[Observa] Backend API error: ${response.status} ${response.statusText}`,
|
|
450
973
|
errorJson.error || errorText
|
|
451
974
|
);
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Observa API error: ${response.status} ${errorJson.error?.message || errorText}`
|
|
977
|
+
);
|
|
452
978
|
} else {
|
|
979
|
+
const result = await response.json().catch(() => ({}));
|
|
453
980
|
console.log(
|
|
454
|
-
`\u2705 [Observa]
|
|
981
|
+
`\u2705 [Observa] Events sent successfully - Trace ID: ${traceId}, Event count: ${result.event_count || events.length}`
|
|
455
982
|
);
|
|
456
983
|
}
|
|
457
984
|
} catch (fetchError) {
|
|
@@ -462,7 +989,7 @@ var Observa = class {
|
|
|
462
989
|
throw fetchError;
|
|
463
990
|
}
|
|
464
991
|
} catch (error) {
|
|
465
|
-
console.error("[Observa] Failed to send
|
|
992
|
+
console.error("[Observa] Failed to send events:", error);
|
|
466
993
|
if (error instanceof Error) {
|
|
467
994
|
console.error("[Observa] Error message:", error.message);
|
|
468
995
|
console.error("[Observa] Error name:", error.name);
|
|
@@ -475,6 +1002,7 @@ var Observa = class {
|
|
|
475
1002
|
console.error("[Observa] Error stack:", error.stack);
|
|
476
1003
|
}
|
|
477
1004
|
}
|
|
1005
|
+
throw error;
|
|
478
1006
|
}
|
|
479
1007
|
}
|
|
480
1008
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "observa-sdk",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Enterprise-grade observability SDK for AI applications. Track and monitor LLM interactions with zero friction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -31,5 +31,8 @@
|
|
|
31
31
|
"analytics"
|
|
32
32
|
],
|
|
33
33
|
"author": "Nicka",
|
|
34
|
-
"license": "MIT"
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
35
38
|
}
|