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