ws.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import * as WS from 'ws';
  2. import { OpenAI } from "../index.mjs";
  3. import { OpenAIRealtimeEmitter, buildRealtimeURL, isAzure } from "./internal-base.mjs";
  4. export class OpenAIRealtimeWS extends OpenAIRealtimeEmitter {
  5. constructor(props, client) {
  6. super();
  7. client ?? (client = new OpenAI());
  8. const hasProvider = typeof client?._options?.apiKey === 'function';
  9. if (hasProvider && !props.__resolvedApiKey) {
  10. throw new Error([
  11. 'Cannot open Realtime WebSocket with a function-based apiKey.',
  12. 'Use the .create() method so that the key is resolved before connecting:',
  13. 'await OpenAIRealtimeWS.create(client, { model })',
  14. ].join('\n'));
  15. }
  16. this.url = buildRealtimeURL(client, props.model);
  17. this.socket = new WS.WebSocket(this.url, {
  18. ...props.options,
  19. headers: {
  20. ...props.options?.headers,
  21. ...(isAzure(client) && !props.__resolvedApiKey ? {} : { Authorization: `Bearer ${client.apiKey}` }),
  22. },
  23. });
  24. this.socket.on('message', (wsEvent) => {
  25. const event = (() => {
  26. try {
  27. return JSON.parse(wsEvent.toString());
  28. }
  29. catch (err) {
  30. this._onError(null, 'could not parse websocket event', err);
  31. return null;
  32. }
  33. })();
  34. if (event) {
  35. this._emit('event', event);
  36. if (event.type === 'error') {
  37. this._onError(event);
  38. }
  39. else {
  40. // @ts-expect-error TS isn't smart enough to get the relationship right here
  41. this._emit(event.type, event);
  42. }
  43. }
  44. });
  45. this.socket.on('error', (err) => {
  46. this._onError(null, err.message, err);
  47. });
  48. }
  49. static async create(client, props) {
  50. return new OpenAIRealtimeWS({ ...props, __resolvedApiKey: await client._callApiKey() }, client);
  51. }
  52. static async azure(client, props = {}) {
  53. const isApiKeyProvider = await client._callApiKey();
  54. const deploymentName = props.deploymentName ?? client.deploymentName;
  55. if (!deploymentName) {
  56. throw new Error('No deployment name provided');
  57. }
  58. return new OpenAIRealtimeWS({
  59. model: deploymentName,
  60. options: {
  61. ...props.options,
  62. headers: {
  63. ...props.options?.headers,
  64. ...(isApiKeyProvider ? {} : { 'api-key': client.apiKey }),
  65. },
  66. },
  67. __resolvedApiKey: isApiKeyProvider,
  68. }, client);
  69. }
  70. send(event) {
  71. try {
  72. this.socket.send(JSON.stringify(event));
  73. }
  74. catch (err) {
  75. this._onError(null, 'could not send data', err);
  76. }
  77. }
  78. close(props) {
  79. try {
  80. this.socket.close(props?.code ?? 1000, props?.reason ?? 'OK');
  81. }
  82. catch (err) {
  83. this._onError(null, 'could not close the connection', err);
  84. }
  85. }
  86. }
  87. //# sourceMappingURL=ws.mjs.map