@vattara/sdk 1.0.0 → 1.0.1

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,96 @@
1
+ /**
2
+ * @vattara/sdk — Official TypeScript SDK for Vattara Observe
3
+ * Realtime PCM audio streaming, interruption tracking, token cost analytics & CLEAR 2.0 metrics.
4
+ */
5
+ interface VattaraOptions {
6
+ apiKey: string;
7
+ serverUrl?: string;
8
+ sampleRate?: 16000 | 24000;
9
+ tags?: Record<string, string>;
10
+ debug?: boolean;
11
+ maxBufferSize?: number;
12
+ maxRetries?: number;
13
+ onError?: (error: Error) => void;
14
+ onConnect?: () => void;
15
+ onDisconnect?: () => void;
16
+ }
17
+ interface InterruptionEvent {
18
+ cutoffLatencyMs: number;
19
+ interruptedAt?: number;
20
+ }
21
+ interface TokenUsageEvent {
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ model?: string;
25
+ }
26
+ interface TranscriptEvent {
27
+ text: string;
28
+ role?: 'caller' | 'agent' | 'user' | 'bot' | 'assistant';
29
+ timestamp?: number;
30
+ }
31
+ interface LatencyEvent {
32
+ kind: string;
33
+ valueMs: number;
34
+ }
35
+ type VattaraEventType = 'audio_frame' | 'interruption' | 'token_usage' | 'transcript' | 'latency' | 'session_start' | 'session_end' | 'call_ended';
36
+ interface VattaraEvent {
37
+ type: VattaraEventType;
38
+ [key: string]: unknown;
39
+ }
40
+ declare class VattaraObserveClient {
41
+ private apiKey;
42
+ private serverUrl;
43
+ private sampleRate;
44
+ private tags;
45
+ private debug;
46
+ private maxBufferSize;
47
+ private maxRetries;
48
+ private onError?;
49
+ private onConnect?;
50
+ private onDisconnect?;
51
+ private ws;
52
+ private token;
53
+ private buffer;
54
+ private isConnecting;
55
+ private retryCount;
56
+ private retryTimer;
57
+ private destroyed;
58
+ constructor(options: VattaraOptions);
59
+ /** Initialize session by fetching JWT token and establishing WebSocket connection. */
60
+ init(): Promise<void>;
61
+ /** Track PCM audio base64 frame (16kHz / 24kHz). */
62
+ sendAudioFrame(base64Data: string, sampleRate?: number): void;
63
+ /** Track customer barge-in / interruption event. */
64
+ trackInterruption(event: InterruptionEvent): void;
65
+ /** Track input/output token usage. */
66
+ trackTokens(event: TokenUsageEvent): void;
67
+ /** Track a transcript turn (caller or agent speech). */
68
+ trackTranscript(event: TranscriptEvent): void;
69
+ /** Track a latency measurement. */
70
+ trackLatency(event: LatencyEvent): void;
71
+ /** Signal the start of a new call session. */
72
+ sessionStart(): void;
73
+ /** Signal the end of the current call session. */
74
+ sessionEnd(): void;
75
+ /** Set dynamic custom properties/tags. */
76
+ setTags(tags: Record<string, string>): void;
77
+ /** Cleanly close the connection and release resources. */
78
+ disconnect(): void;
79
+ private sendEvent;
80
+ private flushBuffer;
81
+ private scheduleReconnect;
82
+ }
83
+ declare const Vattara: {
84
+ init(options: VattaraOptions): VattaraObserveClient;
85
+ sendAudioFrame(base64Data: string, sampleRate?: number): void;
86
+ trackInterruption(event: InterruptionEvent): void;
87
+ trackTokens(event: TokenUsageEvent): void;
88
+ trackTranscript(event: TranscriptEvent): void;
89
+ trackLatency(event: LatencyEvent): void;
90
+ sessionStart(): void;
91
+ sessionEnd(): void;
92
+ setTags(tags: Record<string, string>): void;
93
+ disconnect(): void;
94
+ };
95
+
96
+ export { type InterruptionEvent, type LatencyEvent, type TokenUsageEvent, type TranscriptEvent, Vattara, type VattaraEvent, type VattaraEventType, VattaraObserveClient, type VattaraOptions };
package/dist/index.d.ts CHANGED
@@ -2,32 +2,59 @@
2
2
  * @vattara/sdk — Official TypeScript SDK for Vattara Observe
