@voice-kit/core 0.1.2 → 0.1.3

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.
Files changed (53) hide show
  1. package/dist/index.cjs +2137 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1466 -4
  4. package/dist/index.d.ts +1466 -4
  5. package/dist/index.js +2102 -1
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -31
  8. package/dist/audio.cjs +0 -533
  9. package/dist/audio.cjs.map +0 -1
  10. package/dist/audio.d.cts +0 -260
  11. package/dist/audio.d.ts +0 -260
  12. package/dist/audio.js +0 -514
  13. package/dist/audio.js.map +0 -1
  14. package/dist/compliance.cjs +0 -343
  15. package/dist/compliance.cjs.map +0 -1
  16. package/dist/compliance.d.cts +0 -163
  17. package/dist/compliance.d.ts +0 -163
  18. package/dist/compliance.js +0 -335
  19. package/dist/compliance.js.map +0 -1
  20. package/dist/errors.cjs +0 -284
  21. package/dist/errors.cjs.map +0 -1
  22. package/dist/errors.d.cts +0 -100
  23. package/dist/errors.d.ts +0 -100
  24. package/dist/errors.js +0 -262
  25. package/dist/errors.js.map +0 -1
  26. package/dist/index-D3KfRXMP.d.cts +0 -319
  27. package/dist/index-D3KfRXMP.d.ts +0 -319
  28. package/dist/memory.cjs +0 -121
  29. package/dist/memory.cjs.map +0 -1
  30. package/dist/memory.d.cts +0 -29
  31. package/dist/memory.d.ts +0 -29
  32. package/dist/memory.js +0 -115
  33. package/dist/memory.js.map +0 -1
  34. package/dist/observability.cjs +0 -229
  35. package/dist/observability.cjs.map +0 -1
  36. package/dist/observability.d.cts +0 -122
  37. package/dist/observability.d.ts +0 -122
  38. package/dist/observability.js +0 -222
  39. package/dist/observability.js.map +0 -1
  40. package/dist/stt.cjs +0 -828
  41. package/dist/stt.cjs.map +0 -1
  42. package/dist/stt.d.cts +0 -308
  43. package/dist/stt.d.ts +0 -308
  44. package/dist/stt.js +0 -815
  45. package/dist/stt.js.map +0 -1
  46. package/dist/telephony.errors-BQYr6-vl.d.cts +0 -80
  47. package/dist/telephony.errors-C0-nScrF.d.ts +0 -80
  48. package/dist/tts.cjs +0 -429
  49. package/dist/tts.cjs.map +0 -1
  50. package/dist/tts.d.cts +0 -151
  51. package/dist/tts.d.ts +0 -151
  52. package/dist/tts.js +0 -418
  53. package/dist/tts.js.map +0 -1
