@pauly4010/evalai-sdk 1.3.0

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.
@@ -0,0 +1,628 @@
1
+ "use strict";
2
+ /**
3
+ * Workflow Tracer SDK
4
+ * Multi-agent workflow instrumentation, decision tracking, and cost capture
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { WorkflowTracer } from '@pauly4010/evalai-sdk';
9
+ *
10
+ * const tracer = new WorkflowTracer(client, { organizationId: 123 });
11
+ *
12
+ * // Start a workflow
13
+ * const workflow = await tracer.startWorkflow('Customer Support Pipeline');
14
+ *
15
+ * // Record agent spans and handoffs
16
+ * const span1 = await tracer.startAgentSpan('RouterAgent', { input: query });
17
+ * await tracer.recordDecision({
18
+ * agent: 'RouterAgent',
19
+ * chosen: 'delegate_to_technical',
20
+ * alternatives: [{ action: 'delegate_to_billing', confidence: 0.3 }],
21
+ * reasoning: 'Query contains technical keywords'
22
+ * });
23
+ * await tracer.recordHandoff('RouterAgent', 'TechnicalAgent', { issue: 'API error' });
24
+ * await tracer.endAgentSpan(span1, { result: 'delegated' });
25
+ *
26
+ * // End workflow with final output
27
+ * await tracer.endWorkflow({ resolution: 'Issue resolved' });
28
+ * ```
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.WorkflowTracer = void 0;
32
+ exports.traceLangChainAgent = traceLangChainAgent;
33
+ exports.traceCrewAI = traceCrewAI;
34
+ exports.traceAutoGen = traceAutoGen;
35
+ exports.createWorkflowTracer = createWorkflowTracer;
36
+ exports.traceWorkflowStep = traceWorkflowStep;
37
+ const context_1 = require("./context");
38
+ // ============================================================================
39
+ // MAIN CLASS - WorkflowTracer
40
+ // ============================================================================
41
+ /**
42
+ * WorkflowTracer - Instrument multi-agent workflows with tracing, decision auditing, and cost tracking
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const tracer = new WorkflowTracer(client, { organizationId: 123 });
47
+ *
48
+ * // Simple workflow
49
+ * await tracer.startWorkflow('Data Processing Pipeline');
50
+ *
51
+ * const agentSpan = await tracer.startAgentSpan('DataAgent', { source: 'api' });
52
+ * // ... agent work ...
53
+ * await tracer.endAgentSpan(agentSpan, { processed: 100 });
54
+ *
55
+ * await tracer.endWorkflow({ status: 'success' });
56
+ * ```
57
+ */
58
+ class WorkflowTracer {
59
+ constructor(client, options = {}) {
60
+ this.currentWorkflow = null;
61
+ this.activeSpans = new Map();
62
+ this.handoffs = [];
63
+ this.decisions = [];
64
+ this.costs = [];
65
+ this.spanCounter = 0;
66
+ this.client = client;
67
+ this.options = {
68
+ organizationId: options.organizationId || client.getOrganizationId() || 0,
69
+ autoCalculateCost: options.autoCalculateCost ?? true,
70
+ tracePrefix: options.tracePrefix || 'workflow',
71
+ captureFullPayloads: options.captureFullPayloads ?? true,
72
+ debug: options.debug ?? false,
73
+ };
74
+ }
75
+ // ==========================================================================
76
+ // WORKFLOW LIFECYCLE
77
+ // ==========================================================================
78
+ /**
79
+ * Start a new workflow
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * const workflow = await tracer.startWorkflow('Customer Support Flow', {
84
+ * nodes: [
85
+ * { id: 'router', type: 'agent', name: 'RouterAgent' },
86
+ * { id: 'technical', type: 'agent', name: 'TechnicalAgent' },
87
+ * ],
88
+ * edges: [{ from: 'router', to: 'technical', condition: 'is_technical' }],
89
+ * entrypoint: 'router'
90
+ * });
91
+ * ```
92
+ */
93
+ async startWorkflow(name, definition, metadata) {
94
+ if (this.currentWorkflow) {
95
+ throw new Error('A workflow is already active. Call endWorkflow() first.');
96
+ }
97
+ const traceId = `${this.options.tracePrefix}-${Date.now()}-${this.generateId()}`;
98
+ const startedAt = new Date().toISOString();
99
+ // Create the trace
100
+ const trace = await this.client.traces.create({
101
+ name: `Workflow: ${name}`,
102
+ traceId,
103
+ organizationId: this.options.organizationId,
104
+ status: 'pending',
105
+ metadata: (0, context_1.mergeWithContext)({
106
+ workflowName: name,
107
+ definition,
108
+ ...metadata,
109
+ }),
110
+ });
111
+ this.currentWorkflow = {
112
+ id: 0, // Will be set after API call returns
113
+ traceId: trace.id,
114
+ name,
115
+ startedAt,
116
+ definition,
117
+ metadata,
118
+ };
119
+ // Reset state
120
+ this.handoffs = [];
121
+ this.decisions = [];
122
+ this.costs = [];
123
+ this.activeSpans.clear();
124
+ this.spanCounter = 0;
125
+ this.log('Started workflow', { name, traceId: trace.id });
126
+ return this.currentWorkflow;
127
+ }
128
+ /**
129
+ * End the current workflow
130
+ */
131
+ async endWorkflow(output, status = 'completed') {
132
+ if (!this.currentWorkflow) {
133
+ throw new Error('No active workflow. Call startWorkflow() first.');
134
+ }
135
+ const durationMs = Date.now() - new Date(this.currentWorkflow.startedAt).getTime();
136
+ // Calculate total cost
137
+ const totalCost = this.costs.reduce((sum, cost) => sum + parseFloat(cost.totalCost), 0);
138
+ // Update the trace with final status
139
+ // Note: We create a new trace entry with the same ID pattern to update status
140
+ const traceId = `${this.options.tracePrefix}-complete-${this.currentWorkflow.traceId}`;
141
+ await this.client.traces.create({
142
+ name: `Workflow: ${this.currentWorkflow.name}`,
143
+ traceId,
144
+ organizationId: this.options.organizationId,
145
+ status: status === 'completed' ? 'success' : 'error',
146
+ durationMs,
147
+ metadata: (0, context_1.mergeWithContext)({
148
+ workflowName: this.currentWorkflow.name,
149
+ output,
150
+ status,
151
+ totalCost: totalCost.toFixed(6),
152
+ handoffCount: this.handoffs.length,
153
+ decisionCount: this.decisions.length,
154
+ agentCount: new Set(this.handoffs.map(h => h.toAgent)).size + 1,
155
+ retryCount: this.costs.filter(c => c.isRetry).length,
156
+ handoffs: this.handoffs,
157
+ decisions: this.decisions,
158
+ costs: this.costs,
159
+ }),
160
+ });
161
+ this.log('Ended workflow', {
162
+ name: this.currentWorkflow.name,
163
+ status,
164
+ durationMs,
165
+ totalCost: totalCost.toFixed(6),
166
+ });
167
+ this.currentWorkflow = null;
168
+ }
169
+ // ==========================================================================
170
+ // AGENT SPANS
171
+ // ==========================================================================
172
+ /**
173
+ * Start an agent span within the workflow
174
+ *
175
+ * @example
176
+ * ```typescript
177
+ * const span = await tracer.startAgentSpan('RouterAgent', {
178
+ * input: userQuery
179
+ * });
180
+ * ```
181
+ */
182
+ async startAgentSpan(agentName, input, parentSpanId) {
183
+ if (!this.currentWorkflow) {
184
+ throw new Error('No active workflow. Call startWorkflow() first.');
185
+ }
186
+ const spanId = `span-${++this.spanCounter}-${this.generateId()}`;
187
+ const startTime = new Date().toISOString();
188
+ const spanContext = {
189
+ spanId,
190
+ agentName,
191
+ startTime,
192
+ parentSpanId,
193
+ metadata: input,
194
+ };
195
+ this.activeSpans.set(spanId, spanContext);
196
+ // Create span via API
197
+ await this.client.traces.createSpan(this.currentWorkflow.traceId, {
198
+ name: `Agent: ${agentName}`,
199
+ spanId,
200
+ parentSpanId,
201
+ startTime,
202
+ metadata: (0, context_1.mergeWithContext)({
203
+ agentName,
204
+ ...(this.options.captureFullPayloads ? { input } : {}),
205
+ }),
206
+ });
207
+ this.log('Started agent span', { agentName, spanId });
208
+ return spanContext;
209
+ }
210
+ /**
211
+ * End an agent span
212
+ */
213
+ async endAgentSpan(span, output, error) {
214
+ if (!this.currentWorkflow) {
215
+ throw new Error('No active workflow.');
216
+ }
217
+ const endTime = new Date().toISOString();
218
+ const durationMs = new Date(endTime).getTime() - new Date(span.startTime).getTime();
219
+ // Update span via API (create completion record)
220
+ await this.client.traces.createSpan(this.currentWorkflow.traceId, {
221
+ name: `Agent: ${span.agentName}`,
222
+ spanId: `${span.spanId}-end`,
223
+ parentSpanId: span.spanId,
224
+ startTime: span.startTime,
225
+ endTime,
226
+ durationMs,
227
+ metadata: (0, context_1.mergeWithContext)({
228
+ agentName: span.agentName,
229
+ ...(this.options.captureFullPayloads ? { output } : {}),
230
+ ...(error ? { error } : {}),
231
+ }),
232
+ });
233
+ this.activeSpans.delete(span.spanId);
234
+ this.log('Ended agent span', { agentName: span.agentName, spanId: span.spanId, durationMs });
235
+ }
236
+ // ==========================================================================
237
+ // HANDOFFS
238
+ // ==========================================================================
239
+ /**
240
+ * Record a handoff between agents
241
+ *
242
+ * @example
243
+ * ```typescript
244
+ * await tracer.recordHandoff(
245
+ * 'RouterAgent',
246
+ * 'TechnicalAgent',
247
+ * { issueType: 'api_error', priority: 'high' },
248
+ * 'delegation'
249
+ * );
250
+ * ```
251
+ */
252
+ async recordHandoff(fromAgent, toAgent, context, handoffType = 'delegation') {
253
+ if (!this.currentWorkflow) {
254
+ throw new Error('No active workflow. Call startWorkflow() first.');
255
+ }
256
+ const handoff = {
257
+ fromAgent,
258
+ toAgent,
259
+ handoffType,
260
+ context,
261
+ timestamp: new Date().toISOString(),
262
+ };
263
+ this.handoffs.push(handoff);
264
+ // Also create a span for the handoff
265
+ const spanId = `handoff-${this.handoffs.length}-${this.generateId()}`;
266
+ await this.client.traces.createSpan(this.currentWorkflow.traceId, {
267
+ name: `Handoff: ${fromAgent || 'start'} → ${toAgent}`,
268
+ spanId,
269
+ startTime: handoff.timestamp,
270
+ endTime: handoff.timestamp,
271
+ durationMs: 0,
272
+ metadata: (0, context_1.mergeWithContext)({
273
+ handoffType,
274
+ fromAgent,
275
+ toAgent,
276
+ context,
277
+ }),
278
+ });
279
+ this.log('Recorded handoff', { fromAgent, toAgent, handoffType });
280
+ }
281
+ // ==========================================================================
282
+ // DECISION AUDITING
283
+ // ==========================================================================
284
+ /**
285
+ * Record a decision made by an agent
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * await tracer.recordDecision({
290
+ * agent: 'RouterAgent',
291
+ * type: 'route',
292
+ * chosen: 'technical_support',
293
+ * alternatives: [
294
+ * { action: 'billing_support', confidence: 0.3, reasoning: 'No billing keywords' },
295
+ * { action: 'general_support', confidence: 0.1, reasoning: 'Fallback option' }
296
+ * ],
297
+ * reasoning: 'Query contains technical terms like "API", "error", "endpoint"',
298
+ * confidence: 85,
299
+ * contextFactors: ['keyword_match', 'user_history']
300
+ * });
301
+ * ```
302
+ */
303
+ async recordDecision(params) {
304
+ if (!this.currentWorkflow) {
305
+ throw new Error('No active workflow. Call startWorkflow() first.');
306
+ }
307
+ this.decisions.push(params);
308
+ // Create a span for the decision
309
+ const spanId = `decision-${this.decisions.length}-${this.generateId()}`;
310
+ const timestamp = new Date().toISOString();
311
+ await this.client.traces.createSpan(this.currentWorkflow.traceId, {
312
+ name: `Decision: ${params.agent} chose ${params.chosen}`,
313
+ spanId,
314
+ startTime: timestamp,
315
+ endTime: timestamp,
316
+ durationMs: 0,
317
+ metadata: (0, context_1.mergeWithContext)({
318
+ isDecisionPoint: true,
319
+ agentName: params.agent,
320
+ decisionType: params.type,
321
+ chosen: params.chosen,
322
+ alternatives: params.alternatives,
323
+ reasoning: params.reasoning,
324
+ confidence: params.confidence,
325
+ contextFactors: params.contextFactors,
326
+ inputContext: params.inputContext,
327
+ }),
328
+ });
329
+ this.log('Recorded decision', {
330
+ agent: params.agent,
331
+ type: params.type,
332
+ chosen: params.chosen,
333
+ confidence: params.confidence,
334
+ });
335
+ }
336
+ // ==========================================================================
337
+ // COST TRACKING
338
+ // ==========================================================================
339
+ /**
340
+ * Record cost for an LLM call or operation
341
+ *
342
+ * @example
343
+ * ```typescript
344
+ * await tracer.recordCost({
345
+ * provider: 'openai',
346
+ * model: 'gpt-4',
347
+ * inputTokens: 500,
348
+ * outputTokens: 200,
349
+ * category: 'llm',
350
+ * isRetry: false
351
+ * });
352
+ * ```
353
+ */
354
+ async recordCost(params) {
355
+ const totalTokens = params.inputTokens + params.outputTokens;
356
+ // Calculate cost based on known pricing (can be extended)
357
+ const pricing = this.getModelPricing(params.provider, params.model);
358
+ const inputCost = (params.inputTokens / 1000000) * pricing.inputPricePerMillion;
359
+ const outputCost = (params.outputTokens / 1000000) * pricing.outputPricePerMillion;
360
+ const totalCost = inputCost + outputCost;
361
+ const costRecord = {
362
+ ...params,
363
+ totalTokens,
364
+ category: params.category || 'llm',
365
+ inputCost: inputCost.toFixed(6),
366
+ outputCost: outputCost.toFixed(6),
367
+ totalCost: totalCost.toFixed(6),
368
+ };
369
+ this.costs.push(costRecord);
370
+ // Also record as a span if in an active workflow
371
+ if (this.currentWorkflow) {
372
+ const spanId = `cost-${this.costs.length}-${this.generateId()}`;
373
+ const timestamp = new Date().toISOString();
374
+ await this.client.traces.createSpan(this.currentWorkflow.traceId, {
375
+ name: `Cost: ${params.provider}/${params.model}`,
376
+ spanId,
377
+ startTime: timestamp,
378
+ endTime: timestamp,
379
+ durationMs: 0,
380
+ metadata: (0, context_1.mergeWithContext)({
381
+ costRecord,
382
+ }),
383
+ });
384
+ }
385
+ this.log('Recorded cost', {
386
+ provider: params.provider,
387
+ model: params.model,
388
+ totalTokens,
389
+ totalCost: costRecord.totalCost,
390
+ });
391
+ return costRecord;
392
+ }
393
+ /**
394
+ * Get total cost for the current workflow
395
+ */
396
+ getTotalCost() {
397
+ return this.costs.reduce((sum, cost) => sum + parseFloat(cost.totalCost), 0);
398
+ }
399
+ /**
400
+ * Get cost breakdown by category
401
+ */
402
+ getCostBreakdown() {
403
+ const breakdown = {
404
+ llm: 0,
405
+ tool: 0,
406
+ embedding: 0,
407
+ other: 0,
408
+ };
409
+ for (const cost of this.costs) {
410
+ const category = cost.category || 'other';
411
+ breakdown[category] += parseFloat(cost.totalCost);
412
+ }
413
+ return breakdown;
414
+ }
415
+ // ==========================================================================
416
+ // UTILITY METHODS
417
+ // ==========================================================================
418
+ /**
419
+ * Get known pricing for a model (can be extended or fetched from API)
420
+ */
421
+ getModelPricing(provider, model) {
422
+ // Default pricing (can be extended with API lookup)
423
+ const knownPricing = {
424
+ // OpenAI
425
+ 'openai/gpt-4': { inputPricePerMillion: 30.00, outputPricePerMillion: 60.00 },
426
+ 'openai/gpt-4-turbo': { inputPricePerMillion: 10.00, outputPricePerMillion: 30.00 },
427
+ 'openai/gpt-4o': { inputPricePerMillion: 5.00, outputPricePerMillion: 15.00 },
428
+ 'openai/gpt-4o-mini': { inputPricePerMillion: 0.15, outputPricePerMillion: 0.60 },
429
+ 'openai/gpt-3.5-turbo': { inputPricePerMillion: 0.50, outputPricePerMillion: 1.50 },
430
+ // Anthropic
431
+ 'anthropic/claude-3-opus': { inputPricePerMillion: 15.00, outputPricePerMillion: 75.00 },
432
+ 'anthropic/claude-3-sonnet': { inputPricePerMillion: 3.00, outputPricePerMillion: 15.00 },
433
+ 'anthropic/claude-3-haiku': { inputPricePerMillion: 0.25, outputPricePerMillion: 1.25 },
434
+ 'anthropic/claude-3.5-sonnet': { inputPricePerMillion: 3.00, outputPricePerMillion: 15.00 },
435
+ // Google
436
+ 'google/gemini-pro': { inputPricePerMillion: 0.50, outputPricePerMillion: 1.50 },
437
+ 'google/gemini-1.5-pro': { inputPricePerMillion: 3.50, outputPricePerMillion: 10.50 },
438
+ 'google/gemini-1.5-flash': { inputPricePerMillion: 0.075, outputPricePerMillion: 0.30 },
439
+ };
440
+ const key = `${provider}/${model}`;
441
+ return knownPricing[key] || { inputPricePerMillion: 1.00, outputPricePerMillion: 3.00 };
442
+ }
443
+ /**
444
+ * Generate a unique ID
445
+ */
446
+ generateId() {
447
+ return Math.random().toString(36).substring(2, 11);
448
+ }
449
+ /**
450
+ * Log if debug mode is enabled
451
+ */
452
+ log(message, data) {
453
+ if (this.options.debug) {
454
+ console.log(`[WorkflowTracer] ${message}`, data || '');
455
+ }
456
+ }
457
+ /**
458
+ * Get current workflow context
459
+ */
460
+ getCurrentWorkflow() {
461
+ return this.currentWorkflow;
462
+ }
463
+ /**
464
+ * Check if a workflow is active
465
+ */
466
+ isWorkflowActive() {
467
+ return this.currentWorkflow !== null;
468
+ }
469
+ /**
470
+ * Get all recorded handoffs
471
+ */
472
+ getHandoffs() {
473
+ return [...this.handoffs];
474
+ }
475
+ /**
476
+ * Get all recorded decisions
477
+ */
478
+ getDecisions() {
479
+ return [...this.decisions];
480
+ }
481
+ /**
482
+ * Get all recorded costs
483
+ */
484
+ getCosts() {
485
+ return [...this.costs];
486
+ }
487
+ }
488
+ exports.WorkflowTracer = WorkflowTracer;
489
+ // ============================================================================
490
+ // FRAMEWORK INTEGRATIONS
491
+ // ============================================================================
492
+ /**
493
+ * Wrap a LangChain agent for automatic workflow tracing
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * import { AgentExecutor } from 'langchain/agents';
498
+ *
499
+ * const executor = new AgentExecutor({ ... });
500
+ * const tracedExecutor = traceLangChainAgent(executor, tracer);
501
+ *
502
+ * const result = await tracedExecutor.invoke({ input: 'Hello' });
503
+ * ```
504
+ */
505
+ function traceLangChainAgent(executor, tracer, options = {}) {
506
+ const agentName = options.agentName || 'LangChainAgent';
507
+ const originalInvoke = executor.invoke?.bind(executor);
508
+ const originalCall = executor.call?.bind(executor);
509
+ if (originalInvoke) {
510
+ executor.invoke = async (input, config) => {
511
+ const span = await tracer.startAgentSpan(agentName, { input });
512
+ try {
513
+ const result = await originalInvoke(input, config);
514
+ await tracer.endAgentSpan(span, { output: result });
515
+ return result;
516
+ }
517
+ catch (error) {
518
+ await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
519
+ throw error;
520
+ }
521
+ };
522
+ }
523
+ if (originalCall) {
524
+ executor.call = async (input, config) => {
525
+ const span = await tracer.startAgentSpan(agentName, { input });
526
+ try {
527
+ const result = await originalCall(input, config);
528
+ await tracer.endAgentSpan(span, { output: result });
529
+ return result;
530
+ }
531
+ catch (error) {
532
+ await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
533
+ throw error;
534
+ }
535
+ };
536
+ }
537
+ return executor;
538
+ }
539
+ /**
540
+ * Create a traced wrapper for CrewAI crews
541
+ *
542
+ * @example
543
+ * ```typescript
544
+ * const tracedCrew = traceCrewAI(crew, tracer, {
545
+ * crewName: 'ResearchCrew'
546
+ * });
547
+ *
548
+ * const result = await tracedCrew.kickoff({ topic: 'AI Safety' });
549
+ * ```
550
+ */
551
+ function traceCrewAI(crew, tracer, options = {}) {
552
+ const crewName = options.crewName || 'CrewAI';
553
+ const originalKickoff = crew.kickoff?.bind(crew);
554
+ if (originalKickoff) {
555
+ crew.kickoff = async (input) => {
556
+ await tracer.startWorkflow(`${crewName} Execution`);
557
+ const span = await tracer.startAgentSpan(crewName, { input });
558
+ try {
559
+ const result = await originalKickoff(input);
560
+ await tracer.endAgentSpan(span, { output: result });
561
+ await tracer.endWorkflow({ result }, 'completed');
562
+ return result;
563
+ }
564
+ catch (error) {
565
+ await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
566
+ await tracer.endWorkflow({ error: error instanceof Error ? error.message : String(error) }, 'failed');
567
+ throw error;
568
+ }
569
+ };
570
+ }
571
+ return crew;
572
+ }
573
+ /**
574
+ * Create a traced wrapper for AutoGen conversations
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * const tracedConversation = traceAutoGen(conversation, tracer, {
579
+ * conversationName: 'CodeReview'
580
+ * });
581
+ * ```
582
+ */
583
+ function traceAutoGen(conversation, tracer, options = {}) {
584
+ const conversationName = options.conversationName || 'AutoGenConversation';
585
+ const originalInitiateChat = conversation.initiate_chat?.bind(conversation);
586
+ if (originalInitiateChat) {
587
+ conversation.initiate_chat = async (...args) => {
588
+ await tracer.startWorkflow(`${conversationName}`);
589
+ const span = await tracer.startAgentSpan(conversationName, { args });
590
+ try {
591
+ const result = await originalInitiateChat(...args);
592
+ await tracer.endAgentSpan(span, { output: result });
593
+ await tracer.endWorkflow({ result }, 'completed');
594
+ return result;
595
+ }
596
+ catch (error) {
597
+ await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
598
+ await tracer.endWorkflow({ error: error instanceof Error ? error.message : String(error) }, 'failed');
599
+ throw error;
600
+ }
601
+ };
602
+ }
603
+ return conversation;
604
+ }
605
+ // ============================================================================
606
+ // UTILITY FUNCTIONS
607
+ // ============================================================================
608
+ /**
609
+ * Create a workflow tracer from an existing client
610
+ */
611
+ function createWorkflowTracer(client, options) {
612
+ return new WorkflowTracer(client, options);
613
+ }
614
+ /**
615
+ * Helper to trace an async function as a workflow step
616
+ */
617
+ async function traceWorkflowStep(tracer, agentName, fn, input) {
618
+ const span = await tracer.startAgentSpan(agentName, input);
619
+ try {
620
+ const result = await fn();
621
+ await tracer.endAgentSpan(span, { result });
622
+ return result;
623
+ }
624
+ catch (error) {
625
+ await tracer.endAgentSpan(span, undefined, error instanceof Error ? error.message : String(error));
626
+ throw error;
627
+ }
628
+ }