@vattara/sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +51 -0
- package/dist/index.js +147 -0
- package/dist/index.mjs +121 -0
- package/package.json +26 -0
- package/src/index.ts +170 -0
- package/tsconfig.json +13 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
export interface VattaraOptions {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
serverUrl?: string;
|
|
8
|
+
sampleRate?: 16000 | 24000;
|
|
9
|
+
tags?: Record<string, string>;
|
|
10
|
+
debug?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface InterruptionEvent {
|
|
13
|
+
cutoffLatencyMs: number;
|
|
14
|
+
interruptedAt?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface TokenUsageEvent {
|
|
17
|
+
inputTokens: number;
|
|
18
|
+
outputTokens: number;
|
|
19
|
+
model?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class VattaraObserveClient {
|
|
22
|
+
private apiKey;
|
|
23
|
+
private serverUrl;
|
|
24
|
+
private sampleRate;
|
|
25
|
+
private tags;
|
|
26
|
+
private debug;
|
|
27
|
+
private ws;
|
|
28
|
+
private token;
|
|
29
|
+
private buffer;
|
|
30
|
+
private isConnecting;
|
|
31
|
+
constructor(options: VattaraOptions);
|
|
32
|
+
/** Initialize session by fetching JWT token and establishing WebSocket connection. */
|
|
33
|
+
init(): Promise<void>;
|
|
34
|
+
/** Track PCM audio base64 frame (16kHz / 24kHz). */
|
|
35
|
+
sendAudioFrame(base64Data: string, sampleRate?: number): void;
|
|
36
|
+
/** Track customer barge-in / interruption event. */
|
|
37
|
+
trackInterruption(event: InterruptionEvent): void;
|
|
38
|
+
/** Track input/output token usage. */
|
|
39
|
+
trackTokens(event: TokenUsageEvent): void;
|
|
40
|
+
/** Set dynamic custom properties/tags. */
|
|
41
|
+
setTags(tags: Record<string, string>): void;
|
|
42
|
+
private sendEvent;
|
|
43
|
+
private flushBuffer;
|
|
44
|
+
}
|
|
45
|
+
export declare const Vattara: {
|
|
46
|
+
init(options: VattaraOptions): VattaraObserveClient;
|
|
47
|
+
sendAudioFrame(base64Data: string, sampleRate?: number): void;
|
|
48
|
+
trackInterruption(event: InterruptionEvent): void;
|
|
49
|
+
trackTokens(event: TokenUsageEvent): void;
|
|
50
|
+
setTags(tags: Record<string, string>): void;
|
|
51
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Vattara: () => Vattara,
|
|
24
|
+
VattaraObserveClient: () => VattaraObserveClient
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
var VattaraObserveClient = class {
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.ws = null;
|
|
30
|
+
this.token = null;
|
|
31
|
+
this.buffer = [];
|
|
32
|
+
this.isConnecting = false;
|
|
33
|
+
this.apiKey = options.apiKey;
|
|
34
|
+
this.serverUrl = options.serverUrl || "wss://vattara-observe-production.up.railway.app/api/v1/ws/telemetry";
|
|
35
|
+
this.sampleRate = options.sampleRate || 24e3;
|
|
36
|
+
this.tags = options.tags || {};
|
|
37
|
+
this.debug = options.debug || false;
|
|
38
|
+
}
|
|
39
|
+
/** Initialize session by fetching JWT token and establishing WebSocket connection. */
|
|
40
|
+
async init() {
|
|
41
|
+
if (this.isConnecting || this.ws) return;
|
|
42
|
+
this.isConnecting = true;
|
|
43
|
+
try {
|
|
44
|
+
const httpUrl = this.serverUrl.replace(/^ws/, "http").replace(/\/ws\/telemetry$/, "/auth/token");
|
|
45
|
+
const res = await fetch(httpUrl, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: { "Content-Type": "application/json" },
|
|
48
|
+
body: JSON.stringify({ api_key: this.apiKey })
|
|
49
|
+
});
|
|
50
|
+
if (!res.ok) {
|
|
51
|
+
throw new Error(`Authentication failed with status ${res.status}`);
|
|
52
|
+
}
|
|
53
|
+
const data = await res.json();
|
|
54
|
+
this.token = data.token;
|
|
55
|
+
const wsUrl = `${this.serverUrl}?token=${encodeURIComponent(this.token)}`;
|
|
56
|
+
this.ws = new WebSocket(wsUrl);
|
|
57
|
+
this.ws.onopen = () => {
|
|
58
|
+
if (this.debug) console.log("[Vattara] Telemetry WebSocket connected.");
|
|
59
|
+
this.flushBuffer();
|
|
60
|
+
};
|
|
61
|
+
this.ws.onclose = () => {
|
|
62
|
+
if (this.debug) console.log("[Vattara] Telemetry WebSocket disconnected.");
|
|
63
|
+
this.ws = null;
|
|
64
|
+
};
|
|
65
|
+
this.ws.onerror = (err) => {
|
|
66
|
+
if (this.debug) console.error("[Vattara] WebSocket error:", err);
|
|
67
|
+
};
|
|
68
|
+
} catch (err) {
|
|
69
|
+
if (this.debug) console.error("[Vattara] Initialization error:", err);
|
|
70
|
+
} finally {
|
|
71
|
+
this.isConnecting = false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Track PCM audio base64 frame (16kHz / 24kHz). */
|
|
75
|
+
sendAudioFrame(base64Data, sampleRate) {
|
|
76
|
+
this.sendEvent({
|
|
77
|
+
type: "audio_frame",
|
|
78
|
+
sampleRate: sampleRate || this.sampleRate,
|
|
79
|
+
data: base64Data,
|
|
80
|
+
timestamp: Date.now()
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Track customer barge-in / interruption event. */
|
|
84
|
+
trackInterruption(event) {
|
|
85
|
+
this.sendEvent({
|
|
86
|
+
type: "interruption",
|
|
87
|
+
cutoffLatencyMs: event.cutoffLatencyMs,
|
|
88
|
+
interruptedAt: event.interruptedAt || Date.now()
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Track input/output token usage. */
|
|
92
|
+
trackTokens(event) {
|
|
93
|
+
this.sendEvent({
|
|
94
|
+
type: "token_usage",
|
|
95
|
+
inputTokens: event.inputTokens,
|
|
96
|
+
outputTokens: event.outputTokens,
|
|
97
|
+
model: event.model || "gpt-4o-realtime",
|
|
98
|
+
timestamp: Date.now()
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** Set dynamic custom properties/tags. */
|
|
102
|
+
setTags(tags) {
|
|
103
|
+
this.tags = { ...this.tags, ...tags };
|
|
104
|
+
}
|
|
105
|
+
sendEvent(payload) {
|
|
106
|
+
const fullPayload = { ...payload, tags: this.tags };
|
|
107
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
108
|
+
this.ws.send(JSON.stringify(fullPayload));
|
|
109
|
+
} else {
|
|
110
|
+
this.buffer.push(fullPayload);
|
|
111
|
+
if (!this.isConnecting && !this.ws) {
|
|
112
|
+
this.init();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
flushBuffer() {
|
|
117
|
+
while (this.buffer.length > 0 && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
118
|
+
const payload = this.buffer.shift();
|
|
119
|
+
this.ws.send(JSON.stringify(payload));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
var _globalClient = null;
|
|
124
|
+
var Vattara = {
|
|
125
|
+
init(options) {
|
|
126
|
+
_globalClient = new VattaraObserveClient(options);
|
|
127
|
+
_globalClient.init();
|
|
128
|
+
return _globalClient;
|
|
129
|
+
},
|
|
130
|
+
sendAudioFrame(base64Data, sampleRate) {
|
|
131
|
+
_globalClient?.sendAudioFrame(base64Data, sampleRate);
|
|
132
|
+
},
|
|
133
|
+
trackInterruption(event) {
|
|
134
|
+
_globalClient?.trackInterruption(event);
|
|
135
|
+
},
|
|
136
|
+
trackTokens(event) {
|
|
137
|
+
_globalClient?.trackTokens(event);
|
|
138
|
+
},
|
|
139
|
+
setTags(tags) {
|
|
140
|
+
_globalClient?.setTags(tags);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
144
|
+
0 && (module.exports = {
|
|
145
|
+
Vattara,
|
|
146
|
+
VattaraObserveClient
|
|
147
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var VattaraObserveClient = class {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.ws = null;
|
|
5
|
+
this.token = null;
|
|
6
|
+
this.buffer = [];
|
|
7
|
+
this.isConnecting = false;
|
|
8
|
+
this.apiKey = options.apiKey;
|
|
9
|
+
this.serverUrl = options.serverUrl || "wss://vattara-observe-production.up.railway.app/api/v1/ws/telemetry";
|
|
10
|
+
this.sampleRate = options.sampleRate || 24e3;
|
|
11
|
+
this.tags = options.tags || {};
|
|
12
|
+
this.debug = options.debug || false;
|
|
13
|
+
}
|
|
14
|
+
/** Initialize session by fetching JWT token and establishing WebSocket connection. */
|
|
15
|
+
async init() {
|
|
16
|
+
if (this.isConnecting || this.ws) return;
|
|
17
|
+
this.isConnecting = true;
|
|
18
|
+
try {
|
|
19
|
+
const httpUrl = this.serverUrl.replace(/^ws/, "http").replace(/\/ws\/telemetry$/, "/auth/token");
|
|
20
|
+
const res = await fetch(httpUrl, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify({ api_key: this.apiKey })
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
throw new Error(`Authentication failed with status ${res.status}`);
|
|
27
|
+
}
|
|
28
|
+
const data = await res.json();
|
|
29
|
+
this.token = data.token;
|
|
30
|
+
const wsUrl = `${this.serverUrl}?token=${encodeURIComponent(this.token)}`;
|
|
31
|
+
this.ws = new WebSocket(wsUrl);
|
|
32
|
+
this.ws.onopen = () => {
|
|
33
|
+
if (this.debug) console.log("[Vattara] Telemetry WebSocket connected.");
|
|
34
|
+
this.flushBuffer();
|
|
35
|
+
};
|
|
36
|
+
this.ws.onclose = () => {
|
|
37
|
+
if (this.debug) console.log("[Vattara] Telemetry WebSocket disconnected.");
|
|
38
|
+
this.ws = null;
|
|
39
|
+
};
|
|
40
|
+
this.ws.onerror = (err) => {
|
|
41
|
+
if (this.debug) console.error("[Vattara] WebSocket error:", err);
|
|
42
|
+
};
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (this.debug) console.error("[Vattara] Initialization error:", err);
|
|
45
|
+
} finally {
|
|
46
|
+
this.isConnecting = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Track PCM audio base64 frame (16kHz / 24kHz). */
|
|
50
|
+
sendAudioFrame(base64Data, sampleRate) {
|
|
51
|
+
this.sendEvent({
|
|
52
|
+
type: "audio_frame",
|
|
53
|
+
sampleRate: sampleRate || this.sampleRate,
|
|
54
|
+
data: base64Data,
|
|
55
|
+
timestamp: Date.now()
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/** Track customer barge-in / interruption event. */
|
|
59
|
+
trackInterruption(event) {
|
|
60
|
+
this.sendEvent({
|
|
61
|
+
type: "interruption",
|
|
62
|
+
cutoffLatencyMs: event.cutoffLatencyMs,
|
|
63
|
+
interruptedAt: event.interruptedAt || Date.now()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** Track input/output token usage. */
|
|
67
|
+
trackTokens(event) {
|
|
68
|
+
this.sendEvent({
|
|
69
|
+
type: "token_usage",
|
|
70
|
+
inputTokens: event.inputTokens,
|
|
71
|
+
outputTokens: event.outputTokens,
|
|
72
|
+
model: event.model || "gpt-4o-realtime",
|
|
73
|
+
timestamp: Date.now()
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
/** Set dynamic custom properties/tags. */
|
|
77
|
+
setTags(tags) {
|
|
78
|
+
this.tags = { ...this.tags, ...tags };
|
|
79
|
+
}
|
|
80
|
+
sendEvent(payload) {
|
|
81
|
+
const fullPayload = { ...payload, tags: this.tags };
|
|
82
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
83
|
+
this.ws.send(JSON.stringify(fullPayload));
|
|
84
|
+
} else {
|
|
85
|
+
this.buffer.push(fullPayload);
|
|
86
|
+
if (!this.isConnecting && !this.ws) {
|
|
87
|
+
this.init();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
flushBuffer() {
|
|
92
|
+
while (this.buffer.length > 0 && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
93
|
+
const payload = this.buffer.shift();
|
|
94
|
+
this.ws.send(JSON.stringify(payload));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var _globalClient = null;
|
|
99
|
+
var Vattara = {
|
|
100
|
+
init(options) {
|
|
101
|
+
_globalClient = new VattaraObserveClient(options);
|
|
102
|
+
_globalClient.init();
|
|
103
|
+
return _globalClient;
|
|
104
|
+
},
|
|
105
|
+
sendAudioFrame(base64Data, sampleRate) {
|
|
106
|
+
_globalClient?.sendAudioFrame(base64Data, sampleRate);
|
|
107
|
+
},
|
|
108
|
+
trackInterruption(event) {
|
|
109
|
+
_globalClient?.trackInterruption(event);
|
|
110
|
+
},
|
|
111
|
+
trackTokens(event) {
|
|
112
|
+
_globalClient?.trackTokens(event);
|
|
113
|
+
},
|
|
114
|
+
setTags(tags) {
|
|
115
|
+
_globalClient?.setTags(tags);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
export {
|
|
119
|
+
Vattara,
|
|
120
|
+
VattaraObserveClient
|
|
121
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vattara/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official TypeScript SDK for Vattara Observe — Realtime AI Voice Agent Observability & CLEAR 2.0 Evaluation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup src/index.ts --format cjs,esm && tsc --emitDeclarationOnly"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"vattara",
|
|
13
|
+
"voice-ai",
|
|
14
|
+
"observability",
|
|
15
|
+
"clear-framework",
|
|
16
|
+
"telemetry",
|
|
17
|
+
"retell",
|
|
18
|
+
"vapi"
|
|
19
|
+
],
|
|
20
|
+
"author": "Vattara AI",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"tsup": "^8.3.5",
|
|
24
|
+
"typescript": "^5.7.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
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
ADDED