{"_id":"@aurora-nuanu/realtime-api","_rev":"6-93aac1dee003210d89b934bce83baf9f","name":"@aurora-nuanu/realtime-api","dist-tags":{"latest":"0.0.5"},"versions":{"0.0.0":{"name":"@aurora-nuanu/realtime-api","version":"0.0.0","author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","_id":"@aurora-nuanu/realtime-api@0.0.0","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"dist":{"shasum":"b6114a8207ba62ed09a9637547064d1924bf13d3","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.0.tgz","fileCount":25,"integrity":"sha512-gOJUCsFAwXvdGArQjCDB4eGg9kt3JG3s4muwXx1DmGFccILrJA7upDFGFqRQTXQdwophcaAp5hBmttfPWjfo2Q==","signatures":[{"sig":"MEQCIFbvWBd+aic4ecvYB7c454taDbCqXOnMPMdxq5w/vX5PAiBDGQPaQWtKWDahbcCZXib3Rb4Lwhe3PZ57uu9BJT2lrw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":114003},"main":"index.js","type":"module","types":"./dist/index.d.ts","gitHead":"a88e637c7dd85d0aa65111d0c76b7eecf447c342","private":false,"scripts":{"test":"mocha ./test/index.js"},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"_npmVersion":"9.8.1","description":"Nuanu's Realtime API Reference Client (beta)","directories":{},"_nodeVersion":"18.18.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.1","mocha":"^10.7.3","dotenv":"^16.4.5","prettier":"^3.3.3","websocket":"^1.0.35","typescript":"^5.6.2","audio-decode":"^2.2.2"},"_npmOperationalInternal":{"tmp":"tmp/realtime-api_0.0.0_1742891339387_0.6904900038418864","host":"s3://npm-registry-packages-npm-production"}},"0.0.1":{"name":"@aurora-nuanu/realtime-api","version":"0.0.1","author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","_id":"@aurora-nuanu/realtime-api@0.0.1","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"dist":{"shasum":"f2bdcdd517019046295f8c27de9f51579242564a","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.1.tgz","fileCount":25,"integrity":"sha512-IYW/mrZfTFaAWP6HHQRb73ZrG3kh1B/jcfwd6p+p+fFnUeskdtkg+pqQBFdA0dODOgjW4iop45wr7BNkQXFOyg==","signatures":[{"sig":"MEQCIFaaQ87uCGiMjgVM+peCQ8sjw0uf38M6FX2JTmV4ZES/AiBfTAuMCmFPkSw3JpA7LxOzDkrTWeraNuEKUTHWbTblKQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138680},"main":"index.js","type":"module","types":"./dist/index.d.ts","gitHead":"83aaaaddd779fb505fa7f13069bb11623d9d584b","private":false,"scripts":{"test":"mocha ./test/index.js"},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"_npmVersion":"9.8.1","description":"Nuanu's Realtime API Reference Client (beta)","directories":{},"_nodeVersion":"18.18.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.1","mocha":"^10.7.3","dotenv":"^16.4.5","prettier":"^3.3.3","websocket":"^1.0.35","typescript":"^5.6.2","audio-decode":"^2.2.2"},"_npmOperationalInternal":{"tmp":"tmp/realtime-api_0.0.1_1742892480608_0.3908654270274543","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@aurora-nuanu/realtime-api","version":"0.0.2","author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","_id":"@aurora-nuanu/realtime-api@0.0.2","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"dist":{"shasum":"eb554f9d735462f726a7cf58a6a8ee1c068d66b2","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.2.tgz","fileCount":25,"integrity":"sha512-1F8cplb7adACJkvhMXiICUeHLX8LhdeUmeKEGzay2E6ZTzQBdFnRB04p626M9LUy44yqrWClDwmUfJndtsL0Mg==","signatures":[{"sig":"MEUCICG39/Flw0xc//QDszjBdeEX/bZtK04cq1e338sXEn+jAiEAj002RPZ5PHvRCOY3tPe0k2uwa9bLJf99CUGkB+XYduA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":114025},"main":"index.js","type":"module","types":"./dist/index.d.ts","gitHead":"7443b15261eb91332c88d3f743115f617b08d25c","private":false,"scripts":{"test":"mocha ./test/index.js"},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"_npmVersion":"9.8.1","description":"Nuanu's Realtime API Reference Client (beta)","directories":{},"_nodeVersion":"18.18.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.1","mocha":"^10.7.3","dotenv":"^16.4.5","prettier":"^3.3.3","websocket":"^1.0.35","typescript":"^5.6.2","audio-decode":"^2.2.2"},"_npmOperationalInternal":{"tmp":"tmp/realtime-api_0.0.2_1742893653944_0.7236739471623099","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@aurora-nuanu/realtime-api","version":"0.0.3","author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","_id":"@aurora-nuanu/realtime-api@0.0.3","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"dist":{"shasum":"124f2d3b9008b15a143783d72c01076cbde85fd5","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.3.tgz","fileCount":25,"integrity":"sha512-u95Uz+rH0BbGrLwgmfcHIxro3WQ4JPITai5hhumnmDDUe6jksDNN1ogOuSkI5qM0ZFLMJZPBM7TKbRQSp5dr1w==","signatures":[{"sig":"MEUCIQCasxsUkwwQR4J7H0+wl3CyGuAHTQwe2rR9V5p4aZQ1GgIgSzggf9oaU/5qEBUOfrwaa5bdggZ+jqqK9oF49M0tFOA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138678},"main":"index.js","type":"module","types":"./dist/index.d.ts","gitHead":"f6df1b46613a656c753550d6653fb5ee475281bb","private":false,"scripts":{"test":"mocha ./test/index.js"},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"_npmVersion":"9.8.1","description":"Nuanu's Realtime API Reference Client (beta)","directories":{},"_nodeVersion":"18.18.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.1","mocha":"^10.7.3","dotenv":"^16.4.5","prettier":"^3.3.3","websocket":"^1.0.35","typescript":"^5.6.2","audio-decode":"^2.2.2"},"_npmOperationalInternal":{"tmp":"tmp/realtime-api_0.0.3_1742893805421_0.14468408928972587","host":"s3://npm-registry-packages-npm-production"}},"0.0.4":{"name":"@aurora-nuanu/realtime-api","version":"0.0.4","author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","_id":"@aurora-nuanu/realtime-api@0.0.4","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"dist":{"shasum":"51a68a7399d2da3ac20ba8126797191d6b44cbce","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.4.tgz","fileCount":25,"integrity":"sha512-nJTJSbe7mnaPjley4LSzQy2Jb2OJSpvUltyQG7YMPonAT4NRGA4ucP/zDMDV/GTObNf/FCsxkLL8sz4pa15SyQ==","signatures":[{"sig":"MEUCIQDJLf58bvWhbRJlXOeDCP1ixEkZHr/ZhWFssFTbaqxRXAIgH7Y6wuzPKultIQ40SnSgCBc4v7mmuCe38bPcgaAIN/s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":114796},"main":"index.js","type":"module","types":"./dist/index.d.ts","gitHead":"c69bb8b2804275a6c80dfa618acf81c8574033c0","private":false,"scripts":{"test":"mocha ./test/index.js"},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"_npmVersion":"9.8.1","description":"Nuanu's Realtime API Reference Client (beta)","directories":{},"_nodeVersion":"18.18.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"chai":"^5.1.1","mocha":"^10.7.3","dotenv":"^16.4.5","prettier":"^3.3.3","websocket":"^1.0.35","typescript":"^5.6.2","audio-decode":"^2.2.2"},"_npmOperationalInternal":{"tmp":"tmp/realtime-api_0.0.4_1742894027591_0.983716521625515","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@aurora-nuanu/realtime-api","version":"0.0.5","private":false,"type":"module","description":"Nuanu's Realtime API Reference Client (beta)","main":"index.js","types":"./dist/index.d.ts","scripts":{"test":"mocha ./test/index.js"},"author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","devDependencies":{"audio-decode":"^2.2.2","chai":"^5.1.1","dotenv":"^16.4.5","mocha":"^10.7.3","prettier":"^3.3.3","typescript":"^5.6.2","websocket":"^1.0.35"},"dependencies":{"ws":"^8.18.0"},"_id":"@aurora-nuanu/realtime-api@0.0.5","gitHead":"51dc858c303913a31f062fcb61f9a031459b29ee","_nodeVersion":"18.18.2","_npmVersion":"9.8.1","dist":{"integrity":"sha512-YkZ6n/lWjaX41Kb4NK4gbZoEwhjzd+JesmueJoDghujf6A5jdaPdL3uBOsKGsusFvgLRznqTNRNDwkXK7ZWr1A==","shasum":"20762926528bed98f028bd45e8f97a56423e99f7","tarball":"https://registry.npmjs.org/@aurora-nuanu/realtime-api/-/realtime-api-0.0.5.tgz","fileCount":25,"unpackedSize":114700,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCUcqABgYpfNLI28CehcMU1bRzVjdHS6dIIOwqWeZ93FgIhAMY5E7D/C/tObxytln8PDGAydgWG1PxsoEYZb/Wj48q9"}]},"_npmUser":{"name":"aivanouski","email":"aivanouski@gmail.com"},"directories":{},"maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/realtime-api_0.0.5_1742901262813_0.9094751890382908"},"_hasShrinkwrap":false}},"time":{"created":"2025-03-25T08:28:59.313Z","modified":"2025-03-25T11:14:23.242Z","0.0.0":"2025-03-25T08:28:59.564Z","0.0.1":"2025-03-25T08:48:00.870Z","0.0.2":"2025-03-25T09:07:34.195Z","0.0.3":"2025-03-25T09:10:05.626Z","0.0.4":"2025-03-25T09:13:47.774Z","0.0.5":"2025-03-25T11:14:23.061Z"},"author":{"name":"Keith Horwood","email":"khorwood@openai.com"},"license":"MIT","description":"Nuanu's Realtime API Reference Client (beta)","maintainers":[{"name":"aivanouski","email":"aivanouski@gmail.com"}],"readme":"# Reference Client: Realtime API (beta)\n\nThis repository contains a reference client aka sample library for connecting\nto Nuanu's Realtime API.\n**This library is in beta and should not be treated as a final implementation.**\nYou can use it to easily prototype conversational apps.\n\n# Quickstart\n\nThis library is built to be used both server-side (Node.js) and in browser (React, Vue),\nin both JavaScript and TypeScript codebases. While in beta, to install the library you will\nneed to `npm install` directly from the GitHub repository.\n\n```shell\n$ npm i aurora-nuanu/realtime-api --save\n```\n\n```javascript\nimport { RealtimeClient } from '@aurora-nuanu/realtime-api';\n\nconst client = new RealtimeClient({ apiKey: process.env.OPENAI_API_KEY });\n\n// Can set parameters ahead of connecting, either separately or all at once\nclient.updateSession({ instructions: 'You are a great, upbeat friend.' });\nclient.updateSession({ voice: 'alloy' });\nclient.updateSession({\n  turn_detection: { type: 'none' }, // or 'server_vad'\n  input_audio_transcription: { model: 'whisper-1' },\n});\n\n// Set up event handling\nclient.on('conversation.updated', (event) => {\n  const { item, delta } = event;\n  const items = client.conversation.getItems();\n  /**\n   * item is the current item being updated\n   * delta can be null or populated\n   * you can fetch a full list of items at any time\n   */\n});\n\n// Connect to Realtime API\nawait client.connect();\n\n// Send a item and triggers a generation\nclient.sendUserMessageContent([{ type: 'input_text', text: `How are you?` }]);\n```\n\n## Browser (front-end) quickstart\n\nYou can use this client directly from the browser in e.g. React or Vue apps.\n**We do not recommend this, your API keys are at risk if you connect to OpenAI directly from the browser.**\nIn order to instantiate the client in a browser environment, use:\n\n```javascript\nimport { RealtimeClient } from '@aurora-nuanu/realtime-api';\n\nconst client = new RealtimeClient({\n  apiKey: process.env.OPENAI_API_KEY,\n  dangerouslyAllowAPIKeyInBrowser: true,\n});\n```\n\nIf you are running your own relay server, e.g. with the\n[Realtime Console](https://github.com/openai/openai-realtime-console), you can\ninstead connect to the relay server URL like so:\n\n```javascript\nconst client = new RealtimeClient({ url: RELAY_SERVER_URL });\n```\n\n# Table of contents\n\n1. [Project structure](#project-structure)\n1. [Using the reference client](#using-the-reference-client)\n   1. [Sending messages](#sending-messages)\n   1. [Sending streaming audio](#sending-streaming-audio)\n   1. [Adding and using tools](#adding-and-using-tools)\n      1. [Manually using tools](#manually-using-tools)\n   1. [Interrupting the model](#interrupting-the-model)\n1. [Client events](#client-events)\n   1. [Reference client utility events](#reference-client-utility-events)\n1. [Server events](#server-events)\n1. [Running tests](#running-tests)\n1. [Acknowledgements and contact](#acknowledgements-and-contact)\n\n# Project structure\n\nIn this library, there are three primitives for interfacing with the Realtime API.\nWe recommend starting with the `RealtimeClient`, but more advanced users may be\nmore comfortable working closer to the metal.\n\n1. [`RealtimeClient`](./lib/client.js)\n   - Primary abstraction for interfacing with the Realtime API\n   - Enables rapid application development with a simplified control flow\n   - Has custom `conversation.updated`, `conversation.item.appended`, `conversation.item.completed`, `conversation.interrupted` and `realtime.event` events\n   - These events send item deltas and conversation history\n1. [`RealtimeAPI`](./lib/api.js)\n   - Exists on client instance as `client.realtime`\n   - Thin wrapper over [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)\n   - Use this for connecting to the API, authenticating, and sending items\n   - There is **no item validation**, you will have to rely on the API specification directly\n   - Dispatches events as `server.{event_name}` and `client.{event_name}`, respectively\n1. [`RealtimeConversation`](./lib/conversation.js)\n   - Exists on client instance as `client.conversation`\n   - Stores a client-side cache of your current conversation\n   - Has **event validation**, will validate incoming events to make sure it can cache them properly\n\n# Using the reference client\n\nThe client comes packaged with some basic utilities that make it easy to build realtime\napps quickly.\n\n## Sending messages\n\nSending messages to the server from the user is easy.\n\n```javascript\nclient.sendUserMessageContent([{ type: 'input_text', text: `How are you?` }]);\n// or (empty audio)\nclient.sendUserMessageContent([\n  { type: 'input_audio', audio: new Int16Array(0) },\n]);\n```\n\n## Sending streaming audio\n\nTo send streaming audio, use the `.appendInputAudio()` method. If you're in `turn_detection: 'disabled'` mode,\nthen you need to use `.createResponse()` to tell the model to respond.\n\n```javascript\n// Send user audio, must be Int16Array or ArrayBuffer\n// Default audio format is pcm16 with sample rate of 24,000 Hz\n// This populates 1s of noise in 0.1s chunks\nfor (let i = 0; i < 10; i++) {\n  const data = new Int16Array(2400);\n  for (let n = 0; n < 2400; n++) {\n    const value = Math.floor((Math.random() * 2 - 1) * 0x8000);\n    data[n] = value;\n  }\n  client.appendInputAudio(data);\n}\n// Pending audio is committed and model is asked to generate\nclient.createResponse();\n```\n\n## Adding and using tools\n\nWorking with tools is easy. Just call `.addTool()` and set a callback as the second parameter.\nThe callback will be executed with the parameters for the tool, and the result will be automatically\nsent back to the model.\n\n```javascript\n// We can add tools as well, with callbacks specified\nclient.addTool(\n  {\n    name: 'get_weather',\n    description:\n      'Retrieves the weather for a given lat, lng coordinate pair. Specify a label for the location.',\n    parameters: {\n      type: 'object',\n      properties: {\n        lat: {\n          type: 'number',\n          description: 'Latitude',\n        },\n        lng: {\n          type: 'number',\n          description: 'Longitude',\n        },\n        location: {\n          type: 'string',\n          description: 'Name of the location',\n        },\n      },\n      required: ['lat', 'lng', 'location'],\n    },\n  },\n  async ({ lat, lng, location }) => {\n    const result = await fetch(\n      `https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}&current=temperature_2m,wind_speed_10m`,\n    );\n    const json = await result.json();\n    return json;\n  },\n);\n```\n\n### Manually using tools\n\nThe `.addTool()` method automatically runs a tool handler and triggers a response\non handler completion. Sometimes you may not want that, for example: using tools\nto generate a schema that you use for other purposes.\n\nIn this case, we can use the `tools` item with `updateSession`. In this case you\n**must** specify `type: 'function'`, which is not required for `.addTool()`.\n\n**Note:** Tools added with `.addTool()` will **not** be overridden when updating\nsessions manually like this, but every `updateSession()` change will override previous\n`updateSession()` changes. Tools added via `.addTool()` are persisted and appended\nto anything set manually here.\n\n```javascript\nclient.updateSession({\n  tools: [\n    {\n      type: 'function',\n      name: 'get_weather',\n      description:\n        'Retrieves the weather for a given lat, lng coordinate pair. Specify a label for the location.',\n      parameters: {\n        type: 'object',\n        properties: {\n          lat: {\n            type: 'number',\n            description: 'Latitude',\n          },\n          lng: {\n            type: 'number',\n            description: 'Longitude',\n          },\n          location: {\n            type: 'string',\n            description: 'Name of the location',\n          },\n        },\n        required: ['lat', 'lng', 'location'],\n      },\n    },\n  ],\n});\n```\n\nThen, to handle function calls...\n\n```javascript\nclient.on('conversation.updated', ({ item, delta }) => {\n  if (item.type === 'function_call') {\n    // do something\n    if (delta.arguments) {\n      // populating the arguments\n    }\n  }\n});\n\nclient.on('conversation.item.completed', ({ item }) => {\n  if (item.type === 'function_call') {\n    // your function call is complete, execute some custom code\n  }\n});\n```\n\n## Interrupting the model\n\nYou may want to manually interrupt the model, especially in `turn_detection: 'disabled'` mode.\nTo do this, we can use:\n\n```javascript\n// id is the id of the item currently being generated\n// sampleCount is the number of audio samples that have been heard by the listener\nclient.cancelResponse(id, sampleCount);\n```\n\nThis method will cause the model to immediately cease generation, but also truncate the\nitem being played by removing all audio after `sampleCount` and clearing the text\nresponse. By using this method you can interrupt the model and prevent it from \"remembering\"\nanything it has generated that is ahead of where the user's state is.\n\n# Client events\n\nIf you need more manual control and want to send custom client events according\nto the [Realtime Client Events API Reference](https://platform.openai.com/docs/api-reference/realtime-client-events),\nyou can use `client.realtime.send()` like so:\n\n```javascript\n// manually send a function call output\nclient.realtime.send('conversation.item.create', {\n  item: {\n    type: 'function_call_output',\n    call_id: 'my-call-id',\n    output: '{function_succeeded:true}',\n  },\n});\nclient.realtime.send('response.create');\n```\n\n## Reference client utility events\n\nWith `RealtimeClient` we have reduced the event overhead from server events to **five**\nmain events that are most critical for your application control flow. These events\n**are not** part of the API specification itself, but wrap logic to make application\ndevelopment easier.\n\n```javascript\n// errors like connection failures\nclient.on('error', (event) => {\n  // do thing\n});\n\n// in VAD mode, the user starts speaking\n// we can use this to stop audio playback of a previous response if necessary\nclient.on('conversation.interrupted', () => {\n  /* do something */\n});\n\n// includes all changes to conversations\n// delta may be populated\nclient.on('conversation.updated', ({ item, delta }) => {\n  // get all items, e.g. if you need to update a chat window\n  const items = client.conversation.getItems();\n  switch (item.type) {\n    case 'message':\n      // system, user, or assistant message (item.role)\n      break;\n    case 'function_call':\n      // always a function call from the model\n      break;\n    case 'function_call_output':\n      // always a response from the user / application\n      break;\n  }\n  if (delta) {\n    // Only one of the following will be populated for any given event\n    // delta.audio = Int16Array, audio added\n    // delta.transcript = string, transcript added\n    // delta.arguments = string, function arguments added\n  }\n});\n\n// only triggered after item added to conversation\nclient.on('conversation.item.appended', ({ item }) => {\n  /* item status can be 'in_progress' or 'completed' */\n});\n\n// only triggered after item completed in conversation\n// will always be triggered after conversation.item.appended\nclient.on('conversation.item.completed', ({ item }) => {\n  /* item status will always be 'completed' */\n});\n```\n\n# Server events\n\nIf you want more control over your application development, you can use the\n`realtime.event` event and choose only to respond to **server** events.\nThe full documentation for these events are available on\nthe [Realtime Server Events API Reference](https://platform.openai.com/docs/api-reference/realtime-server-events).\n\n```javascript\n// all events, can use for logging, debugging, or manual event handling\nclient.on('realtime.event', ({ time, source, event }) => {\n  // time is an ISO timestamp\n  // source is 'client' or 'server'\n  // event is the raw event payload (json)\n  if (source === 'server') {\n    doSomething(event);\n  }\n});\n```\n\n# Running tests\n\nYou will need to make sure you have a `.env` file with `OPENAI_API_KEY=` set in order\nto run tests. From there, running the test suite is easy.\n\n```shell\n$ npm test\n```\n\nTo run tests with debug logs (will log events sent to and received from WebSocket), use:\n\n```shell\n$ npm test -- --debug\n```\n","readmeFilename":"README.md"}