3
3
  * Realtime PCM audio streaming, interruption tracking, token cost analytics & CLEAR 2.0 metrics.
4
4
  */
5
- export interface VattaraOptions {
5
+ interface VattaraOptions {
6
6
  apiKey: string;
7
7
  serverUrl?: string;
8
8
  sampleRate?: 16000 | 24000;
9
9
  tags?: Record<string, string>;
10
10
  debug?: boolean;
11
+ maxBufferSize?: number;
12
+ maxRetries?: number;
13
+ onError?: (error: Error) => void;
14
+ onConnect?: () => void;
15
+ onDisconnect?: () => void;
11
16
  }
12
- export interface InterruptionEvent {
17
+ interface InterruptionEvent {
13
18
  cutoffLatencyMs: number;
14
19
  interruptedAt?: number;
15
20
  }
16
- export interface TokenUsageEvent {
21
+ interface TokenUsageEvent {
17
22
  inputTokens: number;
18
23
  outputTokens: number;
19
24
  model?: string;
20
25
  }
21
- export declare class VattaraObserveClient {
26
+ interface TranscriptEvent {
27
+ text: string;
28
+ role?: 'caller' | 'agent' | 'user' | 'bot' | 'assistant';
29
+ timestamp?: number;
30
+ }
31
+ interface LatencyEvent {
32
+ kind: string;
33
+ valueMs: number;
34
+ }
35
+ type VattaraEventType = 'audio_frame' | 'interruption' | 'token_usage' | 'transcript' | 'latency' | 'session_start' | 'session_end' | 'call_ended';
36
+ interface VattaraEvent {
37
+ type: VattaraEventType;
38
+ [key: string]: unknown;
39
+ }
40
+ declare class VattaraObserveClient {
22
41
  private apiKey;
23
42
  private serverUrl;
24
43
  private sampleRate;
25
44
  private tags;
26
45
  private debug;
46
+ private maxBufferSize;
47
+ private maxRetries;
48
+ private onError?;
49
+ private onConnect?;
50
+ private onDisconnect?;
27
51
  private ws;
28
52
  private token;
29
53
  private buffer;
30
54
  private isConnecting;
55
+ private retryCount;
56
+ private retryTimer;
57
+ private destroyed;
31
58
  constructor(options: VattaraOptions);
32
59
  /** Initialize session by fetching JWT token and establishing WebSocket connection. */
33
60
  init(): Promise<void>;
@@ -37,15 +64,33 @@ export declare class VattaraObserveClient {
37
64
  trackInterruption(event: InterruptionEvent): void;
38
65
  /** Track input/output token usage. */
39
66
  trackTokens(event: TokenUsageEvent): void;
67
+ /** Track a transcript turn (caller or agent speech). */
68
+ trackTranscript(event: TranscriptEvent): void;
69
+ /** Track a latency measurement. */
70
+ trackLatency(event: LatencyEvent): void;
71
+ /** Signal the start of a new call session. */
72
+ sessionStart(): void;
73
+ /** Signal the end of the current call session. */
74
+ sessionEnd(): void;
40
75
  /** Set dynamic custom properties/tags. */
41
76
  setTags(tags: Record<string, string>): void;
77
+ /** Cleanly close the connection and release resources. */
78
+ disconnect(): void;
42
79
  private sendEvent;
43
80
  private flushBuffer;
81
+ private scheduleReconnect;
44
82
  }
45
- export declare const Vattara: {
83
+ declare const Vattara: {
46
84
  init(options: VattaraOptions): VattaraObserveClient;
47
85
  sendAudioFrame(base64Data: string, sampleRate?: number): void;
48
86
  trackInterruption(event: InterruptionEvent): void;
49
87
  trackTokens(event: TokenUsageEvent): void;
88
+ trackTranscript(event: TranscriptEvent): void;
89
+ trackLatency(event: LatencyEvent): void;
90
+ sessionStart(): void;
91
+ sessionEnd(): void;
50
92
  setTags(tags: Record<string, string>): void;
93
+ disconnect(): void;
51
94
  };
95
+
96
+ export { type InterruptionEvent, type LatencyEvent, type TokenUsageEvent, type TranscriptEvent, Vattara, type VattaraEvent, type VattaraEventType, VattaraObserveClient, type VattaraOptions };
package/dist/index.js CHANGED
@@ -30,15 +30,25 @@ var VattaraObserveClient = class {
30
30
  this.token = null;
31
31
  this.buffer = [];
32
32
  this.isConnecting = false;
33
+ this.retryCount = 0;
34
+ this.retryTimer = null;
35
+ this.destroyed = false;
33
36
  this.apiKey = options.apiKey;
34
37
  this.serverUrl = options.serverUrl || "wss://vattara-observe-production.up.railway.app/api/v1/ws/telemetry";
35
38
  this.sampleRate = options.sampleRate || 24e3;
36
39
  this.tags = options.tags || {};
37
40
  this.debug = options.debug || false;
41
+ this.maxBufferSize = options.maxBufferSize ?? 1e3;
42
+ this.maxRetries = options.maxRetries ?? 10;
43
+ this.onError = options.onError;
44
+ this.onConnect = options.onConnect;
45
+ this.onDisconnect = options.onDisconnect;
38
46
  }
39
47
  /** Initialize session by fetching JWT token and establishing WebSocket connection. */
40
48
  async init() {
41
- if (this.isConnecting || this.ws) return;
49
+ if (this.destroyed) return;
50
+ if (this.isConnecting || this.ws && this.ws.readyState === WebSocket.OPEN)
51
+ return;
42
52
  this.isConnecting = true;
43
53
  try {
44
54
  const httpUrl = this.serverUrl.replace(/^ws/, "http").replace(/\/ws\/telemetry$/, "/auth/token");
@@ -52,21 +62,33 @@ var VattaraObserveClient = class {
52
62
  }
53
63
  const data = await res.json();
54
64
  this.token = data.token;
65
+ this.retryCount = 0;
55
66
  const wsUrl = `${this.serverUrl}?token=${encodeURIComponent(this.token)}`;
56
67
  this.ws = new WebSocket(wsUrl);
57
68
  this.ws.onopen = () => {
58
69
  if (this.debug) console.log("[Vattara] Telemetry WebSocket connected.");
59
70
  this.flushBuffer();
71
+ this.onConnect?.();
60
72
  };
61
73
  this.ws.onclose = () => {
62
- if (this.debug) console.log("[Vattara] Telemetry WebSocket disconnected.");
74
+ if (this.debug)
75
+ console.log("[Vattara] Telemetry WebSocket disconnected.");
63
76
  this.ws = null;
77
+ this.onDisconnect?.();
78
+ if (!this.destroyed) {
79
+ this.scheduleReconnect();
80
+ }
64
81
  };
65
82
  this.ws.onerror = (err) => {
66
83
  if (this.debug) console.error("[Vattara] WebSocket error:", err);
67
84
  };
68
85
  } catch (err) {
69
- if (this.debug) console.error("[Vattara] Initialization error:", err);
86
+ const error = err instanceof Error ? err : new Error(String(err));
87
+ if (this.debug) console.error("[Vattara] Initialization error:", error);
88
+ this.onError?.(error);
89
+ if (!this.destroyed) {
90
+ this.scheduleReconnect();
91
+ }
70
92
  } finally {
71
93
  this.isConnecting = false;
72
94
  }
@@ -85,7 +107,8 @@ var VattaraObserveClient = class {
85
107
  this.sendEvent({
86
108
  type: "interruption",
87
109
  cutoffLatencyMs: event.cutoffLatencyMs,
88
- interruptedAt: event.interruptedAt || Date.now()
110
+ interruptedAt: event.interruptedAt || Date.now(),
111
+ timestamp: Date.now()
89
112
  });
90
113
  }
91
114
  /** Track input/output token usage. */
@@ -98,15 +121,70 @@ var VattaraObserveClient = class {
98
121
  timestamp: Date.now()
99
122
  });
100
123
  }
124
+ /** Track a transcript turn (caller or agent speech). */
125
+ trackTranscript(event) {
126
+ this.sendEvent({
127
+ type: "transcript",
128
+ text: event.text,
129
+ role: event.role || "caller",
130
+ timestamp: event.timestamp || Date.now()
131
+ });
132
+ }
133
+ /** Track a latency measurement. */
134
+ trackLatency(event) {
135
+ this.sendEvent({
136
+ type: "latency",
137
+ kind: event.kind,
138
+ valueMs: event.valueMs,
139
+ timestamp: Date.now()
140
+ });
141
+ }
142
+ /** Signal the start of a new call session. */
143
+ sessionStart() {
144
+ this.sendEvent({
145
+ type: "session_start",
146
+ timestamp: Date.now()
147
+ });
148
+ }
149
+ /** Signal the end of the current call session. */
150
+ sessionEnd() {
151
+ this.sendEvent({
152
+ type: "session_end",
153
+ timestamp: Date.now()
154
+ });
155
+ }
101
156
  /** Set dynamic custom properties/tags. */
102
157
  setTags(tags) {
103
158
  this.tags = { ...this.tags, ...tags };
104
159
  }
160
+ /** Cleanly close the connection and release resources. */
161
+ disconnect() {
162
+ this.destroyed = true;
163
+ if (this.retryTimer !== null) {
164
+ clearTimeout(this.retryTimer);
165
+ this.retryTimer = null;
166
+ }
167
+ if (this.ws) {
168
+ this.ws.onclose = null;
169
+ this.ws.onerror = null;
170
+ this.ws.close();
171
+ this.ws = null;
172
+ }
173
+ this.buffer = [];
174
+ }
105
175
  sendEvent(payload) {
176
+ if (this.destroyed) return;
106
177
  const fullPayload = { ...payload, tags: this.tags };
107
178
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
108
179
  this.ws.send(JSON.stringify(fullPayload));
109
180
  } else {
181
+ if (this.buffer.length >= this.maxBufferSize) {
182
+ this.buffer.shift();
183
+ if (this.debug)
184
+ console.warn(
185
+ "[Vattara] Buffer full \u2014 dropped oldest event."
186
+ );
187
+ }
110
188
  this.buffer.push(fullPayload);
111
189
  if (!this.isConnecting && !this.ws) {
112
190
  this.init();
@@ -119,10 +197,32 @@ var VattaraObserveClient = class {
119
197
  this.ws.send(JSON.stringify(payload));
120
198
  }
121
199
  }
200
+ scheduleReconnect() {
201
+ if (this.destroyed) return;
202
+ if (this.retryCount >= this.maxRetries) {
203
+ const error = new Error(
204
+ `[Vattara] Max reconnect attempts (${this.maxRetries}) exhausted.`
205
+ );
206
+ if (this.debug) console.error(error.message);
207
+ this.onError?.(error);
208
+ return;
209
+ }
210
+ const delay = Math.min(1e3 * 2 ** this.retryCount, 3e4);
211
+ this.retryCount++;
212
+ if (this.debug)
213
+ console.log(
214
+ `[Vattara] Reconnecting in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})...`
215
+ );
216
+ this.retryTimer = setTimeout(() => {
217
+ this.retryTimer = null;
218
+ this.init();
219
+ }, delay);
220
+ }
122
221
  };
123
222
  var _globalClient = null;
124
223
  var Vattara = {
125
224
  init(options) {
225
+ _globalClient?.disconnect();
126
226
  _globalClient = new VattaraObserveClient(options);
127
227
  _globalClient.init();
128
228
  return _globalClient;
@@ -136,8 +236,24 @@ var Vattara = {
136
236
  trackTokens(event) {
137
237
  _globalClient?.trackTokens(event);
138
238
  },
239
+ trackTranscript(event) {
240
+ _globalClient?.trackTranscript(event);
241
+ },
242
+ trackLatency(event) {
243
+ _globalClient?.trackLatency(event);
244
+ },
245
+ sessionStart() {
246
+ _globalClient?.sessionStart();
247
+ },
248
+ sessionEnd() {
249
+ _globalClient?.sessionEnd();
250
+ },
139
251
  setTags(tags) {
140
252
  _globalClient?.setTags(tags);
253
+ },
254
+ disconnect() {
255
+ _globalClient?.disconnect();
256
+ _globalClient = null;
141
257
  }
142
258
  };
143
259
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.mjs CHANGED
@@ -5,15 +5,25 @@ var VattaraObserveClient = class {
5
5
  this.token = null;
6
6
  this.buffer = [];
7
7
  this.isConnecting = false;
8
+ this.retryCount = 0;
9
+ this.retryTimer = null;
10
+ this.destroyed = false;
8
11
  this.apiKey = options.apiKey;
9
12
  this.serverUrl = options.serverUrl || "wss://vattara-observe-production.up.railway.app/api/v1/ws/telemetry";
10
13
  this.sampleRate = options.sampleRate || 24e3;
11
14
  this.tags = options.tags || {};
12
15
  this.debug = options.debug || false;
16
+ this.maxBufferSize = options.maxBufferSize ?? 1e3;
17
+ this.maxRetries = options.maxRetries ?? 10;
18
+ this.onError = options.onError;
19
+ this.onConnect = options.onConnect;
20
+ this.onDisconnect = options.onDisconnect;
13
21
  }
14
22
  /** Initialize session by fetching JWT token and establishing WebSocket connection. */
15
23
  async init() {
16
- if (this.isConnecting || this.ws) return;
24
+ if (this.destroyed) return;
25
+ if (this.isConnecting || this.ws && this.ws.readyState === WebSocket.OPEN)
26
+ return;
17
27
  this.isConnecting = true;
18
28
  try {
19
29
  const httpUrl = this.serverUrl.replace(/^ws/, "http").replace(/\/ws\/telemetry$/, "/auth/token");
@@ -27,21 +37,33 @@ var VattaraObserveClient = class {
27
37
  }
28
38
  const data = await res.json();
29
39
  this.token = data.token;
40
+ this.retryCount = 0;
30
41
  const wsUrl = `${this.serverUrl}?token=${encodeURIComponent(this.token)}`;
31
42
  this.ws = new WebSocket(wsUrl);
32
43
  this.ws.onopen = () => {
33
44
  if (this.debug) console.log("[Vattara] Telemetry WebSocket connected.");
34
45
  this.flushBuffer();
46
+ this.onConnect?.();
35
47
  };
36
48
  this.ws.onclose = () => {
37
- if (this.debug) console.log("[Vattara] Telemetry WebSocket disconnected.");
49
+ if (this.debug)
50
+ console.log("[Vattara] Telemetry WebSocket disconnected.");
38
51
  this.ws = null;
52
+ this.onDisconnect?.();
53
+ if (!this.destroyed) {
54
+ this.scheduleReconnect();
55
+ }
39
56
  };
40
57
  this.ws.onerror = (err) => {
41
58
  if (this.debug) console.error("[Vattara] WebSocket error:", err);
42
59
  };
43
60
  } catch (err) {
44
- if (this.debug) console.error("[Vattara] Initialization error:", err);
61
+ const error = err instanceof Error ? err : new Error(String(err));
62
+ if (this.debug) console.error("[Vattara] Initialization error:", error);
63
+ this.onError?.(error);
64
+ if (!this.destroyed) {
65
+ this.scheduleReconnect();
66
+ }
45
67
  } finally {
46
68
  this.isConnecting = false;
47
69
  }
@@ -60,7 +82,8 @@ var VattaraObserveClient = class {
60
82
  this.sendEvent({
61
83
  type: "interruption",
62
84
  cutoffLatencyMs: event.cutoffLatencyMs,
63
- interruptedAt: event.interruptedAt || Date.now()
85
+ interruptedAt: event.interruptedAt || Date.now(),
86
+ timestamp: Date.now()
64
87
  });
65
88
  }
66
89
  /** Track input/output token usage. */
@@ -73,15 +96,70 @@ var VattaraObserveClient = class {
73
96
  timestamp: Date.now()
74
97
  });
75
98
  }
99
+ /** Track a transcript turn (caller or agent speech). */
100
+ trackTranscript(event) {
101
+ this.sendEvent({
102
+ type: "transcript",
103
+ text: event.text,
104
+ role: event.role || "caller",
105
+ timestamp: event.timestamp || Date.now()
106
+ });
107
+ }
108
+ /** Track a latency measurement. */
109
+ trackLatency(event) {
110
+ this.sendEvent({
111
+ type: "latency",
112
+ kind: event.kind,
113
+ valueMs: event.valueMs,
114
+ timestamp: Date.now()
115
+ });
116
+ }
117
+ /** Signal the start of a new call session. */
118
+ sessionStart() {
119
+ this.sendEvent({
120
+ type: "session_start",
121
+ timestamp: Date.now()
122
+ });
123
+ }
124
+ /** Signal the end of the current call session. */
125
+ sessionEnd() {
126
+ this.sendEvent({
127
+ type: "session_end",
128
+ timestamp: Date.now()
129
+ });
130
+ }
76
131
  /** Set dynamic custom properties/tags. */
77
132
  setTags(tags) {
78
133
  this.tags = { ...this.tags, ...tags };
79
134
  }
135
+ /** Cleanly close the connection and release resources. */
136
+ disconnect() {
137
+ this.destroyed = true;
138
+ if (this.retryTimer !== null) {
139
+ clearTimeout(this.retryTimer);
140
+ this.retryTimer = null;
141
+ }
142
+ if (this.ws) {
143
+ this.ws.onclose = null;
144
+ this.ws.onerror = null;
145
+ this.ws.close();
146
+ this.ws = null;
147
+ }
148
+ this.buffer = [];
149
+ }
80
150
  sendEvent(payload) {
151
+ if (this.destroyed) return;
81
152
  const fullPayload = { ...payload, tags: this.tags };
82
153
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
83
154
  this.ws.send(JSON.stringify(fullPayload));
84
155
  } else {
156
+ if (this.buffer.length >= this.maxBufferSize) {
157
+ this.buffer.shift();
158
+ if (this.debug)
159
+ console.warn(
160
+ "[Vattara] Buffer full \u2014 dropped oldest event."
161
+ );
162
+ }
85
163
  this.buffer.push(fullPayload);
86
164
  if (!this.isConnecting && !this.ws) {
87
165
  this.init();
@@ -94,10 +172,32 @@ var VattaraObserveClient = class {
94
172
  this.ws.send(JSON.stringify(payload));
95
173
  }
96
174
  }
175
+ scheduleReconnect() {
176
+ if (this.destroyed) return;
177
+ if (this.retryCount >= this.maxRetries) {
178
+ const error = new Error(
179
+ `[Vattara] Max reconnect attempts (${this.maxRetries}) exhausted.`
180
+ );
181
+ if (this.debug) console.error(error.message);
182
+ this.onError?.(error);
183
+ return;
184
+ }
185
+ const delay = Math.min(1e3 * 2 ** this.retryCount, 3e4);
186
+ this.retryCount++;
187
+ if (this.debug)
188
+ console.log(
189
+ `[Vattara] Reconnecting in ${delay}ms (attempt ${this.retryCount}/${this.maxRetries})...`
190
+ );
191
+ this.retryTimer = setTimeout(() => {
192
+ this.retryTimer = null;
193
+ this.init();
194
+ }, delay);
195
+ }
97
196
  };
98
197
  var _globalClient = null;
99
198
  var Vattara = {
100
199
  init(options) {
200
+ _globalClient?.disconnect();
101
201
  _globalClient = new VattaraObserveClient(options);
102
202
  _globalClient.init();
103
203
  return _globalClient;
@@ -111,8 +211,24 @@ var Vattara = {
111
211
  trackTokens(event) {
112
212
  _globalClient?.trackTokens(event);
113
213
  },
214
+ trackTranscript(event) {
215
+ _globalClient?.trackTranscript(event);
216
+ },
217
+ trackLatency(event) {
218
+ _globalClient?.trackLatency(event);
219
+ },
220
+ sessionStart() {
221
+ _globalClient?.sessionStart();
222
+ },
223
+ sessionEnd() {
224
+ _globalClient?.sessionEnd();
225
+ },
114
226
  setTags(tags) {
115
227
  _globalClient?.setTags(tags);
228
+ },
229
+ disconnect() {
230
+ _globalClient?.disconnect();
231
+ _globalClient = null;
116
232
  }
117
233
  };
118
234
  export {
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@vattara/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Official TypeScript SDK for Vattara Observe — Realtime AI Voice Agent Observability & CLEAR 2.0 Evaluation",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
8
12
  "scripts": {
9
- "build": "tsup src/index.ts --format cjs,esm && tsc --emitDeclarationOnly"
13
+ "build": "tsup src/index.ts --format cjs,esm --dts"
10
14
  },
11
15
  "keywords": [
12
16
  "vattara",
package/src/index.ts DELETED
@@ -1,170 +0,0 @@
1
- /**
2
- * @vattara/sdk — Official TypeScript SDK for Vattara Observe
3
- * Realtime PCM audio streaming, interruption tracking, token cost analytics & CLEAR 2.0 metrics.
4
- */
5
-
6
- export interface VattaraOptions {
7
- apiKey: string;
8
- serverUrl?: string;
9
- sampleRate?: 16000 | 24000;
10
- tags?: Record<string, string>;
11
- debug?: boolean;
12
- }
13
-
14
- export interface InterruptionEvent {
15
- cutoffLatencyMs: number;
16
- interruptedAt?: number;
17
- }
18
-
19
- export interface TokenUsageEvent {
20
- inputTokens: number;
21
- outputTokens: number;
22
- model?: string;
23
- }
24
-
25
- export class VattaraObserveClient {
26
- private apiKey: string;
27
- private serverUrl: string;
28
- private sampleRate: number;
29
- private tags: Record<string, string>;
30
- private debug: boolean;
31
- private ws: WebSocket | null = null;
32
- private token: string | null = null;
33
- private buffer: any[] = [];
34
- private isConnecting: boolean = false;
35
-
36
- constructor(options: VattaraOptions) {
37
- this.apiKey = options.apiKey;
38
- this.serverUrl = options.serverUrl || 'wss://vattara-observe-production.up.railway.app/api/v1/ws/telemetry';
39
- this.sampleRate = options.sampleRate || 24000;
40
- this.tags = options.tags || {};
41
- this.debug = options.debug || false;
42
- }
43
-
44
- /** Initialize session by fetching JWT token and establishing WebSocket connection. */
45
- async init(): Promise<void> {
46
- if (this.isConnecting || this.ws) return;
47
- this.isConnecting = true;
48
-
49
- try {
50
- // Exchange API Key for short-lived JWT token
51
- const httpUrl = this.serverUrl.replace(/^ws/, 'http').replace(/\/ws\/telemetry$/, '/auth/token');
52
- const res = await fetch(httpUrl, {
53
- method: 'POST',
54
- headers: { 'Content-Type': 'application/json' },
55
- body: JSON.stringify({ api_key: this.apiKey }),
56
- });
57
-
58
- if (!res.ok) {
59
- throw new Error(`Authentication failed with status ${res.status}`);
60
- }
61
-
62
- const data = await res.json();
63
- this.token = data.token;
64
-
65
- // Establish WebSocket Connection
66
- const wsUrl = `${this.serverUrl}?token=${encodeURIComponent(this.token!)}`;
67
- this.ws = new WebSocket(wsUrl);
68
-
69
- this.ws.onopen = () => {
70
- if (this.debug) console.log('[Vattara] Telemetry WebSocket connected.');
71
- this.flushBuffer();
72
- };
73
-
74
- this.ws.onclose = () => {
75
- if (this.debug) console.log('[Vattara] Telemetry WebSocket disconnected.');
76
- this.ws = null;
77
- };
78
-
79
- this.ws.onerror = (err) => {
80
- if (this.debug) console.error('[Vattara] WebSocket error:', err);
81
- };
82
-
83
- } catch (err) {
84
- if (this.debug) console.error('[Vattara] Initialization error:', err);
85
- } finally {
86
- this.isConnecting = false;
87
- }
88
- }
89
-
90
- /** Track PCM audio base64 frame (16kHz / 24kHz). */
91
- sendAudioFrame(base64Data: string, sampleRate?: number): void {
92
- this.sendEvent({
93
- type: 'audio_frame',
94
- sampleRate: sampleRate || this.sampleRate,
95
- data: base64Data,
96
- timestamp: Date.now(),
97
- });
98
- }
99
-
100
- /** Track customer barge-in / interruption event. */
101
- trackInterruption(event: InterruptionEvent): void {
102
- this.sendEvent({
103
- type: 'interruption',
104
- cutoffLatencyMs: event.cutoffLatencyMs,
105
- interruptedAt: event.interruptedAt || Date.now(),
106
- });
107
- }
108
-
109
- /** Track input/output token usage. */
110
- trackTokens(event: TokenUsageEvent): void {
111
- this.sendEvent({
112
- type: 'token_usage',
113
- inputTokens: event.inputTokens,
114
- outputTokens: event.outputTokens,
115
- model: event.model || 'gpt-4o-realtime',
116
- timestamp: Date.now(),
117
- });
118
- }
119
-
120
- /** Set dynamic custom properties/tags. */
121
- setTags(tags: Record<string, string>): void {
122
- this.tags = { ...this.tags, ...tags };
123
- }
124
-
125
- private sendEvent(payload: any): void {
126
- const fullPayload = { ...payload, tags: this.tags };
127
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
128
- this.ws.send(JSON.stringify(fullPayload));
129
- } else {
130
- this.buffer.push(fullPayload);
131
- if (!this.isConnecting && !this.ws) {
132
- this.init();
133
- }
134
- }
135
- }
136
-
137
- private flushBuffer(): void {
138
- while (this.buffer.length > 0 && this.ws && this.ws.readyState === WebSocket.OPEN) {
139
- const payload = this.buffer.shift();
140
- this.ws.send(JSON.stringify(payload));
141
- }
142
- }
143
- }
144
-
145
- /** Global Singleton Helper (CAST AI Style) */
146
- let _globalClient: VattaraObserveClient | null = null;
147
-
148
- export const Vattara = {
149
- init(options: VattaraOptions): VattaraObserveClient {
150
- _globalClient = new VattaraObserveClient(options);
151
- _globalClient.init();
152
- return _globalClient;
153
- },
154
-
155
- sendAudioFrame(base64Data: string, sampleRate?: number): void {
156
- _globalClient?.sendAudioFrame(base64Data, sampleRate);
157
- },
158
-
159
- trackInterruption(event: InterruptionEvent): void {
160
- _globalClient?.trackInterruption(event);
161
- },
162
-
163
- trackTokens(event: TokenUsageEvent): void {
164
- _globalClient?.trackTokens(event);
165
- },
166
-
167
- setTags(tags: Record<string, string>): void {
168
- _globalClient?.setTags(tags);
169
- },
170
- };
package/tsconfig.json DELETED
@@ -1,13 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "CommonJS",
5
- "declaration": true,
6
- "outDir": "./dist",
7
- "rootDir": "./src",
8
- "strict": true,
9
- "esModuleInterop": true,
10
- "skipLibCheck": true
11
- },
12
- "include": ["src/**/*"]
13
- }