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 +61 -0
- package/dist/index.d.mts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +185 -0
- package/dist/index.mjs +160 -0
- package/package.json +16 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|