@cloudbase/agent-observability 1.0.1-alpha.9

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/src/index.ts ADDED
@@ -0,0 +1,775 @@
1
+ /**
2
+ * Observability - OpenTelemetry-based tracing with OpenInference semantic conventions
3
+ *
4
+ * @packageDocumentation
5
+ */
6
+
7
+ import { trace, context, TimeInput, SpanStatusCode, Span, SpanContext } from "@opentelemetry/api";
8
+
9
+ import {
10
+ createObservationAttributes,
11
+ createTraceAttributes,
12
+ } from "./core/attributes.js";
13
+ import {
14
+ ObservationSpan,
15
+ ObservationLLM,
16
+ ObservationEmbedding,
17
+ ObservationAgent,
18
+ ObservationTool,
19
+ ObservationChain,
20
+ ObservationRetriever,
21
+ ObservationReranker,
22
+ ObservationEvaluator,
23
+ ObservationGuardrail,
24
+ type Observation,
25
+ } from "./core/spanWrapper.js";
26
+ import { getTracer } from "./core/tracerProvider.js";
27
+ import {
28
+ ObservationType,
29
+ ObservationLevel,
30
+ BaseSpanAttributes,
31
+ LLMAttributes,
32
+ ToolAttributes,
33
+ AgentAttributes,
34
+ ChainAttributes,
35
+ RetrieverAttributes,
36
+ RerankerAttributes,
37
+ EvaluatorAttributes,
38
+ GuardrailAttributes,
39
+ EmbeddingAttributes,
40
+ ObservationAttributes,
41
+ TraceAttributes,
42
+ } from "./types.js";
43
+
44
+ // Export types
45
+ export type {
46
+ ObservationType,
47
+ ObservationLevel,
48
+ BaseSpanAttributes,
49
+ LLMAttributes,
50
+ ToolAttributes,
51
+ AgentAttributes,
52
+ ChainAttributes,
53
+ RetrieverAttributes,
54
+ RerankerAttributes,
55
+ EvaluatorAttributes,
56
+ GuardrailAttributes,
57
+ EmbeddingAttributes,
58
+ ObservationAttributes,
59
+ TraceAttributes,
60
+ };
61
+
62
+ // Export observation classes
63
+ export {
64
+ ObservationSpan,
65
+ ObservationLLM,
66
+ ObservationEmbedding,
67
+ ObservationAgent,
68
+ ObservationTool,
69
+ ObservationChain,
70
+ ObservationRetriever,
71
+ ObservationReranker,
72
+ ObservationEvaluator,
73
+ ObservationGuardrail,
74
+ };
75
+
76
+ // Export observation union type
77
+ export type { Observation };
78
+
79
+ // Export core functions
80
+ export {
81
+ createTraceAttributes,
82
+ createObservationAttributes,
83
+ } from "./core/attributes.js";
84
+ export {
85
+ setTracerProvider,
86
+ getTracerProvider,
87
+ getTracer,
88
+ } from "./core/tracerProvider.js";
89
+
90
+ /**
91
+ * Options for starting observations (spans).
92
+ *
93
+ * @public
94
+ */
95
+ export type StartObservationOptions = {
96
+ /** Custom start time for the observation */
97
+ startTime?: Date;
98
+ /** Parent span context to attach this observation to */
99
+ parentSpanContext?: SpanContext;
100
+ };
101
+
102
+ /**
103
+ * Options for startObservation function.
104
+ *
105
+ * @public
106
+ */
107
+ export type StartObservationOpts = StartObservationOptions & {
108
+ /** Type of observation to create. Defaults to 'span' */
109
+ asType?: ObservationType;
110
+ };
111
+
112
+ /**
113
+ * Creates an OpenTelemetry span with the AG-Kit tracer.
114
+ *
115
+ * @param params - Parameters for span creation
116
+ * @returns The created OpenTelemetry span
117
+ * @internal
118
+ */
119
+ function createOtelSpan(params: {
120
+ name: string;
121
+ startTime?: TimeInput;
122
+ parentSpanContext?: SpanContext;
123
+ }): Span {
124
+ return getTracer().startSpan(
125
+ params.name,
126
+ { startTime: params.startTime },
127
+ createParentContext(params.parentSpanContext),
128
+ );
129
+ }
130
+
131
+ /**
132
+ * Creates a parent context from a span context.
133
+ *
134
+ * @param parentSpanContext - The span context to use as parent
135
+ * @returns The created context or undefined if no parent provided
136
+ * @internal
137
+ */
138
+ function createParentContext(
139
+ parentSpanContext?: SpanContext,
140
+ ): ReturnType<typeof trace.setSpanContext> | undefined {
141
+ if (!parentSpanContext) return;
142
+ return trace.setSpanContext(context.active(), parentSpanContext);
143
+ }
144
+
145
+ // Function overloads for proper type inference
146
+ // Generic overload for dynamic asType (returns Observation union)
147
+ export function startObservation(
148
+ name: string,
149
+ attributes?: BaseSpanAttributes,
150
+ options?: StartObservationOpts & { asType?: ObservationType },
151
+ ): Observation;
152
+
153
+ // Type-specific overloads for precise type inference
154
+ export function startObservation(
155
+ name: string,
156
+ attributes: LLMAttributes,
157
+ options: StartObservationOpts & { asType: "llm" },
158
+ ): ObservationLLM;
159
+ export function startObservation(
160
+ name: string,
161
+ attributes: EmbeddingAttributes,
162
+ options: StartObservationOpts & { asType: "embedding" },
163
+ ): ObservationEmbedding;
164
+ export function startObservation(
165
+ name: string,
166
+ attributes: AgentAttributes,
167
+ options: StartObservationOpts & { asType: "agent" },
168
+ ): ObservationAgent;
169
+ export function startObservation(
170
+ name: string,
171
+ attributes: ToolAttributes,
172
+ options: StartObservationOpts & { asType: "tool" },
173
+ ): ObservationTool;
174
+ export function startObservation(
175
+ name: string,
176
+ attributes: ChainAttributes,
177
+ options: StartObservationOpts & { asType: "chain" },
178
+ ): ObservationChain;
179
+ export function startObservation(
180
+ name: string,
181
+ attributes: RetrieverAttributes,
182
+ options: StartObservationOpts & { asType: "retriever" },
183
+ ): ObservationRetriever;
184
+ export function startObservation(
185
+ name: string,
186
+ attributes: RerankerAttributes,
187
+ options: StartObservationOpts & { asType: "reranker" },
188
+ ): ObservationReranker;
189
+ export function startObservation(
190
+ name: string,
191
+ attributes: EvaluatorAttributes,
192
+ options: StartObservationOpts & { asType: "evaluator" },
193
+ ): ObservationEvaluator;
194
+ export function startObservation(
195
+ name: string,
196
+ attributes: GuardrailAttributes,
197
+ options: StartObservationOpts & { asType: "guardrail" },
198
+ ): ObservationGuardrail;
199
+ export function startObservation(
200
+ name: string,
201
+ attributes?: BaseSpanAttributes,
202
+ options?: StartObservationOpts & { asType?: "span" },
203
+ ): ObservationSpan;
204
+
205
+ /**
206
+ * Creates and starts a new AG-Kit observation.
207
+ *
208
+ * Supports multiple observation types with full TypeScript type safety:
209
+ * - **span**: General-purpose operations (default)
210
+ * - **llm**: LLM calls and AI model interactions
211
+ * - **embedding**: Text embedding and vector operations
212
+ * - **agent**: AI agent workflows
213
+ * - **tool**: Individual tool calls
214
+ * - **chain**: Multi-step processes
215
+ * - **retriever**: Document retrieval
216
+ * - **reranker**: Result reranking
217
+ * - **evaluator**: Quality assessment
218
+ * - **guardrail**: Safety checks
219
+ *
220
+ * @param name - Descriptive name for the observation
221
+ * @param attributes - Type-specific attributes
222
+ * @param options - Configuration options
223
+ * @returns Strongly-typed observation object
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * import { startObservation } from './observability';
228
+ *
229
+ * // LLM observation
230
+ * const llm = startObservation('openai-gpt-4', {
231
+ * input: [{ role: 'user', content: 'Hello' }],
232
+ * model: 'gpt-4',
233
+ * modelParameters: { temperature: 0.7 }
234
+ * }, { asType: 'llm' });
235
+ *
236
+ * // Tool observation
237
+ * const tool = startObservation('weather-api', {
238
+ * input: { location: 'SF' }
239
+ * }, { asType: 'tool' });
240
+ *
241
+ * // Chain observation
242
+ * const chain = startObservation('rag-pipeline', {
243
+ * input: { question: 'What is AI?' }
244
+ * }, { asType: 'chain' });
245
+ * ```
246
+ *
247
+ * @public
248
+ */
249
+ export function startObservation(
250
+ name: string,
251
+ attributes?:
252
+ | BaseSpanAttributes
253
+ | LLMAttributes
254
+ | EmbeddingAttributes
255
+ | AgentAttributes
256
+ | ToolAttributes
257
+ | ChainAttributes
258
+ | RetrieverAttributes
259
+ | RerankerAttributes
260
+ | EvaluatorAttributes
261
+ | GuardrailAttributes,
262
+ options?: StartObservationOpts,
263
+ ): Observation {
264
+ const { asType = "span", ...observationOptions } = options || {};
265
+
266
+ const otelSpan = createOtelSpan({
267
+ name,
268
+ ...observationOptions,
269
+ });
270
+
271
+ switch (asType) {
272
+ case "llm":
273
+ return new ObservationLLM({
274
+ otelSpan,
275
+ attributes: attributes as LLMAttributes,
276
+ });
277
+
278
+ case "embedding":
279
+ return new ObservationEmbedding({
280
+ otelSpan,
281
+ attributes: attributes as EmbeddingAttributes,
282
+ });
283
+
284
+ case "agent":
285
+ return new ObservationAgent({
286
+ otelSpan,
287
+ attributes: attributes as AgentAttributes,
288
+ });
289
+
290
+ case "tool":
291
+ return new ObservationTool({
292
+ otelSpan,
293
+ attributes: attributes as ToolAttributes,
294
+ });
295
+
296
+ case "chain":
297
+ return new ObservationChain({
298
+ otelSpan,
299
+ attributes: attributes as ChainAttributes,
300
+ });
301
+
302
+ case "retriever":
303
+ return new ObservationRetriever({
304
+ otelSpan,
305
+ attributes: attributes as RetrieverAttributes,
306
+ });
307
+
308
+ case "reranker":
309
+ return new ObservationReranker({
310
+ otelSpan,
311
+ attributes: attributes as RerankerAttributes,
312
+ });
313
+
314
+ case "evaluator":
315
+ return new ObservationEvaluator({
316
+ otelSpan,
317
+ attributes: attributes as EvaluatorAttributes,
318
+ });
319
+
320
+ case "guardrail":
321
+ return new ObservationGuardrail({
322
+ otelSpan,
323
+ attributes: attributes as GuardrailAttributes,
324
+ });
325
+
326
+ case "span":
327
+ default:
328
+ return new ObservationSpan({
329
+ otelSpan,
330
+ attributes: attributes as BaseSpanAttributes,
331
+ });
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Updates the currently active trace with new attributes.
337
+ *
338
+ * @param attributes - Trace attributes to set
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * import { updateActiveTrace } from './observability';
343
+ *
344
+ * updateActiveTrace({
345
+ * name: 'user-workflow',
346
+ * userId: 'user-123',
347
+ * tags: ['production']
348
+ * });
349
+ * ```
350
+ *
351
+ * @public
352
+ */
353
+ export function updateActiveTrace(attributes: TraceAttributes) {
354
+ const span = trace.getActiveSpan();
355
+
356
+ if (!span) {
357
+ console.warn(
358
+ "[Observability] No active OTEL span in context. Skipping trace update.",
359
+ );
360
+ return;
361
+ }
362
+
363
+ span.setAttributes(createTraceAttributes(attributes));
364
+ }
365
+
366
+ /**
367
+ * Gets the current active trace ID.
368
+ *
369
+ * @returns The trace ID of the currently active span, or undefined
370
+ *
371
+ * @public
372
+ */
373
+ export function getActiveTraceId(): string | undefined {
374
+ return trace.getActiveSpan()?.spanContext().traceId;
375
+ }
376
+
377
+ /**
378
+ * Gets the current active observation ID.
379
+ *
380
+ * @returns The span ID of the currently active span, or undefined
381
+ *
382
+ * @public
383
+ */
384
+ export function getActiveSpanId(): string | undefined {
385
+ return trace.getActiveSpan()?.spanContext().spanId;
386
+ }
387
+
388
+ // ============================================================================
389
+ // Active Observation Functions
390
+ // ============================================================================
391
+
392
+ /**
393
+ * Options for startActiveObservation.
394
+ *
395
+ * @public
396
+ */
397
+ export type StartActiveObservationOpts = StartObservationOpts & {
398
+ /** Whether to automatically end the observation when the function exits. Default: true */
399
+ endOnExit?: boolean;
400
+ };
401
+
402
+ /**
403
+ * Wraps a Promise to automatically end the span when it resolves/rejects.
404
+ *
405
+ * @param promise - The promise to wrap
406
+ * @param span - The OpenTelemetry span
407
+ * @param endOnExit - Whether to end the span on exit
408
+ * @returns The wrapped promise
409
+ * @internal
410
+ */
411
+ function wrapPromise<T>(
412
+ promise: Promise<T>,
413
+ span: Span,
414
+ endOnExit: boolean | undefined,
415
+ ): Promise<T> {
416
+ return promise.then(
417
+ (value) => {
418
+ if (endOnExit !== false) {
419
+ span.end();
420
+ }
421
+ return value;
422
+ },
423
+ (err: unknown) => {
424
+ span.setStatus({
425
+ code: SpanStatusCode.ERROR,
426
+ message: err instanceof Error ? err.message : "Unknown error",
427
+ });
428
+ if (endOnExit !== false) {
429
+ span.end();
430
+ }
431
+ throw err;
432
+ },
433
+ );
434
+ }
435
+
436
+ // Function overloads for startActiveObservation
437
+ export function startActiveObservation<
438
+ F extends (observation: ObservationSpan) => unknown,
439
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
440
+ export function startActiveObservation<
441
+ F extends (observation: ObservationLLM) => unknown,
442
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
443
+ export function startActiveObservation<
444
+ F extends (observation: ObservationEmbedding) => unknown,
445
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
446
+ export function startActiveObservation<
447
+ F extends (observation: ObservationAgent) => unknown,
448
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
449
+ export function startActiveObservation<
450
+ F extends (observation: ObservationTool) => unknown,
451
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
452
+ export function startActiveObservation<
453
+ F extends (observation: ObservationChain) => unknown,
454
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
455
+ export function startActiveObservation<
456
+ F extends (observation: ObservationRetriever) => unknown,
457
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
458
+ export function startActiveObservation<
459
+ F extends (observation: ObservationReranker) => unknown,
460
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
461
+ export function startActiveObservation<
462
+ F extends (observation: ObservationEvaluator) => unknown,
463
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
464
+ export function startActiveObservation<
465
+ F extends (observation: ObservationGuardrail) => unknown,
466
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F>;
467
+
468
+ /**
469
+ * Creates an observation with automatic lifecycle management.
470
+ *
471
+ * This function creates an observation and executes a function with that observation
472
+ * as a parameter. The observation is automatically ended when the function completes
473
+ * (unless `endOnExit` is set to `false`).
474
+ *
475
+ * Supports both synchronous and asynchronous functions, with automatic error handling.
476
+ *
477
+ * @param name - Descriptive name for the observation
478
+ * @param fn - Function to execute with the observation
479
+ * @param options - Configuration options
480
+ * @returns The result of the function
481
+ *
482
+ * @example
483
+ * ```typescript
484
+ * import { startActiveObservation } from './observability';
485
+ *
486
+ * // Synchronous function
487
+ * const result = startActiveObservation('data-processing', (span) => {
488
+ * span.update({ input: { data: [1, 2, 3] } });
489
+ * const processed = data.map(x => x * 2);
490
+ * span.update({ output: { result: processed } });
491
+ * return processed;
492
+ * }, { asType: 'span' });
493
+ *
494
+ * // Asynchronous function
495
+ * const embeddings = await startActiveObservation(
496
+ * 'text-embeddings',
497
+ * async (embedding) => {
498
+ * embedding.update({
499
+ * input: { texts: ['Hello', 'World'] },
500
+ * model: 'text-embedding-ada-002'
501
+ * });
502
+ *
503
+ * const vectors = await generateEmbeddings(texts);
504
+ *
505
+ * embedding.update({ output: { embeddings: vectors } });
506
+ * return vectors;
507
+ * },
508
+ * { asType: 'embedding' }
509
+ * );
510
+ *
511
+ * // Disable automatic ending (for long-running operations)
512
+ * startActiveObservation(
513
+ * 'background-task',
514
+ * (span) => {
515
+ * span.update({ input: { taskId: '123' } });
516
+ * startBackgroundProcess(span);
517
+ * return 'started';
518
+ * },
519
+ * { asType: 'span', endOnExit: false }
520
+ * );
521
+ * ```
522
+ *
523
+ * @see {@link startObservation} for manual observation lifecycle management
524
+ * @see {@link observe} for decorator-style function wrapping
525
+ *
526
+ * @public
527
+ */
528
+ export function startActiveObservation<
529
+ F extends (observation: Observation) => unknown,
530
+ >(name: string, fn: F, options?: StartActiveObservationOpts): ReturnType<F> {
531
+ const { asType = "span", endOnExit, ...observationOptions } = options || {};
532
+
533
+ return getTracer().startActiveSpan(
534
+ name,
535
+ { startTime: observationOptions?.startTime },
536
+ createParentContext(observationOptions?.parentSpanContext) ??
537
+ context.active(),
538
+ (span) => {
539
+ try {
540
+ let observation: Observation;
541
+
542
+ switch (asType) {
543
+ case "llm":
544
+ observation = new ObservationLLM({ otelSpan: span });
545
+ break;
546
+ case "embedding":
547
+ observation = new ObservationEmbedding({ otelSpan: span });
548
+ break;
549
+ case "agent":
550
+ observation = new ObservationAgent({ otelSpan: span });
551
+ break;
552
+ case "tool":
553
+ observation = new ObservationTool({ otelSpan: span });
554
+ break;
555
+ case "chain":
556
+ observation = new ObservationChain({ otelSpan: span });
557
+ break;
558
+ case "retriever":
559
+ observation = new ObservationRetriever({ otelSpan: span });
560
+ break;
561
+ case "reranker":
562
+ observation = new ObservationReranker({ otelSpan: span });
563
+ break;
564
+ case "evaluator":
565
+ observation = new ObservationEvaluator({ otelSpan: span });
566
+ break;
567
+ case "guardrail":
568
+ observation = new ObservationGuardrail({ otelSpan: span });
569
+ break;
570
+ case "span":
571
+ default:
572
+ observation = new ObservationSpan({ otelSpan: span });
573
+ }
574
+
575
+ const result = fn(observation as Parameters<F>[0]);
576
+
577
+ if (result instanceof Promise) {
578
+ return wrapPromise(
579
+ result,
580
+ span,
581
+ endOnExit,
582
+ ) as ReturnType<F>;
583
+ } else {
584
+ if (endOnExit !== false) {
585
+ span.end();
586
+ }
587
+ return result as ReturnType<F>;
588
+ }
589
+ } catch (err) {
590
+ span.setStatus({
591
+ code: SpanStatusCode.ERROR,
592
+ message: err instanceof Error ? err.message : "Unknown error",
593
+ });
594
+ if (endOnExit !== false) {
595
+ span.end();
596
+ }
597
+ throw err;
598
+ }
599
+ },
600
+ );
601
+ }
602
+
603
+ /**
604
+ * Updates the currently active observation with new attributes.
605
+ *
606
+ * @param attributes - Observation attributes to set
607
+ *
608
+ * @example
609
+ * ```typescript
610
+ * import { updateActiveObservation } from './observability';
611
+ *
612
+ * // Within an active observation context
613
+ * updateActiveObservation({
614
+ * metadata: { stage: 'processing' }
615
+ * });
616
+ * ```
617
+ *
618
+ * @public
619
+ */
620
+ export function updateActiveObservation(
621
+ attributes: BaseSpanAttributes,
622
+ ): void {
623
+ const span = trace.getActiveSpan();
624
+
625
+ if (!span) {
626
+ console.warn(
627
+ "[Observability] No active OTEL span in context. Skipping observation update.",
628
+ );
629
+ return;
630
+ }
631
+
632
+ span.setAttributes(createObservationAttributes("span", attributes));
633
+ }
634
+
635
+ // ============================================================================
636
+ // Decorator Function
637
+ // ============================================================================
638
+
639
+ /**
640
+ * Options for the observe decorator.
641
+ *
642
+ * @public
643
+ */
644
+ export type ObserveOptions = Omit<StartObservationOpts, "name"> & {
645
+ /** Whether to capture function arguments as input. Default: true */
646
+ captureInput?: boolean;
647
+ /** Whether to capture return value as output. Default: true */
648
+ captureOutput?: boolean;
649
+ };
650
+
651
+ /**
652
+ * Captures function arguments for observability input.
653
+ *
654
+ * @param args - Function arguments to capture
655
+ * @returns Serialized arguments
656
+ * @internal
657
+ */
658
+ function _captureArguments(args: unknown[]): Record<string, unknown> {
659
+ if (args.length === 0) return {};
660
+ if (args.length === 1) return { arg: args[0] };
661
+ return { args };
662
+ }
663
+
664
+ /**
665
+ * Decorator function to add observability to any function.
666
+ *
667
+ * Wraps a function with automatic observation creation, input/output capture,
668
+ * and lifecycle management. The observation is automatically ended when the
669
+ * function completes.
670
+ *
671
+ * @param fn - Function to wrap
672
+ * @param options - Configuration options
673
+ * @returns Wrapped function with observability
674
+ *
675
+ * @example
676
+ * ```typescript
677
+ * import { observe } from './observability';
678
+ *
679
+ * // Wrap an existing function
680
+ * const fetchData = observe(async (url: string) => {
681
+ * const response = await fetch(url);
682
+ * return response.json();
683
+ * }, { asType: 'tool' });
684
+ *
685
+ * // Wrap with custom name
686
+ * const processPayment = observe(
687
+ * async (amount: number, currency: string) => {
688
+ * return await paymentGateway.charge(amount, currency);
689
+ * },
690
+ * { name: 'payment-gateway-call', asType: 'tool' }
691
+ * );
692
+ *
693
+ * // Class method decoration
694
+ * class UserService {
695
+ * @observe({ asType: 'chain' })
696
+ * async getUser(id: string) {
697
+ * return await db.users.find(id);
698
+ * }
699
+ * }
700
+ * ```
701
+ *
702
+ * @public
703
+ */
704
+ export function observe<T extends (...args: any[]) => any>(
705
+ fn: T,
706
+ options: ObserveOptions = {},
707
+ ): T {
708
+ const {
709
+ asType = "span",
710
+ captureInput = true,
711
+ captureOutput = true,
712
+ ...observationOptions
713
+ } = options;
714
+
715
+ const wrappedFunction = function (
716
+ this: any,
717
+ ...args: Parameters<T>
718
+ ): ReturnType<T> {
719
+ const name = fn.name || "anonymous-function";
720
+
721
+ // Prepare input data
722
+ const inputData = captureInput ? _captureArguments(args) : undefined;
723
+
724
+ // Create the observation
725
+ const observation = startObservation(
726
+ name,
727
+ inputData ? { input: inputData } : {},
728
+ {
729
+ ...observationOptions,
730
+ asType: asType as "span",
731
+ },
732
+ );
733
+
734
+ // Set the observation span as active in the context
735
+ const activeContext = trace.setSpan(context.active(), observation.otelSpan);
736
+
737
+ // Execute the function within the observation context
738
+ const result = context.with(activeContext, () => fn.apply(this, args));
739
+
740
+ // Handle promises
741
+ if (result instanceof Promise) {
742
+ return result.then(
743
+ (value) => {
744
+ if (captureOutput) {
745
+ observation.update({ output: value });
746
+ }
747
+ observation.end();
748
+ return value;
749
+ },
750
+ (err: unknown) => {
751
+ observation.update({
752
+ level: "ERROR",
753
+ statusMessage: err instanceof Error ? err.message : "Unknown error",
754
+ });
755
+ observation.end();
756
+ throw err;
757
+ },
758
+ ) as ReturnType<T>;
759
+ }
760
+
761
+ // Handle synchronous functions
762
+ if (captureOutput) {
763
+ observation.update({ output: result });
764
+ }
765
+ observation.end();
766
+
767
+ return result as ReturnType<T>;
768
+ };
769
+
770
+ // Preserve function properties
771
+ Object.defineProperty(wrappedFunction, "name", { value: fn.name });
772
+ Object.defineProperty(wrappedFunction, "length", { value: fn.length });
773
+
774
+ return wrappedFunction as T;
775
+ }