observa-sdk 0.0.6 → 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 +543 -21
- package/dist/index.d.cts +129 -2
- package/dist/index.d.ts +129 -2
- package/dist/index.js +543 -21
- package/package.json +1 -1
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,
|
|
@@ -395,17 +847,40 @@ var Observa = class {
|
|
|
395
847
|
systemFingerprint: extracted.systemFingerprint ?? null,
|
|
396
848
|
...headers !== void 0 && { headers },
|
|
397
849
|
// Conversation tracking fields
|
|
398
|
-
...event.conversationId !== void 0 && {
|
|
850
|
+
...event.conversationId !== void 0 && {
|
|
851
|
+
conversationId: event.conversationId
|
|
852
|
+
},
|
|
399
853
|
...event.sessionId !== void 0 && { sessionId: event.sessionId },
|
|
400
854
|
...event.userId !== void 0 && { userId: event.userId },
|
|
401
|
-
...event.messageIndex !== void 0 && {
|
|
402
|
-
|
|
855
|
+
...event.messageIndex !== void 0 && {
|
|
856
|
+
messageIndex: event.messageIndex
|
|
857
|
+
},
|
|
858
|
+
...event.parentMessageId !== void 0 && {
|
|
859
|
+
parentMessageId: event.parentMessageId
|
|
860
|
+
}
|
|
403
861
|
};
|
|
404
862
|
console.log(
|
|
405
|
-
`[Observa] Trace data prepared,
|
|
863
|
+
`[Observa] Trace data prepared, converting to canonical events for ${traceId}, response length: ${fullResponse.length}`
|
|
406
864
|
);
|
|
407
|
-
|
|
408
|
-
|
|
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}`);
|
|
409
884
|
} catch (err) {
|
|
410
885
|
console.error("[Observa] Error capturing stream:", err);
|
|
411
886
|
if (err instanceof Error) {
|
|
@@ -417,15 +892,57 @@ var Observa = class {
|
|
|
417
892
|
}
|
|
418
893
|
}
|
|
419
894
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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
|
+
}
|
|
423
940
|
}
|
|
424
941
|
try {
|
|
425
942
|
const baseUrl = this.apiUrl.replace(/\/+$/, "");
|
|
426
|
-
const url = `${baseUrl}/api/v1/
|
|
943
|
+
const url = `${baseUrl}/api/v1/events/ingest`;
|
|
427
944
|
console.log(
|
|
428
|
-
`[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"}`
|
|
429
946
|
);
|
|
430
947
|
const controller = new AbortController();
|
|
431
948
|
const timeoutId = setTimeout(() => controller.abort(), 1e4);
|
|
@@ -436,7 +953,7 @@ var Observa = class {
|
|
|
436
953
|
Authorization: `Bearer ${this.apiKey}`,
|
|
437
954
|
"Content-Type": "application/json"
|
|
438
955
|
},
|
|
439
|
-
body: JSON.stringify(
|
|
956
|
+
body: JSON.stringify(events),
|
|
440
957
|
signal: controller.signal
|
|
441
958
|
});
|
|
442
959
|
clearTimeout(timeoutId);
|
|
@@ -455,9 +972,13 @@ var Observa = class {
|
|
|
455
972
|
`[Observa] Backend API error: ${response.status} ${response.statusText}`,
|
|
456
973
|
errorJson.error || errorText
|
|
457
974
|
);
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Observa API error: ${response.status} ${errorJson.error?.message || errorText}`
|
|
977
|
+
);
|
|
458
978
|
} else {
|
|
979
|
+
const result = await response.json().catch(() => ({}));
|
|
459
980
|
console.log(
|
|
460
|
-
`\u2705 [Observa]
|
|
981
|
+
`\u2705 [Observa] Events sent successfully - Trace ID: ${traceId}, Event count: ${result.event_count || events.length}`
|
|
461
982
|
);
|
|
462
983
|
}
|
|
463
984
|
} catch (fetchError) {
|
|
@@ -468,7 +989,7 @@ var Observa = class {
|
|
|
468
989
|
throw fetchError;
|
|
469
990
|
}
|
|
470
991
|
} catch (error) {
|
|
471
|
-
console.error("[Observa] Failed to send
|
|
992
|
+
console.error("[Observa] Failed to send events:", error);
|
|
472
993
|
if (error instanceof Error) {
|
|
473
994
|
console.error("[Observa] Error message:", error.message);
|
|
474
995
|
console.error("[Observa] Error name:", error.name);
|
|
@@ -481,6 +1002,7 @@ var Observa = class {
|
|
|
481
1002
|
console.error("[Observa] Error stack:", error.stack);
|
|
482
1003
|
}
|
|
483
1004
|
}
|
|
1005
|
+
throw error;
|
|
484
1006
|
}
|
|
485
1007
|
}
|
|
486
1008
|
};
|
package/package.json
CHANGED