phonic 0.3.0 → 0.5.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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024 Phonic, Inc.
1
+ Copyright (c) 2025 Phonic, Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
4
 
package/README.md CHANGED
@@ -8,6 +8,7 @@ Node.js library for the Phonic API.
8
8
  - [Get voices](#get-voices)
9
9
  - [Get voice by id](#get-voice-by-id)
10
10
  - [Text-to-speech via WebSocket](#text-to-speech-via-websocket)
11
+ - [Speech-to-speech via WebSocket](#speech-to-speech-via-websocket)
11
12
 
12
13
  ## Installation
13
14
 
@@ -19,7 +20,7 @@ npm i phonic
19
20
 
20
21
  Grab an API key from [Phonic settings](https://phonic.co/settings) and pass it to the Phonic constructor.
21
22
 
22
- ```js
23
+ ```ts
23
24
  import { Phonic } from "phonic";
24
25
 
25
26
  const phonic = new Phonic("ph_...");
@@ -29,8 +30,8 @@ const phonic = new Phonic("ph_...");
29
30
 
30
31
  ### Get voices
31
32
 
32
- ```js
33
- const { data, error } = await phonic.voices.list();
33
+ ```ts
34
+ const { data, error } = await phonic.voices.list({ model: "shasta" });
34
35
 
35
36
  if (error === null) {
36
37
  console.log(data.voices);
@@ -40,23 +41,107 @@ if (error === null) {
40
41
 
41
42
  ### Get voice by id
42
43
 
43
- ```js
44
- const { data, error } = await phonic.voices.get("australian-man");
44
+ ```ts
45
+ const { data, error } = await phonic.voices.get("meredith");
45
46
 
46
47
  if (error === null) {
47
48
  console.log(data.voice);
48
49
  }
49
50
  ```
50
51
 
52
+ ### Speesh-to-speech via WebSocket
53
+
54
+ Open a WebSocket connection:
55
+
56
+ ```ts
57
+ const { data, error } = await phonic.sts.websocket();
58
+
59
+ if (error !== null) {
60
+ throw new Error(error.message);
61
+ }
62
+
63
+ // Here we know that the WebSocket connection is open.
64
+ const { phonicWebSocket } = data;
65
+ ```
66
+
67
+ Send config params for the conversation:
68
+
69
+ ```ts
70
+ phonicWebSocket.config({
71
+ input_format: "mulaw_8000",
72
+
73
+ // Optional fields
74
+ system_prompt: "You are a helpful assistant.",
75
+ welcome_message: "Hello, how can I help you?",
76
+ voice_id: "meredith",
77
+ output_format: "mulaw_8000"
78
+ });
79
+ ```
80
+
81
+ Stream input (user) audio chunks:
82
+
83
+ ```ts
84
+ phonicWebSocket.audioChunk({
85
+ audio: "...", // base64 encoded audio chunk
86
+ });
87
+ ```
88
+
89
+ Process messages that Phonic sends back to you:
90
+
91
+ ```ts
92
+ phonicWebSocket.onMessage((message) => {
93
+ switch (message.type) {
94
+ case "input_text": {
95
+ console.log(`User: ${message.text}`);
96
+ break;
97
+ }
98
+
99
+ case "audio_chunk": {
100
+ // Send the audio chunk to Twilio, for example:
101
+ ws.send(
102
+ JSON.stringify({
103
+ event: "media",
104
+ streamSid: "...",
105
+ media: {
106
+ payload: message.audio,
107
+ },
108
+ }),
109
+ );
110
+ break;
111
+ }
112
+ }
113
+ });
114
+ ```
115
+
116
+ To end the conversation, close the WebSocket:
117
+
118
+ ```ts
119
+ phonicWebSocket.close();
120
+ ```
121
+
122
+ You can also listen for close and error events:
123
+
124
+ ```ts
125
+ phonicWebSocket.onClose((event) => {
126
+ console.log(
127
+ `Phonic WebSocket closed with code ${event.code} and reason "${event.reason}"`,
128
+ );
129
+ });
130
+
131
+ phonicWebSocket.onError((event) => {
132
+ console.log(`Error from Phonic WebSocket: ${event.message}`);
133
+ });
134
+ ```
135
+
51
136
  ### Text-to-speech via WebSocket
52
137
 
53
138
  Open a WebSocket connection:
54
139
 
55
- ```js
140
+ ```ts
56
141
  const { data, error } = await phonic.tts.websocket({
57
142
  model: "shasta",
58
143
  output_format: "mulaw_8000",
59
- voice_id: "australian-man",
144
+ voice_id: "meredith",
60
145
  });
61
146
 
62
147
  if (error !== null) {
@@ -69,7 +154,7 @@ const { phonicWebSocket } = data;
69
154
 
70
155
  Process audio chunks that Phonic sends back to you, by sending them to Twilio, for example:
71
156
 
72
- ```js
157
+ ```ts
73
158
  phonicWebSocket.onMessage((message) => {
74
159
  if (message.type === "audio_chunk") {
75
160
  ws.send(
@@ -87,7 +172,7 @@ phonicWebSocket.onMessage((message) => {
87
172
 
88
173
  Send text chunks to Phonic for audio generation as you receive them from LLM:
89
174
 
90
- ```js
175
+ ```ts
91
176
  const stream = await openai.chat.completions.create(...);
92
177
 
93
178
  for await (const chunk of stream) {
@@ -101,25 +186,25 @@ for await (const chunk of stream) {
101
186
 
102
187
  Tell Phonic to finish generating audio for all text chunks you've sent:
103
188
 
104
- ```js
189
+ ```ts
105
190
  phonicWebSocket.flush();
106
191
  ```
107
192
 
108
193
  You can also tell Phonic to stop sending audio chunks back, e.g. if the user interrupts the conversation:
109
194
 
110
- ```js
195
+ ```ts
111
196
  phonicWebSocket.stop();
112
197
  ```
113
198
 
114
199
  To close the WebSocket connection:
115
200
 
116
- ```js
201
+ ```ts
117
202
  phonicWebSocket.close();
118
203
  ```
119
204
 
120
205
  To know when the last audio chunk has been received:
121
206
 
122
- ```js
207
+ ```ts
123
208
  phonicWebSocket.onMessage((message) => {
124
209
  if (message.type === "flushed") {
125
210
  console.log("Last audio chunk received");
@@ -129,7 +214,7 @@ phonicWebSocket.onMessage((message) => {
129
214
 
130
215
  You can also listen for close and error events:
131
216
 
132
- ```js
217
+ ```ts
133
218
  phonicWebSocket.onClose((event) => {
134
219
  console.log(
135
220
  `Phonic WebSocket closed with code ${event.code} and reason "${event.reason}"`,
@@ -141,15 +226,15 @@ phonicWebSocket.onError((event) => {
141
226
  });
142
227
  ```
143
228
 
144
- ## Release a new version to npm
229
+ ## Publish a new version on npm
145
230
 
146
231
  1. `bunx changeset`
147
232
  2. `git add .`
148
233
  3. `git commit -m "Add changeset"`
149
234
  4. `git push`
150
235
 
151
- Git action will run and create a PR.
152
- Once this PR is merged, the new version will be released to npm.
236
+ This should trigger the `publish` github workflow that will create a Pull Request named "Version Packages".
237
+ Once this Pull Request is merged, the new version will be published on npm.
153
238
 
154
239
  ## License
155
240
 
package/dist/index.d.mts CHANGED
@@ -18,12 +18,12 @@ type DataOrError<T> = Promise<{
18
18
  error: ErrorResponse;
19
19
  }>;
20
20
 
21
- type PhonicWebSocketParams = {
21
+ type PhonicTTSWebSocketParams = {
22
22
  model?: string;
23
23
  output_format?: string;
24
24
  voice_id?: string;
25
25
  };
26
- type PhonicWebSocketResponseMessage = {
26
+ type PhonicTTSWebSocketResponseMessage = {
27
27
  type: "config";
28
28
  model: string;
29
29
  output_format: string;
@@ -50,19 +50,19 @@ type PhonicWebSocketResponseMessage = {
50
50
  speed?: string;
51
51
  };
52
52
  };
53
- type OnMessageCallback = (message: PhonicWebSocketResponseMessage) => void;
54
- type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
55
- type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
53
+ type OnMessageCallback$1 = (message: PhonicTTSWebSocketResponseMessage) => void;
54
+ type OnCloseCallback$1 = (event: WebSocket.CloseEvent) => void;
55
+ type OnErrorCallback$1 = (event: WebSocket.ErrorEvent) => void;
56
56
 
57
- declare class PhonicWebSocket {
57
+ declare class PhonicTTSWebSocket {
58
58
  private readonly ws;
59
59
  private onMessageCallback;
60
60
  private onCloseCallback;
61
61
  private onErrorCallback;
62
62
  constructor(ws: WebSocket);
63
- onMessage(callback: OnMessageCallback): void;
64
- onClose(callback: OnCloseCallback): void;
65
- onError(callback: OnErrorCallback): void;
63
+ onMessage(callback: OnMessageCallback$1): void;
64
+ onClose(callback: OnCloseCallback$1): void;
65
+ onError(callback: OnErrorCallback$1): void;
66
66
  generate(message: {
67
67
  text: string;
68
68
  speed?: number;
@@ -75,8 +75,8 @@ declare class PhonicWebSocket {
75
75
  declare class TextToSpeech {
76
76
  private readonly phonic;
77
77
  constructor(phonic: Phonic);
78
- websocket(params?: PhonicWebSocketParams): DataOrError<{
79
- phonicWebSocket: PhonicWebSocket;
78
+ websocket(params?: PhonicTTSWebSocketParams): DataOrError<{
79
+ phonicWebSocket: PhonicTTSWebSocket;
80
80
  }>;
81
81
  }
82
82
 
@@ -94,7 +94,9 @@ type VoiceSuccessResponse = {
94
94
  declare class Voices {
95
95
  private readonly phonic;
96
96
  constructor(phonic: Phonic);
97
- list(): DataOrError<VoicesSuccessResponse>;
97
+ list({ model }: {
98
+ model: string;
99
+ }): DataOrError<VoicesSuccessResponse>;
98
100
  get(id: string): DataOrError<VoiceSuccessResponse>;
99
101
  }
100
102
 
@@ -115,4 +117,51 @@ declare class Phonic {
115
117
  }>;
116
118
  }
117
119
 
118
- export { Phonic, PhonicWebSocket };
120
+ type PhonicSTSWebSocketResponseMessage = {
121
+ type: "input_text";
122
+ text: string;
123
+ } | {
124
+ type: "audio_chunk";
125
+ text: string;
126
+ audio: string;
127
+ } | {
128
+ type: "error";
129
+ error: {
130
+ message: string;
131
+ code?: string;
132
+ };
133
+ paramErrors?: {
134
+ system_prompt?: string;
135
+ welcome_message?: string;
136
+ voice_id?: string;
137
+ input_format?: string;
138
+ output_format?: string;
139
+ };
140
+ };
141
+ type OnMessageCallback = (message: PhonicSTSWebSocketResponseMessage) => void;
142
+ type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
143
+ type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
144
+
145
+ declare class PhonicSTSWebSocket {
146
+ private readonly ws;
147
+ private onMessageCallback;
148
+ private onCloseCallback;
149
+ private onErrorCallback;
150
+ constructor(ws: WebSocket);
151
+ onMessage(callback: OnMessageCallback): void;
152
+ onClose(callback: OnCloseCallback): void;
153
+ onError(callback: OnErrorCallback): void;
154
+ config(message: {
155
+ system_prompt?: string;
156
+ welcome_message?: string;
157
+ voice_id?: string;
158
+ input_format?: "pcm_44100" | "mulaw_8000";
159
+ output_format?: "pcm_44100" | "mulaw_8000";
160
+ }): void;
161
+ audioChunk(message: {
162
+ audio: string;
163
+ }): void;
164
+ close(): void;
165
+ }
166
+
167
+ export { Phonic, PhonicSTSWebSocket, PhonicTTSWebSocket };
package/dist/index.d.ts CHANGED
@@ -18,12 +18,12 @@ type DataOrError<T> = Promise<{
18
18
  error: ErrorResponse;
19
19
  }>;
20
20
 
21
- type PhonicWebSocketParams = {
21
+ type PhonicTTSWebSocketParams = {
22
22
  model?: string;
23
23
  output_format?: string;
24
24
  voice_id?: string;
25
25
  };
26
- type PhonicWebSocketResponseMessage = {
26
+ type PhonicTTSWebSocketResponseMessage = {
27
27
  type: "config";
28
28
  model: string;
29
29
  output_format: string;
@@ -50,19 +50,19 @@ type PhonicWebSocketResponseMessage = {
50
50
  speed?: string;
51
51
  };
52
52
  };
53
- type OnMessageCallback = (message: PhonicWebSocketResponseMessage) => void;
54
- type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
55
- type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
53
+ type OnMessageCallback$1 = (message: PhonicTTSWebSocketResponseMessage) => void;
54
+ type OnCloseCallback$1 = (event: WebSocket.CloseEvent) => void;
55
+ type OnErrorCallback$1 = (event: WebSocket.ErrorEvent) => void;
56
56
 
57
- declare class PhonicWebSocket {
57
+ declare class PhonicTTSWebSocket {
58
58
  private readonly ws;
59
59
  private onMessageCallback;
60
60
  private onCloseCallback;
61
61
  private onErrorCallback;
62
62
  constructor(ws: WebSocket);
63
- onMessage(callback: OnMessageCallback): void;
64
- onClose(callback: OnCloseCallback): void;
65
- onError(callback: OnErrorCallback): void;
63
+ onMessage(callback: OnMessageCallback$1): void;
64
+ onClose(callback: OnCloseCallback$1): void;
65
+ onError(callback: OnErrorCallback$1): void;
66
66
  generate(message: {
67
67
  text: string;
68
68
  speed?: number;
@@ -75,8 +75,8 @@ declare class PhonicWebSocket {
75
75
  declare class TextToSpeech {
76
76
  private readonly phonic;
77
77
  constructor(phonic: Phonic);
78
- websocket(params?: PhonicWebSocketParams): DataOrError<{
79
- phonicWebSocket: PhonicWebSocket;
78
+ websocket(params?: PhonicTTSWebSocketParams): DataOrError<{
79
+ phonicWebSocket: PhonicTTSWebSocket;
80
80
  }>;
81
81
  }
82
82
 
@@ -94,7 +94,9 @@ type VoiceSuccessResponse = {
94
94
  declare class Voices {
95
95
  private readonly phonic;
96
96
  constructor(phonic: Phonic);
97
- list(): DataOrError<VoicesSuccessResponse>;
97
+ list({ model }: {
98
+ model: string;
99
+ }): DataOrError<VoicesSuccessResponse>;
98
100
  get(id: string): DataOrError<VoiceSuccessResponse>;
99
101
  }
100
102
 
@@ -115,4 +117,51 @@ declare class Phonic {
115
117
  }>;
116
118
  }
117
119
 
118
- export { Phonic, PhonicWebSocket };
120
+ type PhonicSTSWebSocketResponseMessage = {
121
+ type: "input_text";
122
+ text: string;
123
+ } | {
124
+ type: "audio_chunk";
125
+ text: string;
126
+ audio: string;
127
+ } | {
128
+ type: "error";
129
+ error: {
130
+ message: string;
131
+ code?: string;
132
+ };
133
+ paramErrors?: {
134
+ system_prompt?: string;
135
+ welcome_message?: string;
136
+ voice_id?: string;
137
+ input_format?: string;
138
+ output_format?: string;
139
+ };
140
+ };
141
+ type OnMessageCallback = (message: PhonicSTSWebSocketResponseMessage) => void;
142
+ type OnCloseCallback = (event: WebSocket.CloseEvent) => void;
143
+ type OnErrorCallback = (event: WebSocket.ErrorEvent) => void;
144
+
145
+ declare class PhonicSTSWebSocket {
146
+ private readonly ws;
147
+ private onMessageCallback;
148
+ private onCloseCallback;
149
+ private onErrorCallback;
150
+ constructor(ws: WebSocket);
151
+ onMessage(callback: OnMessageCallback): void;
152
+ onClose(callback: OnCloseCallback): void;
153
+ onError(callback: OnErrorCallback): void;
154
+ config(message: {
155
+ system_prompt?: string;
156
+ welcome_message?: string;
157
+ voice_id?: string;
158
+ input_format?: "pcm_44100" | "mulaw_8000";
159
+ output_format?: "pcm_44100" | "mulaw_8000";
160
+ }): void;
161
+ audioChunk(message: {
162
+ audio: string;
163
+ }): void;
164
+ close(): void;
165
+ }
166
+
167
+ export { Phonic, PhonicSTSWebSocket, PhonicTTSWebSocket };
package/dist/index.js CHANGED
@@ -28,20 +28,20 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
29
 
30
30
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
33
  Phonic: () => Phonic
34
34
  });
35
- module.exports = __toCommonJS(src_exports);
35
+ module.exports = __toCommonJS(index_exports);
36
36
 
37
37
  // package.json
38
- var version = "0.3.0";
38
+ var version = "0.5.0";
39
39
 
40
40
  // src/tts/index.ts
41
41
  var import_ws = __toESM(require("ws"));
42
42
 
43
43
  // src/tts/websocket.ts
44
- var PhonicWebSocket = class {
44
+ var PhonicTTSWebSocket = class {
45
45
  constructor(ws) {
46
46
  this.ws = ws;
47
47
  this.ws.onmessage = (event) => {
@@ -51,7 +51,9 @@ var PhonicWebSocket = class {
51
51
  if (typeof event.data !== "string") {
52
52
  throw new Error("Received non-string message");
53
53
  }
54
- const dataObj = JSON.parse(event.data);
54
+ const dataObj = JSON.parse(
55
+ event.data
56
+ );
55
57
  this.onMessageCallback(dataObj);
56
58
  };
57
59
  this.ws.onclose = (event) => {
@@ -67,6 +69,8 @@ var PhonicWebSocket = class {
67
69
  this.onErrorCallback(event);
68
70
  };
69
71
  this.onMessage = this.onMessage.bind(this);
72
+ this.onClose = this.onClose.bind(this);
73
+ this.onError = this.onError.bind(this);
70
74
  this.generate = this.generate.bind(this);
71
75
  this.flush = this.flush.bind(this);
72
76
  this.stop = this.stop.bind(this);
@@ -118,7 +122,7 @@ var TextToSpeech = class {
118
122
  }
119
123
  });
120
124
  ws.onopen = () => {
121
- const phonicWebSocket = new PhonicWebSocket(ws);
125
+ const phonicWebSocket = new PhonicTTSWebSocket(ws);
122
126
  resolve({ data: { phonicWebSocket }, error: null });
123
127
  };
124
128
  ws.onerror = (error) => {
@@ -138,8 +142,10 @@ var Voices = class {
138
142
  constructor(phonic) {
139
143
  this.phonic = phonic;
140
144
  }
141
- async list() {
142
- const response = await this.phonic.get("/voices");
145
+ async list({ model }) {
146
+ const response = await this.phonic.get(
147
+ `/voices?model=${encodeURIComponent(model)}`
148
+ );
143
149
  return response;
144
150
  }
145
151
  async get(id) {
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  // package.json
2
- var version = "0.3.0";
2
+ var version = "0.5.0";
3
3
 
4
4
  // src/tts/index.ts
5
5
  import WebSocket from "ws";
6
6
 
7
7
  // src/tts/websocket.ts
8
- var PhonicWebSocket = class {
8
+ var PhonicTTSWebSocket = class {
9
9
  constructor(ws) {
10
10
  this.ws = ws;
11
11
  this.ws.onmessage = (event) => {
@@ -15,7 +15,9 @@ var PhonicWebSocket = class {
15
15
  if (typeof event.data !== "string") {
16
16
  throw new Error("Received non-string message");
17
17
  }
18
- const dataObj = JSON.parse(event.data);
18
+ const dataObj = JSON.parse(
19
+ event.data
20
+ );
19
21
  this.onMessageCallback(dataObj);
20
22
  };
21
23
  this.ws.onclose = (event) => {
@@ -31,6 +33,8 @@ var PhonicWebSocket = class {
31
33
  this.onErrorCallback(event);
32
34
  };
33
35
  this.onMessage = this.onMessage.bind(this);
36
+ this.onClose = this.onClose.bind(this);
37
+ this.onError = this.onError.bind(this);
34
38
  this.generate = this.generate.bind(this);
35
39
  this.flush = this.flush.bind(this);
36
40
  this.stop = this.stop.bind(this);
@@ -82,7 +86,7 @@ var TextToSpeech = class {
82
86
  }
83
87
  });
84
88
  ws.onopen = () => {
85
- const phonicWebSocket = new PhonicWebSocket(ws);
89
+ const phonicWebSocket = new PhonicTTSWebSocket(ws);
86
90
  resolve({ data: { phonicWebSocket }, error: null });
87
91
  };
88
92
  ws.onerror = (error) => {
@@ -102,8 +106,10 @@ var Voices = class {
102
106
  constructor(phonic) {
103
107
  this.phonic = phonic;
104
108
  }
105
- async list() {
106
- const response = await this.phonic.get("/voices");
109
+ async list({ model }) {
110
+ const response = await this.phonic.get(
111
+ `/voices?model=${encodeURIComponent(model)}`
112
+ );
107
113
  return response;
108
114
  }
109
115
  async get(id) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phonic",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Phonic Node.js SDK",
5
5
  "scripts": {
6
6
  "build": "tsup",
@@ -33,16 +33,16 @@
33
33
  "url": "https://github.com/Phonic-Co/phonic-node/issues"
34
34
  },
35
35
  "dependencies": {
36
- "ws": "8.18.0"
36
+ "ws": "8.18.1"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "1.9.4",
40
- "@changesets/changelog-github": "0.5.0",
41
- "@changesets/cli": "2.27.11",
42
- "@types/bun": "1.1.14",
43
- "tsup": "8.3.5",
44
- "typescript": "5.7.2",
45
- "zod": "3.24.1"
40
+ "@changesets/changelog-github": "0.5.1",
41
+ "@changesets/cli": "2.28.1",
42
+ "@types/bun": "1.2.3",
43
+ "tsup": "8.3.6",
44
+ "typescript": "5.7.3",
45
+ "zod": "3.24.2"
46
46
  },
47
47
  "files": ["dist/**"],
48
48
  "author": {