@voice-kit/core 0.1.1 → 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 (45) hide show
  1. package/dist/index.cjs +2137 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +1466 -3
  4. package/dist/index.d.ts +1466 -3
  5. package/dist/index.js +2102 -1
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -26
  8. package/dist/compliance.cjs +0 -343
  9. package/dist/compliance.cjs.map +0 -1
  10. package/dist/compliance.d.cts +0 -163
  11. package/dist/compliance.d.ts +0 -163
  12. package/dist/compliance.js +0 -335
  13. package/dist/compliance.js.map +0 -1
  14. package/dist/errors.cjs +0 -284
  15. package/dist/errors.cjs.map +0 -1
  16. package/dist/errors.d.cts +0 -175
  17. package/dist/errors.d.ts +0 -175
  18. package/dist/errors.js +0 -262
  19. package/dist/errors.js.map +0 -1
  20. package/dist/index-CkTG6DOa.d.cts +0 -319
  21. package/dist/index-CkTG6DOa.d.ts +0 -319
  22. package/dist/memory.cjs +0 -121
  23. package/dist/memory.cjs.map +0 -1
  24. package/dist/memory.d.cts +0 -29
  25. package/dist/memory.d.ts +0 -29
  26. package/dist/memory.js +0 -115
  27. package/dist/memory.js.map +0 -1
  28. package/dist/observability.cjs +0 -229
  29. package/dist/observability.cjs.map +0 -1
  30. package/dist/observability.d.cts +0 -122
  31. package/dist/observability.d.ts +0 -122
  32. package/dist/observability.js +0 -222
  33. package/dist/observability.js.map +0 -1
  34. package/dist/stt.cjs +0 -828
  35. package/dist/stt.cjs.map +0 -1
  36. package/dist/stt.d.cts +0 -308
  37. package/dist/stt.d.ts +0 -308
  38. package/dist/stt.js +0 -815
  39. package/dist/stt.js.map +0 -1
  40. package/dist/tts.cjs +0 -429
  41. package/dist/tts.cjs.map +0 -1
  42. package/dist/tts.d.cts +0 -151
  43. package/dist/tts.d.ts +0 -151
  44. package/dist/tts.js +0 -418
  45. 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}"]}