@@ -1,222 +0,0 @@
1
- import { LRUCache } from 'lru-cache';
2
- import pino from 'pino';
3
- import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
4
- import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
5
- import { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
6
- import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
7
- import { trace, SpanStatusCode } from '@opentelemetry/api';
8
- import { resourceFromAttributes } from '@opentelemetry/resources';
9
-
10
- // src/observability/metric/index.ts
11
- var logger = pino({ name: "@voice-kit/core:metrics" });
12
- var TOKEN_COSTS_PER_M = {
13
- "gpt-4o": { input: 5, output: 15 },
14
- "gpt-4o-mini": { input: 0.15, output: 0.6 },
15
- "claude-3-5-sonnet": { input: 3, output: 15 },
16
- "llama-3.3-70b": { input: 0.59, output: 0.79 }
17
- };
18
- function p95(values) {
19
- if (values.length === 0) return 0;
20
- const sorted = [...values].sort((a, b) => a - b);
21
- const idx = Math.floor(sorted.length * 0.95);
22
- return sorted[Math.min(idx, sorted.length - 1)] ?? 0;
23
- }
24
- function avg(values) {
25
- if (values.length === 0) return 0;
26
- return values.reduce((a, b) => a + b, 0) / values.length;
27
- }
28
- var CallMetrics = class {
29
- store;
30
- constructor() {
31
- this.store = new LRUCache({
32
- max: 1e4,
33
- ttl: 2 * 60 * 60 * 1e3
34
- // 2 hours
35
- });
36
- }
37
- getOrCreate(callId) {
38
- const existing = this.store.get(callId);
39
- if (existing) return existing;
40
- const data = {
41
- sttFirstByteMs: [],
42
- ttsFirstByteMs: [],
43
- llmFirstTokenMs: [],
44
- turnLatencyMs: [],
45
- interruptionCount: 0,
46
- interruptionPositions: [],
47
- tokenCost: []
48
- };
49
- this.store.set(callId, data);
50
- return data;
51
- }
52
- /** Record time from audio start to first STT partial result. */
53
- recordSTTFirstByte(callId, ms) {
54
- this.getOrCreate(callId).sttFirstByteMs.push(ms);
55
- logger.debug({ callId, ms }, "Metric: STT TTFB");
56
- }
57
- /** Record time from TTS request to first audio chunk. */
58
- recordTTSFirstByte(callId, ms) {
59
- this.getOrCreate(callId).ttsFirstByteMs.push(ms);
60
- logger.debug({ callId, ms }, "Metric: TTS TTFB");
61
- }
62
- /** Record time from LLM request to first token. */
63
- recordLLMFirstToken(callId, ms) {
64
- this.getOrCreate(callId).llmFirstTokenMs.push(ms);
65
- logger.debug({ callId, ms }, "Metric: LLM first token");
66
- }
67
- /**
68
- * Record end-to-end turn latency: speech_end → first TTS audio byte.
69
- * This is the primary latency metric for voice agent quality.
70
- */
71
- recordTurnLatency(callId, ms) {
72
- this.getOrCreate(callId).turnLatencyMs.push(ms);
73
- logger.debug({ callId, ms }, "Metric: turn latency");
74
- }
75
- /**
76
- * Record an interruption event.
77
- *
78
- * @param callId Call identifier
79
- * @param positionPct 0–1, how far through the TTS stream the interruption occurred
80
- */
81
- recordInterruption(callId, positionPct) {
82
- const data = this.getOrCreate(callId);
83
- data.interruptionCount++;
84
- data.interruptionPositions.push(positionPct);
85
- logger.debug({ callId, positionPct }, "Metric: interruption");
86
- }
87
- /** Record token usage and estimated cost for a model call. */
88
- recordTokenCost(callId, model, inputTokens, outputTokens) {
89
- const costs = TOKEN_COSTS_PER_M[model] ?? { input: 0, output: 0 };
90
- const estimatedUsdCost = inputTokens / 1e6 * costs.input + outputTokens / 1e6 * costs.output;
91
- this.getOrCreate(callId).tokenCost.push({
92
- model,
93
- inputTokens,
94
- outputTokens,
95
- estimatedUsdCost
96
- });
97
- logger.debug({ callId, model, inputTokens, outputTokens, estimatedUsdCost }, "Metric: token cost");
98
- }
99
- /**
100
- * Get a full summary of metrics for a call.
101
- *
102
- * @param callId The call identifier
103
- * @returns Aggregated metrics summary
104
- */
105
- getCallSummary(callId) {
106
- const data = this.getOrCreate(callId);
107
- return {
108
- callId,
109
- sttFirstByteMs: [...data.sttFirstByteMs],
110
- ttsFirstByteMs: [...data.ttsFirstByteMs],
111
- llmFirstTokenMs: [...data.llmFirstTokenMs],
112
- turnLatencyMs: [...data.turnLatencyMs],
113
- interruptionCount: data.interruptionCount,
114
- interruptionPositions: [...data.interruptionPositions],
115
- tokenCost: [...data.tokenCost],
116
- avgTurnLatencyMs: Math.round(avg(data.turnLatencyMs)),
117
- p95TurnLatencyMs: Math.round(p95(data.turnLatencyMs))
118
- };
119
- }
120
- /** Remove metrics for a call. Call on call.ended to free memory. */
121
- clearCall(callId) {
122
- this.store.delete(callId);
123
- }
124
- };
125
- var logger2 = pino({ name: "@voice-kit/core:observability" });
126
- var _provider = null;
127
- function getOrInitProvider() {
128
- if (_provider) return _provider;
129
- const endpoint = process.env["OTEL_EXPORTER_OTLP_ENDPOINT"];
130
- _provider = new NodeTracerProvider({
131
- resource: resourceFromAttributes({
132
- [ATTR_SERVICE_NAME]: "voice-kit"
133
- }),
134
- // Pass span processors directly in constructor — addSpanProcessor doesn't exist in this version
135
- spanProcessors: endpoint ? [new SimpleSpanProcessor(new OTLPTraceExporter({ url: endpoint }))] : []
136
- });
137
- if (endpoint) {
138
- logger2.info({ endpoint }, "OTel OTLP exporter configured");
139
- }
140
- _provider.register();
141
- return _provider;
142
- }
143
- var VoiceSDKTracer = class {
144
- tracer;
145
- constructor() {
146
- getOrInitProvider();
147
- this.tracer = trace.getTracer("@voice-kit/core", "0.1.0");
148
- }
149
- /**
150
- * Trace an STT operation with provider + language attributes.
151
- */
152
- async traceSTT(fn, attrs) {
153
- return this.withSpan(`stt.${attrs.provider}`, fn, {
154
- "stt.provider": attrs.provider,
155
- "stt.language": attrs.language,
156
- ...attrs.callId && { "call.id": attrs.callId }
157
- });
158
- }
159
- /**
160
- * Trace a TTS synthesis operation.
161
- */
162
- async traceTTS(fn, attrs) {
163
- return this.withSpan(`tts.${attrs.provider}`, fn, {
164
- "tts.provider": attrs.provider,
165
- "tts.voice_id": attrs.voice,
166
- "tts.char_count": attrs.chars,
167
- ...attrs.callId && { "call.id": attrs.callId }
168
- });
169
- }
170
- /**
171
- * Trace an LLM generation call.
172
- */
173
- async traceLLM(fn, attrs) {
174
- return this.withSpan(`llm.${attrs.model}`, fn, {
175
- "llm.model": attrs.model,
176
- "llm.input_tokens": attrs.inputTokens,
177
- ...attrs.callId && { "call.id": attrs.callId }
178
- });
179
- }
180
- /**
181
- * Trace a full call lifecycle.
182
- */
183
- async traceCall(fn, attrs) {
184
- return this.withSpan("call", fn, {
185
- "call.id": attrs.callId,
186
- "call.direction": attrs.direction
187
- });
188
- }
189
- /**
190
- * Trace a single conversation turn.
191
- */
192
- async traceTurn(fn, attrs) {
193
- return this.withSpan("turn", fn, {
194
- "turn.index": attrs.turnIndex,
195
- "call.id": attrs.callId
196
- });
197
- }
198
- /** Generic span wrapper. @internal */
199
- async withSpan(name, fn, attributes) {
200
- const span = this.tracer.startSpan(name, { attributes });
201
- const startMs = Date.now();
202
- try {
203
- const result = await fn();
204
- span.setStatus({ code: SpanStatusCode.OK });
205
- span.setAttribute("duration_ms", Date.now() - startMs);
206
- return result;
207
- } catch (err) {
208
- span.setStatus({
209
- code: SpanStatusCode.ERROR,
210
- message: err instanceof Error ? err.message : String(err)
211
- });
212
- span.recordException(err instanceof Error ? err : new Error(String(err)));
213
- throw err;
214
- } finally {
215
- span.end();
216
- }
217
- }
218
- };
219
-
220
- export { CallMetrics, VoiceSDKTracer };
221
- //# sourceMappingURL=observability.js.map
222
- //# sourceMappingURL=observability.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/observability/metric/index.ts","../src/observability/tracer/index.ts"],"names":["logger","pino","Resource"],"mappings":"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","file":"observability.js","sourcesContent":["/**\r\n * @voice-kit/core — CallMetrics\r\n *\r\n * Records per-call performance metrics: TTFB, turn latency, token cost, interruption rate.\r\n * In-process LRU storage — exported via getCallSummary().\r\n */\r\n\r\nimport { LRUCache } from 'lru-cache'\r\nimport type { CallMetricsSummary } from '../../types'\r\nimport pino from 'pino'\r\n\r\nconst logger = pino({ name: '@voice-kit/core:metrics' })\r\n\r\n/** USD cost per 1M tokens by model. Approximate — update as pricing changes. */\r\nconst TOKEN_COSTS_PER_M: Record<string, { input: number; output: number }> = {\r\n 'gpt-4o': { input: 5.0, output: 15.0 },\r\n 'gpt-4o-mini': { input: 0.15, output: 0.6 },\r\n 'claude-3-5-sonnet': { input: 3.0, output: 15.0 },\r\n 'llama-3.3-70b': { input: 0.59, output: 0.79 },\r\n}\r\n\r\ninterface CallData {\r\n sttFirstByteMs: number[]\r\n ttsFirstByteMs: number[]\r\n llmFirstTokenMs: number[]\r\n turnLatencyMs: number[]\r\n interruptionCount: number\r\n interruptionPositions: number[]\r\n tokenCost: Array<{\r\n model: string\r\n inputTokens: number\r\n outputTokens: number\r\n estimatedUsdCost: number\r\n }>\r\n}\r\n\r\nfunction p95(values: number[]): number {\r\n if (values.length === 0) return 0\r\n const sorted = [...values].sort((a, b) => a - b)\r\n const idx = Math.floor(sorted.length * 0.95)\r\n return sorted[Math.min(idx, sorted.length - 1)] ?? 0\r\n}\r\n\r\nfunction avg(values: number[]): number {\r\n if (values.length === 0) return 0\r\n return values.reduce((a, b) => a + b, 0) / values.length\r\n}\r\n\r\n/**\r\n * Per-call performance metrics recorder.\r\n *\r\n * @example\r\n * ```ts\r\n * const metrics = new CallMetrics()\r\n * metrics.recordSTTFirstByte(callId, 180)\r\n * metrics.recordTurnLatency(callId, 340)\r\n * const summary = metrics.getCallSummary(callId)\r\n * console.log(summary.avgTurnLatencyMs) // 340\r\n * ```\r\n */\r\nexport class CallMetrics {\r\n private readonly store: LRUCache<string, CallData>\r\n\r\n constructor() {\r\n this.store = new LRUCache<string, CallData>({\r\n max: 10_000,\r\n ttl: 2 * 60 * 60 * 1_000, // 2 hours\r\n })\r\n }\r\n\r\n private getOrCreate(callId: string): CallData {\r\n const existing = this.store.get(callId)\r\n if (existing) return existing\r\n\r\n const data: CallData = {\r\n sttFirstByteMs: [],\r\n ttsFirstByteMs: [],\r\n llmFirstTokenMs: [],\r\n turnLatencyMs: [],\r\n interruptionCount: 0,\r\n interruptionPositions: [],\r\n tokenCost: [],\r\n }\r\n this.store.set(callId, data)\r\n return data\r\n }\r\n\r\n /** Record time from audio start to first STT partial result. */\r\n recordSTTFirstByte(callId: string, ms: number): void {\r\n this.getOrCreate(callId).sttFirstByteMs.push(ms)\r\n logger.debug({ callId, ms }, 'Metric: STT TTFB')\r\n }\r\n\r\n /** Record time from TTS request to first audio chunk. */\r\n recordTTSFirstByte(callId: string, ms: number): void {\r\n this.getOrCreate(callId).ttsFirstByteMs.push(ms)\r\n logger.debug({ callId, ms }, 'Metric: TTS TTFB')\r\n }\r\n\r\n /** Record time from LLM request to first token. */\r\n recordLLMFirstToken(callId: string, ms: number): void {\r\n this.getOrCreate(callId).llmFirstTokenMs.push(ms)\r\n logger.debug({ callId, ms }, 'Metric: LLM first token')\r\n }\r\n\r\n /**\r\n * Record end-to-end turn latency: speech_end → first TTS audio byte.\r\n * This is the primary latency metric for voice agent quality.\r\n */\r\n recordTurnLatency(callId: string, ms: number): void {\r\n this.getOrCreate(callId).turnLatencyMs.push(ms)\r\n logger.debug({ callId, ms }, 'Metric: turn latency')\r\n }\r\n\r\n /**\r\n * Record an interruption event.\r\n *\r\n * @param callId Call identifier\r\n * @param positionPct 0–1, how far through the TTS stream the interruption occurred\r\n */\r\n recordInterruption(callId: string, positionPct: number): void {\r\n const data = this.getOrCreate(callId)\r\n data.interruptionCount++\r\n data.interruptionPositions.push(positionPct)\r\n logger.debug({ callId, positionPct }, 'Metric: interruption')\r\n }\r\n\r\n /** Record token usage and estimated cost for a model call. */\r\n recordTokenCost(\r\n callId: string,\r\n model: string,\r\n inputTokens: number,\r\n outputTokens: number\r\n ): void {\r\n const costs = TOKEN_COSTS_PER_M[model] ?? { input: 0, output: 0 }\r\n const estimatedUsdCost =\r\n (inputTokens / 1_000_000) * costs.input +\r\n (outputTokens / 1_000_000) * costs.output\r\n\r\n this.getOrCreate(callId).tokenCost.push({\r\n model,\r\n inputTokens,\r\n outputTokens,\r\n estimatedUsdCost,\r\n })\r\n\r\n logger.debug({ callId, model, inputTokens, outputTokens, estimatedUsdCost }, 'Metric: token cost')\r\n }\r\n\r\n /**\r\n * Get a full summary of metrics for a call.\r\n *\r\n * @param callId The call identifier\r\n * @returns Aggregated metrics summary\r\n */\r\n getCallSummary(callId: string): CallMetricsSummary {\r\n const data = this.getOrCreate(callId)\r\n\r\n return {\r\n callId,\r\n sttFirstByteMs: [...data.sttFirstByteMs],\r\n ttsFirstByteMs: [...data.ttsFirstByteMs],\r\n llmFirstTokenMs: [...data.llmFirstTokenMs],\r\n turnLatencyMs: [...data.turnLatencyMs],\r\n interruptionCount: data.interruptionCount,\r\n interruptionPositions: [...data.interruptionPositions],\r\n tokenCost: [...data.tokenCost],\r\n avgTurnLatencyMs: Math.round(avg(data.turnLatencyMs)),\r\n p95TurnLatencyMs: Math.round(p95(data.turnLatencyMs)),\r\n }\r\n }\r\n\r\n /** Remove metrics for a call. Call on call.ended to free memory. */\r\n clearCall(callId: string): void {\r\n this.store.delete(callId)\r\n }\r\n}","/**\r\n * @voice-kit/core — OpenTelemetry tracing\r\n *\r\n * VoiceSDKTracer: wraps every external provider call with OTel spans.\r\n * Auto-exports to OTLP endpoint if OTEL_EXPORTER_OTLP_ENDPOINT is set.\r\n */\r\n\r\nimport { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'\r\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'\r\nimport { SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'\r\nimport { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions'\r\nimport { trace, SpanStatusCode, type Tracer, type Span } from '@opentelemetry/api'\r\nimport { resourceFromAttributes as Resource } from '@opentelemetry/resources'\r\nimport pino from 'pino'\r\n\r\nconst logger = pino({ name: '@voice-kit/core:observability' })\r\n\r\nlet _provider: NodeTracerProvider | null = null\r\n\r\n/**\r\n * Initialize the global OTel trace provider.\r\n * Called once at SDK startup if OTEL_EXPORTER_OTLP_ENDPOINT is set.\r\n *\r\n * @internal\r\n */\r\nfunction getOrInitProvider(): NodeTracerProvider {\r\n if (_provider) return _provider\r\n\r\n const endpoint = process.env['OTEL_EXPORTER_OTLP_ENDPOINT']\r\n\r\n _provider = new NodeTracerProvider({\r\n resource: Resource({\r\n [ATTR_SERVICE_NAME]: 'voice-kit',\r\n }),\r\n // Pass span processors directly in constructor — addSpanProcessor doesn't exist in this version\r\n spanProcessors: endpoint\r\n ? [new SimpleSpanProcessor(new OTLPTraceExporter({ url: endpoint }))]\r\n : [],\r\n })\r\n\r\n if (endpoint) {\r\n logger.info({ endpoint }, 'OTel OTLP exporter configured')\r\n }\r\n\r\n _provider.register()\r\n return _provider\r\n}\r\n\r\n/**\r\n * OpenTelemetry tracer for VoiceKit. Wraps every external I/O with spans.\r\n *\r\n * @example\r\n * ```ts\r\n * const tracer = new VoiceSDKTracer()\r\n * const result = await tracer.traceSTT(\r\n * () => stt.transcribeBatch(audio),\r\n * { provider: 'deepgram', language: 'en-IN' }\r\n * )\r\n * ```\r\n */\r\nexport class VoiceSDKTracer {\r\n private readonly tracer: Tracer\r\n\r\n constructor() {\r\n getOrInitProvider()\r\n this.tracer = trace.getTracer('@voice-kit/core', '0.1.0')\r\n }\r\n\r\n /**\r\n * Trace an STT operation with provider + language attributes.\r\n */\r\n async traceSTT<T>(\r\n fn: () => Promise<T>,\r\n attrs: { provider: string; language: string; callId?: string }\r\n ): Promise<T> {\r\n return this.withSpan(`stt.${attrs.provider}`, fn, {\r\n 'stt.provider': attrs.provider,\r\n 'stt.language': attrs.language,\r\n ...(attrs.callId && { 'call.id': attrs.callId }),\r\n })\r\n }\r\n\r\n /**\r\n * Trace a TTS synthesis operation.\r\n */\r\n async traceTTS<T>(\r\n fn: () => Promise<T>,\r\n attrs: { provider: string; voice: string; chars: number; callId?: string }\r\n ): Promise<T> {\r\n return this.withSpan(`tts.${attrs.provider}`, fn, {\r\n 'tts.provider': attrs.provider,\r\n 'tts.voice_id': attrs.voice,\r\n 'tts.char_count': attrs.chars,\r\n ...(attrs.callId && { 'call.id': attrs.callId }),\r\n })\r\n }\r\n\r\n /**\r\n * Trace an LLM generation call.\r\n */\r\n async traceLLM<T>(\r\n fn: () => Promise<T>,\r\n attrs: { model: string; inputTokens: number; callId?: string }\r\n ): Promise<T> {\r\n return this.withSpan(`llm.${attrs.model}`, fn, {\r\n 'llm.model': attrs.model,\r\n 'llm.input_tokens': attrs.inputTokens,\r\n ...(attrs.callId && { 'call.id': attrs.callId }),\r\n })\r\n }\r\n\r\n /**\r\n * Trace a full call lifecycle.\r\n */\r\n async traceCall<T>(\r\n fn: () => Promise<T>,\r\n attrs: { callId: string; direction: 'inbound' | 'outbound' }\r\n ): Promise<T> {\r\n return this.withSpan('call', fn, {\r\n 'call.id': attrs.callId,\r\n 'call.direction': attrs.direction,\r\n })\r\n }\r\n\r\n /**\r\n * Trace a single conversation turn.\r\n */\r\n async traceTurn<T>(\r\n fn: () => Promise<T>,\r\n attrs: { turnIndex: number; callId: string }\r\n ): Promise<T> {\r\n return this.withSpan('turn', fn, {\r\n 'turn.index': attrs.turnIndex,\r\n 'call.id': attrs.callId,\r\n })\r\n }\r\n\r\n /** Generic span wrapper. @internal */\r\n private async withSpan<T>(\r\n name: string,\r\n fn: () => Promise<T>,\r\n attributes: Record<string, string | number | boolean>\r\n ): Promise<T> {\r\n const span: Span = this.tracer.startSpan(name, { attributes })\r\n const startMs = Date.now()\r\n\r\n try {\r\n const result = await fn()\r\n span.setStatus({ code: SpanStatusCode.OK })\r\n span.setAttribute('duration_ms', Date.now() - startMs)\r\n return result\r\n } catch (err) {\r\n span.setStatus({\r\n code: SpanStatusCode.ERROR,\r\n message: err instanceof Error ? err.message : String(err),\r\n })\r\n span.recordException(err instanceof Error ? err : new Error(String(err)))\r\n throw err\r\n } finally {\r\n span.end()\r\n }\r\n }\r\n}"]}