@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.
- package/dist/index.d.mts +96 -0
- package/dist/index.d.ts +50 -5
- package/dist/index.js +120 -4
- package/dist/index.mjs +120 -4
- package/package.json +6 -2
- package/src/index.ts +0 -170
- package/tsconfig.json +0 -13
package/dist/index.d.mts
ADDED
|
@@ -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
|
-
|
|
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
|
-
|
|
17
|
+
interface InterruptionEvent {
|
|
13
18
|
cutoffLatencyMs: number;
|
|
14
19
|
interruptedAt?: number;
|
|
15
20
|
}
|
|
16
|
-
|
|
21
|
+
interface TokenUsageEvent {
|
|
17
22
|
inputTokens: number;
|
|
18
23
|
outputTokens: number;
|
|
19
24
|
model?: string;
|
|
20
25
|
}
|
|
21
|
-
|
|
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
|
-
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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
|
|
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