phonic 0.6.1 → 0.8.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 +10 -27
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +68 -15
- package/dist/index.mjs +68 -15
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm i phonic
|
|
|
17
17
|
|
|
18
18
|
## Setup
|
|
19
19
|
|
|
20
|
-
Grab an API key from [Phonic
|
|
20
|
+
Grab an API key from the [Phonic API Keys](https://phonic.co/api-keys) section and pass it to the Phonic constructor.
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
23
|
import { Phonic } from "phonic";
|
|
@@ -38,7 +38,7 @@ if (error === null) {
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
### Get voice by
|
|
41
|
+
### Get voice by ID
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
44
|
const { data, error } = await phonic.voices.get("meredith");
|
|
@@ -50,23 +50,10 @@ if (error === null) {
|
|
|
50
50
|
|
|
51
51
|
### Speech-to-speech via WebSocket
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
To start a conversation, open a WebSocket connection:
|
|
54
54
|
|
|
55
55
|
```ts
|
|
56
|
-
const { data, error } = await phonic.sts.websocket(
|
|
57
|
-
|
|
58
|
-
if (error !== null) {
|
|
59
|
-
throw new Error(error.message);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Here we know that the WebSocket connection is open.
|
|
63
|
-
const { phonicWebSocket } = data;
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Send config params for the conversation:
|
|
67
|
-
|
|
68
|
-
```ts
|
|
69
|
-
phonicWebSocket.config({
|
|
56
|
+
const { data, error } = await phonic.sts.websocket({
|
|
70
57
|
input_format: "mulaw_8000",
|
|
71
58
|
|
|
72
59
|
// Optional fields
|
|
@@ -75,6 +62,12 @@ phonicWebSocket.config({
|
|
|
75
62
|
voice_id: "meredith",
|
|
76
63
|
output_format: "mulaw_8000"
|
|
77
64
|
});
|
|
65
|
+
|
|
66
|
+
if (error !== null) {
|
|
67
|
+
throw new Error(`Failed to start conversation: ${error.message}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { phonicWebSocket } = data;
|
|
78
71
|
```
|
|
79
72
|
|
|
80
73
|
Stream input (user) audio chunks:
|
|
@@ -132,16 +125,6 @@ phonicWebSocket.onError((event) => {
|
|
|
132
125
|
});
|
|
133
126
|
```
|
|
134
127
|
|
|
135
|
-
## Publish a new version on npm
|
|
136
|
-
|
|
137
|
-
1. `bunx changeset`
|
|
138
|
-
2. `git add .`
|
|
139
|
-
3. `git commit -m "Add changeset"`
|
|
140
|
-
4. `git push`
|
|
141
|
-
|
|
142
|
-
This should trigger the `publish` github workflow that will create a Pull Request named "Version Packages".
|
|
143
|
-
Once this Pull Request is merged, the new version will be published on npm.
|
|
144
|
-
|
|
145
128
|
## License
|
|
146
129
|
|
|
147
130
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -26,6 +26,8 @@ type PhonicSTSConfig = {
|
|
|
26
26
|
output_format?: "pcm_44100" | "mulaw_8000";
|
|
27
27
|
};
|
|
28
28
|
type PhonicSTSWebSocketResponseMessage = {
|
|
29
|
+
type: "ready_to_start_conversation";
|
|
30
|
+
} | {
|
|
29
31
|
type: "input_text";
|
|
30
32
|
text: string;
|
|
31
33
|
} | {
|
|
@@ -62,7 +64,6 @@ declare class PhonicSTSWebSocket {
|
|
|
62
64
|
onMessage(callback: OnMessageCallback): void;
|
|
63
65
|
onClose(callback: OnCloseCallback): void;
|
|
64
66
|
onError(callback: OnErrorCallback): void;
|
|
65
|
-
config(message: PhonicSTSConfig): void;
|
|
66
67
|
audioChunk(message: {
|
|
67
68
|
audio: string;
|
|
68
69
|
}): void;
|
|
@@ -72,7 +73,8 @@ declare class PhonicSTSWebSocket {
|
|
|
72
73
|
declare class SpeechToSpeech {
|
|
73
74
|
private readonly phonic;
|
|
74
75
|
constructor(phonic: Phonic);
|
|
75
|
-
|
|
76
|
+
private connectToPhonicAPI;
|
|
77
|
+
websocket(config: PhonicSTSConfig): DataOrError<{
|
|
76
78
|
phonicWebSocket: PhonicSTSWebSocket;
|
|
77
79
|
}>;
|
|
78
80
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ type PhonicSTSConfig = {
|
|
|
26
26
|
output_format?: "pcm_44100" | "mulaw_8000";
|
|
27
27
|
};
|
|
28
28
|
type PhonicSTSWebSocketResponseMessage = {
|
|
29
|
+
type: "ready_to_start_conversation";
|
|
30
|
+
} | {
|
|
29
31
|
type: "input_text";
|
|
30
32
|
text: string;
|
|
31
33
|
} | {
|
|
@@ -62,7 +64,6 @@ declare class PhonicSTSWebSocket {
|
|
|
62
64
|
onMessage(callback: OnMessageCallback): void;
|
|
63
65
|
onClose(callback: OnCloseCallback): void;
|
|
64
66
|
onError(callback: OnErrorCallback): void;
|
|
65
|
-
config(message: PhonicSTSConfig): void;
|
|
66
67
|
audioChunk(message: {
|
|
67
68
|
audio: string;
|
|
68
69
|
}): void;
|
|
@@ -72,7 +73,8 @@ declare class PhonicSTSWebSocket {
|
|
|
72
73
|
declare class SpeechToSpeech {
|
|
73
74
|
private readonly phonic;
|
|
74
75
|
constructor(phonic: Phonic);
|
|
75
|
-
|
|
76
|
+
private connectToPhonicAPI;
|
|
77
|
+
websocket(config: PhonicSTSConfig): DataOrError<{
|
|
76
78
|
phonicWebSocket: PhonicSTSWebSocket;
|
|
77
79
|
}>;
|
|
78
80
|
}
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
|
|
37
37
|
// package.json
|
|
38
|
-
var version = "0.
|
|
38
|
+
var version = "0.8.0";
|
|
39
39
|
|
|
40
40
|
// src/sts/index.ts
|
|
41
41
|
var import_ws = __toESM(require("ws"));
|
|
@@ -71,7 +71,6 @@ var PhonicSTSWebSocket = class {
|
|
|
71
71
|
this.onMessage = this.onMessage.bind(this);
|
|
72
72
|
this.onClose = this.onClose.bind(this);
|
|
73
73
|
this.onError = this.onError.bind(this);
|
|
74
|
-
this.config = this.config.bind(this);
|
|
75
74
|
this.audioChunk = this.audioChunk.bind(this);
|
|
76
75
|
this.close = this.close.bind(this);
|
|
77
76
|
}
|
|
@@ -87,14 +86,6 @@ var PhonicSTSWebSocket = class {
|
|
|
87
86
|
onError(callback) {
|
|
88
87
|
this.onErrorCallback = callback;
|
|
89
88
|
}
|
|
90
|
-
config(message) {
|
|
91
|
-
this.ws.send(
|
|
92
|
-
JSON.stringify({
|
|
93
|
-
type: "config",
|
|
94
|
-
...message
|
|
95
|
-
})
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
89
|
audioChunk(message) {
|
|
99
90
|
this.ws.send(
|
|
100
91
|
JSON.stringify({
|
|
@@ -109,21 +100,43 @@ var PhonicSTSWebSocket = class {
|
|
|
109
100
|
};
|
|
110
101
|
|
|
111
102
|
// src/sts/index.ts
|
|
103
|
+
var phonicApiCloseCodes = {
|
|
104
|
+
insuffucientCapacityAvailable: 4004
|
|
105
|
+
};
|
|
112
106
|
var SpeechToSpeech = class {
|
|
113
107
|
constructor(phonic) {
|
|
114
108
|
this.phonic = phonic;
|
|
115
109
|
}
|
|
116
|
-
async
|
|
110
|
+
async connectToPhonicAPI(phonicApiWsUrl, config) {
|
|
117
111
|
return new Promise((resolve) => {
|
|
118
|
-
const
|
|
119
|
-
const ws = new import_ws.default(`${wsBaseUrl}/v1/sts/ws`, {
|
|
112
|
+
const ws = new import_ws.default(phonicApiWsUrl, {
|
|
120
113
|
headers: {
|
|
121
114
|
Authorization: `Bearer ${this.phonic.apiKey}`
|
|
122
115
|
}
|
|
123
116
|
});
|
|
124
117
|
ws.onopen = () => {
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
ws.send(
|
|
119
|
+
JSON.stringify({
|
|
120
|
+
type: "config",
|
|
121
|
+
...config
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
ws.onmessage = (event) => {
|
|
126
|
+
if (typeof event.data !== "string") {
|
|
127
|
+
throw new Error("Received non-string message");
|
|
128
|
+
}
|
|
129
|
+
const dataObj = JSON.parse(
|
|
130
|
+
event.data
|
|
131
|
+
);
|
|
132
|
+
if (dataObj.type === "ready_to_start_conversation") {
|
|
133
|
+
resolve({
|
|
134
|
+
data: {
|
|
135
|
+
phonicWebSocket: new PhonicSTSWebSocket(ws)
|
|
136
|
+
},
|
|
137
|
+
error: null
|
|
138
|
+
});
|
|
139
|
+
}
|
|
127
140
|
};
|
|
128
141
|
ws.onerror = (error) => {
|
|
129
142
|
resolve({
|
|
@@ -133,8 +146,48 @@ var SpeechToSpeech = class {
|
|
|
133
146
|
}
|
|
134
147
|
});
|
|
135
148
|
};
|
|
149
|
+
ws.onclose = (event) => {
|
|
150
|
+
if (event.code === phonicApiCloseCodes.insuffucientCapacityAvailable) {
|
|
151
|
+
resolve({
|
|
152
|
+
data: null,
|
|
153
|
+
error: {
|
|
154
|
+
message: event.reason,
|
|
155
|
+
code: "insuffucient_capacity_available"
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
136
160
|
});
|
|
137
161
|
}
|
|
162
|
+
async websocket(config) {
|
|
163
|
+
const wsBaseUrl = this.phonic.baseUrl.replace(/^http/, "ws");
|
|
164
|
+
const phonicApiWsUrl = `${wsBaseUrl}/v1/sts/ws`;
|
|
165
|
+
let retryNumber = 0;
|
|
166
|
+
const maxRetries = 14;
|
|
167
|
+
const retryDelay = 15e3;
|
|
168
|
+
while (true) {
|
|
169
|
+
const connectResult = await this.connectToPhonicAPI(
|
|
170
|
+
phonicApiWsUrl,
|
|
171
|
+
config
|
|
172
|
+
);
|
|
173
|
+
if (connectResult.data !== null) {
|
|
174
|
+
return connectResult;
|
|
175
|
+
}
|
|
176
|
+
if (connectResult.error.code === "insuffucient_capacity_available") {
|
|
177
|
+
if (retryNumber >= maxRetries) {
|
|
178
|
+
return connectResult;
|
|
179
|
+
}
|
|
180
|
+
console.info(
|
|
181
|
+
`${connectResult.error.message}, will retry in ${retryDelay / 1e3}sec`
|
|
182
|
+
);
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
184
|
+
retryNumber += 1;
|
|
185
|
+
console.info(`Retrying... ${retryNumber}/${maxRetries}`);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
return connectResult;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
138
191
|
};
|
|
139
192
|
|
|
140
193
|
// src/voices/index.ts
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// package.json
|
|
2
|
-
var version = "0.
|
|
2
|
+
var version = "0.8.0";
|
|
3
3
|
|
|
4
4
|
// src/sts/index.ts
|
|
5
5
|
import WebSocket from "ws";
|
|
@@ -35,7 +35,6 @@ var PhonicSTSWebSocket = class {
|
|
|
35
35
|
this.onMessage = this.onMessage.bind(this);
|
|
36
36
|
this.onClose = this.onClose.bind(this);
|
|
37
37
|
this.onError = this.onError.bind(this);
|
|
38
|
-
this.config = this.config.bind(this);
|
|
39
38
|
this.audioChunk = this.audioChunk.bind(this);
|
|
40
39
|
this.close = this.close.bind(this);
|
|
41
40
|
}
|
|
@@ -51,14 +50,6 @@ var PhonicSTSWebSocket = class {
|
|
|
51
50
|
onError(callback) {
|
|
52
51
|
this.onErrorCallback = callback;
|
|
53
52
|
}
|
|
54
|
-
config(message) {
|
|
55
|
-
this.ws.send(
|
|
56
|
-
JSON.stringify({
|
|
57
|
-
type: "config",
|
|
58
|
-
...message
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
53
|
audioChunk(message) {
|
|
63
54
|
this.ws.send(
|
|
64
55
|
JSON.stringify({
|
|
@@ -73,21 +64,43 @@ var PhonicSTSWebSocket = class {
|
|
|
73
64
|
};
|
|
74
65
|
|
|
75
66
|
// src/sts/index.ts
|
|
67
|
+
var phonicApiCloseCodes = {
|
|
68
|
+
insuffucientCapacityAvailable: 4004
|
|
69
|
+
};
|
|
76
70
|
var SpeechToSpeech = class {
|
|
77
71
|
constructor(phonic) {
|
|
78
72
|
this.phonic = phonic;
|
|
79
73
|
}
|
|
80
|
-
async
|
|
74
|
+
async connectToPhonicAPI(phonicApiWsUrl, config) {
|
|
81
75
|
return new Promise((resolve) => {
|
|
82
|
-
const
|
|
83
|
-
const ws = new WebSocket(`${wsBaseUrl}/v1/sts/ws`, {
|
|
76
|
+
const ws = new WebSocket(phonicApiWsUrl, {
|
|
84
77
|
headers: {
|
|
85
78
|
Authorization: `Bearer ${this.phonic.apiKey}`
|
|
86
79
|
}
|
|
87
80
|
});
|
|
88
81
|
ws.onopen = () => {
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
ws.send(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
type: "config",
|
|
85
|
+
...config
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
ws.onmessage = (event) => {
|
|
90
|
+
if (typeof event.data !== "string") {
|
|
91
|
+
throw new Error("Received non-string message");
|
|
92
|
+
}
|
|
93
|
+
const dataObj = JSON.parse(
|
|
94
|
+
event.data
|
|
95
|
+
);
|
|
96
|
+
if (dataObj.type === "ready_to_start_conversation") {
|
|
97
|
+
resolve({
|
|
98
|
+
data: {
|
|
99
|
+
phonicWebSocket: new PhonicSTSWebSocket(ws)
|
|
100
|
+
},
|
|
101
|
+
error: null
|
|
102
|
+
});
|
|
103
|
+
}
|
|
91
104
|
};
|
|
92
105
|
ws.onerror = (error) => {
|
|
93
106
|
resolve({
|
|
@@ -97,8 +110,48 @@ var SpeechToSpeech = class {
|
|
|
97
110
|
}
|
|
98
111
|
});
|
|
99
112
|
};
|
|
113
|
+
ws.onclose = (event) => {
|
|
114
|
+
if (event.code === phonicApiCloseCodes.insuffucientCapacityAvailable) {
|
|
115
|
+
resolve({
|
|
116
|
+
data: null,
|
|
117
|
+
error: {
|
|
118
|
+
message: event.reason,
|
|
119
|
+
code: "insuffucient_capacity_available"
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
};
|
|
100
124
|
});
|
|
101
125
|
}
|
|
126
|
+
async websocket(config) {
|
|
127
|
+
const wsBaseUrl = this.phonic.baseUrl.replace(/^http/, "ws");
|
|
128
|
+
const phonicApiWsUrl = `${wsBaseUrl}/v1/sts/ws`;
|
|
129
|
+
let retryNumber = 0;
|
|
130
|
+
const maxRetries = 14;
|
|
131
|
+
const retryDelay = 15e3;
|
|
132
|
+
while (true) {
|
|
133
|
+
const connectResult = await this.connectToPhonicAPI(
|
|
134
|
+
phonicApiWsUrl,
|
|
135
|
+
config
|
|
136
|
+
);
|
|
137
|
+
if (connectResult.data !== null) {
|
|
138
|
+
return connectResult;
|
|
139
|
+
}
|
|
140
|
+
if (connectResult.error.code === "insuffucient_capacity_available") {
|
|
141
|
+
if (retryNumber >= maxRetries) {
|
|
142
|
+
return connectResult;
|
|
143
|
+
}
|
|
144
|
+
console.info(
|
|
145
|
+
`${connectResult.error.message}, will retry in ${retryDelay / 1e3}sec`
|
|
146
|
+
);
|
|
147
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
148
|
+
retryNumber += 1;
|
|
149
|
+
console.info(`Retrying... ${retryNumber}/${maxRetries}`);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
return connectResult;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
102
155
|
};
|
|
103
156
|
|
|
104
157
|
// src/voices/index.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phonic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Phonic Node.js SDK",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "tsup",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"@biomejs/biome": "1.9.4",
|
|
40
40
|
"@changesets/changelog-github": "0.5.1",
|
|
41
41
|
"@changesets/cli": "2.28.1",
|
|
42
|
-
"@types/bun": "1.2.
|
|
43
|
-
"tsup": "8.
|
|
42
|
+
"@types/bun": "1.2.4",
|
|
43
|
+
"tsup": "8.4.0",
|
|
44
44
|
"typescript": "5.7.3",
|
|
45
45
|
"zod": "3.24.2"
|
|
46
46
|
},
|