agrasya-voice-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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Agrasya Voice SDK
2
+
3
+ A simple, secure SDK for integrated voice AI.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @agrasya/voice-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { AgrasyaVoiceSDK } from "@agrasya/voice-sdk";
15
+
16
+ // Initialize the SDK
17
+ const sdk = new AgrasyaVoiceSDK({
18
+ apiKey: "your-api-key",
19
+ baseUrl: "https://api.agrasya.com",
20
+ language: "en",
21
+ debug: false,
22
+ });
23
+
24
+ // Start a voice session
25
+ await sdk.start();
26
+
27
+ // Handle events
28
+ const events = {
29
+ onStatusChange: (status) => console.log("Status:", status),
30
+ onMessageReceived: (message) => console.log("Message:", message),
31
+ };
32
+
33
+ const sdkWithEvents = new AgrasyaVoiceSDK({ apiKey: "your-api-key" }, events);
34
+ ```
35
+
36
+ ## Development
37
+
38
+ ```bash
39
+ # Install dependencies
40
+ npm install
41
+
42
+ # Build the SDK
43
+ npm run build
44
+
45
+ # Watch mode for development
46
+ npm run dev
47
+
48
+ # Run tests
49
+ npm test
50
+
51
+ # Run linting
52
+ npm run lint
53
+ ```
54
+
55
+ ## Publishing
56
+
57
+ See [PUBLISHING.md](./PUBLISHING.md) for instructions on publishing to npm.
58
+
59
+ ## License
60
+
61
+ MIT
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Configuration for the Agrasya Voice SDK.
3
+ */
4
+ interface AgrasyaSDKConfig {
5
+ /** The API key provided by AGRASYA */
6
+ apiKey: string;
7
+ /** The language for the voice session (e.g., 'en', 'hi') */
8
+ language?: string;
9
+ /** Custom backend URL (optional) */
10
+ baseUrl?: string;
11
+ /** Debug mode */
12
+ debug?: boolean;
13
+ }
14
+ /**
15
+ * Events emitted by the Agrasya SDK.
16
+ */
17
+ interface AgrasyaEvents {
18
+ /** Emitted when the session starts successfully */
19
+ onStart?: () => void;
20
+ /** Emitted when the session ends */
21
+ onStop?: () => void;
22
+ /** Emitted when an error occurs */
23
+ onError?: (error: Error) => void;
24
+ /** Emitted when a transcript is received */
25
+ onTranscript?: (data: {
26
+ text: string;
27
+ isFinal: boolean;
28
+ }) => void;
29
+ /** Emitted when the audio level changes (0-1) */
30
+ onAudioLevel?: (level: number) => void;
31
+ /** Emitted when the connection status changes */
32
+ onStatusChange?: (status: "idle" | "initializing" | "connecting" | "connected" | "error") => void;
33
+ }
34
+
35
+ /**
36
+ * AgrasyaVoiceSDK - A simple, secure SDK for integrated voice AI.
37
+ */
38
+ declare class AgrasyaVoiceSDK {
39
+ private config;
40
+ private events;
41
+ private peerConnection;
42
+ private dataChannel;
43
+ private audioStream;
44
+ private sessionId;
45
+ private status;
46
+ constructor(config: AgrasyaSDKConfig, events?: AgrasyaEvents);
47
+ private log;
48
+ private setStatus;
49
+ /**
50
+ * Starts a voice session.
51
+ */
52
+ start(): Promise<void>;
53
+ /**
54
+ * Stops the current voice session.
55
+ */
56
+ stop(): Promise<void>;
57
+ private initializeBackendSession;
58
+ private connectWebRTC;
59
+ private notifySessionEnd;
60
+ }
61
+
62
+ export { AgrasyaVoiceSDK };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Configuration for the Agrasya Voice SDK.
3
+ */
4
+ interface AgrasyaSDKConfig {
5
+ /** The API key provided by AGRASYA */
6
+ apiKey: string;
7
+ /** The language for the voice session (e.g., 'en', 'hi') */
8
+ language?: string;
9
+ /** Custom backend URL (optional) */
10
+ baseUrl?: string;
11
+ /** Debug mode */
12
+ debug?: boolean;
13
+ }
14
+ /**
15
+ * Events emitted by the Agrasya SDK.
16
+ */
17
+ interface AgrasyaEvents {
18
+ /** Emitted when the session starts successfully */
19
+ onStart?: () => void;
20
+ /** Emitted when the session ends */
21
+ onStop?: () => void;
22
+ /** Emitted when an error occurs */
23
+ onError?: (error: Error) => void;
24
+ /** Emitted when a transcript is received */
25
+ onTranscript?: (data: {
26
+ text: string;
27
+ isFinal: boolean;
28
+ }) => void;
29
+ /** Emitted when the audio level changes (0-1) */
30
+ onAudioLevel?: (level: number) => void;
31
+ /** Emitted when the connection status changes */
32
+ onStatusChange?: (status: "idle" | "initializing" | "connecting" | "connected" | "error") => void;
33
+ }
34
+
35
+ /**
36
+ * AgrasyaVoiceSDK - A simple, secure SDK for integrated voice AI.
37
+ */
38
+ declare class AgrasyaVoiceSDK {
39
+ private config;
40
+ private events;
41
+ private peerConnection;
42
+ private dataChannel;
43
+ private audioStream;
44
+ private sessionId;
45
+ private status;
46
+ constructor(config: AgrasyaSDKConfig, events?: AgrasyaEvents);
47
+ private log;
48
+ private setStatus;
49
+ /**
50
+ * Starts a voice session.
51
+ */
52
+ start(): Promise<void>;
53
+ /**
54
+ * Stops the current voice session.
55
+ */
56
+ stop(): Promise<void>;
57
+ private initializeBackendSession;
58
+ private connectWebRTC;
59
+ private notifySessionEnd;
60
+ }
61
+
62
+ export { AgrasyaVoiceSDK };
package/dist/index.js ADDED
@@ -0,0 +1,185 @@
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
+ AgrasyaVoiceSDK: () => AgrasyaVoiceSDK
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var AgrasyaVoiceSDK = class {
27
+ config;
28
+ events;
29
+ peerConnection = null;
30
+ dataChannel = null;
31
+ audioStream = null;
32
+ sessionId = null;
33
+ status = "idle";
34
+ constructor(config, events = {}) {
35
+ this.config = {
36
+ baseUrl: "http://localhost:4001",
37
+ language: "en",
38
+ debug: false,
39
+ ...config
40
+ };
41
+ this.events = events;
42
+ }
43
+ log(...args) {
44
+ if (this.config.debug) {
45
+ console.log("[AgrasyaSDK]", ...args);
46
+ }
47
+ }
48
+ setStatus(status) {
49
+ this.status = status;
50
+ this.events.onStatusChange?.(status);
51
+ }
52
+ /**
53
+ * Starts a voice session.
54
+ */
55
+ async start() {
56
+ if (this.status !== "idle") {
57
+ throw new Error("Session is already in progress or initializing");
58
+ }
59
+ try {
60
+ this.setStatus("initializing");
61
+ this.log("Initializing session...");
62
+ const sessionData = await this.initializeBackendSession();
63
+ this.sessionId = sessionData.sessionId;
64
+ await this.connectWebRTC(sessionData.ephemeralKey);
65
+ this.setStatus("connected");
66
+ this.events.onStart?.();
67
+ } catch (error) {
68
+ this.setStatus("error");
69
+ const err = error instanceof Error ? error : new Error(String(error));
70
+ this.events.onError?.(err);
71
+ throw err;
72
+ }
73
+ }
74
+ /**
75
+ * Stops the current voice session.
76
+ */
77
+ async stop() {
78
+ this.log("Stopping session...");
79
+ if (this.peerConnection) {
80
+ this.peerConnection.close();
81
+ this.peerConnection = null;
82
+ }
83
+ if (this.dataChannel) {
84
+ this.dataChannel.close();
85
+ this.dataChannel = null;
86
+ }
87
+ if (this.audioStream) {
88
+ this.audioStream.getTracks().forEach((track) => track.stop());
89
+ this.audioStream = null;
90
+ }
91
+ if (this.sessionId) {
92
+ this.notifySessionEnd(this.sessionId).catch(
93
+ (err) => this.log("Failed to notify session end", err)
94
+ );
95
+ this.sessionId = null;
96
+ }
97
+ this.setStatus("idle");
98
+ this.events.onStop?.();
99
+ }
100
+ async initializeBackendSession() {
101
+ const url = `${this.config.baseUrl}/v1/agrasya/client-token?language=${this.config.language}`;
102
+ const response = await fetch(url, {
103
+ method: "GET",
104
+ headers: {
105
+ "x-agrasya-client-api-key": this.config.apiKey,
106
+ "Content-Type": "application/json"
107
+ }
108
+ });
109
+ if (!response.ok) {
110
+ const errorData = await response.json().catch(() => ({ message: response.statusText }));
111
+ throw new Error(
112
+ `Failed to initialize session: ${errorData.message || response.statusText}`
113
+ );
114
+ }
115
+ const result = await response.json();
116
+ if (!result.success) {
117
+ throw new Error(result.message || "Backend initialization failed");
118
+ }
119
+ return result.data;
120
+ }
121
+ async connectWebRTC(ephemeralKey) {
122
+ this.setStatus("connecting");
123
+ this.log("Connecting to AI Provider via WebRTC...");
124
+ this.peerConnection = new RTCPeerConnection();
125
+ this.peerConnection.ontrack = (event) => {
126
+ this.log("Received remote audio track");
127
+ const audio = new Audio();
128
+ audio.srcObject = event.streams[0];
129
+ audio.play().catch((err) => this.log("Failed to play audio", err));
130
+ };
131
+ this.audioStream = await navigator.mediaDevices.getUserMedia({
132
+ audio: true
133
+ });
134
+ this.audioStream.getTracks().forEach((track) => {
135
+ if (this.peerConnection && this.audioStream) {
136
+ this.peerConnection.addTrack(track, this.audioStream);
137
+ }
138
+ });
139
+ this.dataChannel = this.peerConnection.createDataChannel("oai-events");
140
+ this.dataChannel.onmessage = (event) => {
141
+ const data = JSON.parse(event.data);
142
+ this.log("Received event:", data.type);
143
+ if (data.type === "response.audio_transcript.delta") {
144
+ this.events.onTranscript?.({ text: data.delta, isFinal: false });
145
+ } else if (data.type === "response.audio_transcript.done") {
146
+ this.events.onTranscript?.({ text: data.transcript, isFinal: true });
147
+ }
148
+ };
149
+ const offer = await this.peerConnection.createOffer();
150
+ await this.peerConnection.setLocalDescription(offer);
151
+ const baseUrl = "https://api.openai.com/v1/realtime";
152
+ const model = "gpt-4o-realtime-preview-2024-12-17";
153
+ const sdpResponse = await fetch(`${baseUrl}?model=${model}`, {
154
+ method: "POST",
155
+ body: offer.sdp,
156
+ headers: {
157
+ Authorization: `Bearer ${ephemeralKey}`,
158
+ "Content-Type": "application/sdp"
159
+ }
160
+ });
161
+ if (!sdpResponse.ok) {
162
+ throw new Error(`OpenAI SDP exchange failed: ${sdpResponse.statusText}`);
163
+ }
164
+ const answer = {
165
+ type: "answer",
166
+ sdp: await sdpResponse.text()
167
+ };
168
+ await this.peerConnection.setRemoteDescription(answer);
169
+ this.log("WebRTC connection established");
170
+ }
171
+ async notifySessionEnd(sessionId) {
172
+ const url = `${this.config.baseUrl}/v1/agrasya/session-end/${sessionId}`;
173
+ await fetch(url, {
174
+ method: "PUT",
175
+ headers: {
176
+ "x-agrasya-client-api-key": this.config.apiKey,
177
+ "Content-Type": "application/json"
178
+ }
179
+ });
180
+ }
181
+ };
182
+ // Annotate the CommonJS export names for ESM import in node:
183
+ 0 && (module.exports = {
184
+ AgrasyaVoiceSDK
185
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,160 @@
1
+ // src/index.ts
2
+ var AgrasyaVoiceSDK = class {
3
+ config;
4
+ events;
5
+ peerConnection = null;
6
+ dataChannel = null;
7
+ audioStream = null;
8
+ sessionId = null;
9
+ status = "idle";
10
+ constructor(config, events = {}) {
11
+ this.config = {
12
+ baseUrl: "http://localhost:4001",
13
+ language: "en",
14
+ debug: false,
15
+ ...config
16
+ };
17
+ this.events = events;
18
+ }
19
+ log(...args) {
20
+ if (this.config.debug) {
21
+ console.log("[AgrasyaSDK]", ...args);
22
+ }
23
+ }
24
+ setStatus(status) {
25
+ this.status = status;
26
+ this.events.onStatusChange?.(status);
27
+ }
28
+ /**
29
+ * Starts a voice session.
30
+ */
31
+ async start() {
32
+ if (this.status !== "idle") {
33
+ throw new Error("Session is already in progress or initializing");
34
+ }
35
+ try {
36
+ this.setStatus("initializing");
37
+ this.log("Initializing session...");
38
+ const sessionData = await this.initializeBackendSession();
39
+ this.sessionId = sessionData.sessionId;
40
+ await this.connectWebRTC(sessionData.ephemeralKey);
41
+ this.setStatus("connected");
42
+ this.events.onStart?.();
43
+ } catch (error) {
44
+ this.setStatus("error");
45
+ const err = error instanceof Error ? error : new Error(String(error));
46
+ this.events.onError?.(err);
47
+ throw err;
48
+ }
49
+ }
50
+ /**
51
+ * Stops the current voice session.
52
+ */
53
+ async stop() {
54
+ this.log("Stopping session...");
55
+ if (this.peerConnection) {
56
+ this.peerConnection.close();
57
+ this.peerConnection = null;
58
+ }
59
+ if (this.dataChannel) {
60
+ this.dataChannel.close();
61
+ this.dataChannel = null;
62
+ }
63
+ if (this.audioStream) {
64
+ this.audioStream.getTracks().forEach((track) => track.stop());
65
+ this.audioStream = null;
66
+ }
67
+ if (this.sessionId) {
68
+ this.notifySessionEnd(this.sessionId).catch(
69
+ (err) => this.log("Failed to notify session end", err)
70
+ );
71
+ this.sessionId = null;
72
+ }
73
+ this.setStatus("idle");
74
+ this.events.onStop?.();
75
+ }
76
+ async initializeBackendSession() {
77
+ const url = `${this.config.baseUrl}/v1/agrasya/client-token?language=${this.config.language}`;
78
+ const response = await fetch(url, {
79
+ method: "GET",
80
+ headers: {
81
+ "x-agrasya-client-api-key": this.config.apiKey,
82
+ "Content-Type": "application/json"
83
+ }
84
+ });
85
+ if (!response.ok) {
86
+ const errorData = await response.json().catch(() => ({ message: response.statusText }));
87
+ throw new Error(
88
+ `Failed to initialize session: ${errorData.message || response.statusText}`
89
+ );
90
+ }
91
+ const result = await response.json();
92
+ if (!result.success) {
93
+ throw new Error(result.message || "Backend initialization failed");
94
+ }
95
+ return result.data;
96
+ }
97
+ async connectWebRTC(ephemeralKey) {
98
+ this.setStatus("connecting");
99
+ this.log("Connecting to AI Provider via WebRTC...");
100
+ this.peerConnection = new RTCPeerConnection();
101
+ this.peerConnection.ontrack = (event) => {
102
+ this.log("Received remote audio track");
103
+ const audio = new Audio();
104
+ audio.srcObject = event.streams[0];
105
+ audio.play().catch((err) => this.log("Failed to play audio", err));
106
+ };
107
+ this.audioStream = await navigator.mediaDevices.getUserMedia({
108
+ audio: true
109
+ });
110
+ this.audioStream.getTracks().forEach((track) => {
111
+ if (this.peerConnection && this.audioStream) {
112
+ this.peerConnection.addTrack(track, this.audioStream);
113
+ }
114
+ });
115
+ this.dataChannel = this.peerConnection.createDataChannel("oai-events");
116
+ this.dataChannel.onmessage = (event) => {
117
+ const data = JSON.parse(event.data);
118
+ this.log("Received event:", data.type);
119
+ if (data.type === "response.audio_transcript.delta") {
120
+ this.events.onTranscript?.({ text: data.delta, isFinal: false });
121
+ } else if (data.type === "response.audio_transcript.done") {
122
+ this.events.onTranscript?.({ text: data.transcript, isFinal: true });
123
+ }
124
+ };
125
+ const offer = await this.peerConnection.createOffer();
126
+ await this.peerConnection.setLocalDescription(offer);
127
+ const baseUrl = "https://api.openai.com/v1/realtime";
128
+ const model = "gpt-4o-realtime-preview-2024-12-17";
129
+ const sdpResponse = await fetch(`${baseUrl}?model=${model}`, {
130
+ method: "POST",
131
+ body: offer.sdp,
132
+ headers: {
133
+ Authorization: `Bearer ${ephemeralKey}`,
134
+ "Content-Type": "application/sdp"
135
+ }
136
+ });
137
+ if (!sdpResponse.ok) {
138
+ throw new Error(`OpenAI SDP exchange failed: ${sdpResponse.statusText}`);
139
+ }
140
+ const answer = {
141
+ type: "answer",
142
+ sdp: await sdpResponse.text()
143
+ };
144
+ await this.peerConnection.setRemoteDescription(answer);
145
+ this.log("WebRTC connection established");
146
+ }
147
+ async notifySessionEnd(sessionId) {
148
+ const url = `${this.config.baseUrl}/v1/agrasya/session-end/${sessionId}`;
149
+ await fetch(url, {
150
+ method: "PUT",
151
+ headers: {
152
+ "x-agrasya-client-api-key": this.config.apiKey,
153
+ "Content-Type": "application/json"
154
+ }
155
+ });
156
+ }
157
+ };
158
+ export {
159
+ AgrasyaVoiceSDK
160
+ };
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "agrasya-voice-sdk",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsup src/index.ts --format cjs,esm --dts",
12
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts",
13
+ "lint": "eslint src/**/*.ts",
14
+ "test": "jest"
15
+ }
16
+ }