livekit-plugins-inworld 1.1.4__tar.gz

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.
@@ -0,0 +1,172 @@
1
+ **/.vscode
2
+ **/.DS_Store
3
+
4
+ # Byte-compiled / optimized / DLL files
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+
9
+ # C extensions
10
+ *.so
11
+
12
+ # Distribution / packaging
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ # Usually these files are written by a python script from a template
34
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
35
+ *.manifest
36
+ *.spec
37
+
38
+ # Installer logs
39
+ pip-log.txt
40
+ pip-delete-this-directory.txt
41
+
42
+ # Unit test / coverage reports
43
+ htmlcov/
44
+ .tox/
45
+ .nox/
46
+ .coverage
47
+ .coverage.*
48
+ .cache
49
+ nosetests.xml
50
+ coverage.xml
51
+ *.cover
52
+ *.py,cover
53
+ .hypothesis/
54
+ .pytest_cache/
55
+ cover/
56
+
57
+ # Translations
58
+ *.mo
59
+ *.pot
60
+
61
+ # Django stuff:
62
+ *.log
63
+ local_settings.py
64
+ db.sqlite3
65
+ db.sqlite3-journal
66
+
67
+ # Flask stuff:
68
+ instance/
69
+ .webassets-cache
70
+
71
+ # Scrapy stuff:
72
+ .scrapy
73
+
74
+ # Sphinx documentation
75
+ docs/_build/
76
+
77
+ # PyBuilder
78
+ .pybuilder/
79
+ target/
80
+
81
+ # Jupyter Notebook
82
+ .ipynb_checkpoints
83
+
84
+ # IPython
85
+ profile_default/
86
+ ipython_config.py
87
+
88
+ # pyenv
89
+ # For a library or package, you might want to ignore these files since the code is
90
+ # intended to run in multiple environments; otherwise, check them in:
91
+ # .python-version
92
+
93
+ # pipenv
94
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
96
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
97
+ # install all needed dependencies.
98
+ #Pipfile.lock
99
+
100
+ # poetry
101
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
102
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
103
+ # commonly ignored for libraries.
104
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
105
+ #poetry.lock
106
+
107
+ # pdm
108
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
109
+ #pdm.lock
110
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
111
+ # in version control.
112
+ # https://pdm.fming.dev/#use-with-ide
113
+ .pdm.toml
114
+
115
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
116
+ __pypackages__/
117
+
118
+ # Celery stuff
119
+ celerybeat-schedule
120
+ celerybeat.pid
121
+
122
+ # SageMath parsed files
123
+ *.sage.py
124
+
125
+ # Environments
126
+ .env
127
+ .venv
128
+ env/
129
+ venv/
130
+ ENV/
131
+ env.bak/
132
+ venv.bak/
133
+
134
+ # Spyder project settings
135
+ .spyderproject
136
+ .spyproject
137
+
138
+ # Rope project settings
139
+ .ropeproject
140
+
141
+ # mkdocs documentation
142
+ /site
143
+
144
+ # mypy
145
+ .mypy_cache/
146
+ .dmypy.json
147
+ dmypy.json
148
+
149
+ # trunk
150
+ .trunk/
151
+
152
+ # Pyre type checker
153
+ .pyre/
154
+
155
+ # pytype static type analyzer
156
+ .pytype/
157
+
158
+ # Cython debug symbols
159
+ cython_debug/
160
+
161
+ # PyCharm
162
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
163
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
164
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
165
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
166
+ .idea/
167
+
168
+ node_modules
169
+
170
+ credentials.json
171
+ pyrightconfig.json
172
+ docs/
@@ -0,0 +1,54 @@
1
+ Metadata-Version: 2.4
2
+ Name: livekit-plugins-inworld
3
+ Version: 1.1.4
4
+ Summary: Agent Framework plugin for voice synthesis with Inworld's API.
5
+ Project-URL: Documentation, https://docs.livekit.io
6
+ Project-URL: Website, https://livekit.io/
7
+ Project-URL: Source, https://github.com/livekit/agents
8
+ Author-email: LiveKit <hello@livekit.io>
9
+ License-Expression: Apache-2.0
10
+ Keywords: audio,inworld,livekit,realtime,video,webrtc
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: Apache Software License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Topic :: Multimedia :: Sound/Audio
18
+ Classifier: Topic :: Multimedia :: Video
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.9.0
21
+ Requires-Dist: livekit-agents>=1.1.4
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Inworld plugin for LiveKit Agents
25
+
26
+ Support for voice synthesis with [Inworld](https://beta.docs.inworld.ai/api-reference/ttsAPI/texttospeech/synthesize-speech).
27
+
28
+ See [https://docs.livekit.io/agents/integrations/tts/inworld/](https://docs.livekit.io/agents/integrations/tts/inworld/) for more information.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install livekit-plugins-inworld
34
+ ```
35
+
36
+ ## Authentication
37
+
38
+ The Inworld plugin requires a [Inworld API key](https://studio.inworld.ai/login).
39
+
40
+ Set `INWORLD_API_KEY` in your `.env` file.
41
+
42
+ ## Usage
43
+
44
+ Use Inworld TTS within an `AgentSession` or as a standalone speech generator. For example,
45
+ you can use this TTS in the [Voice AI quickstart](/agents/start/voice-ai/).
46
+
47
+ ```python
48
+ from livekit.plugins import inworld
49
+
50
+ session = AgentSession(
51
+ tts=inworld.TTS(voice="Ashley")
52
+ # ... llm, stt, etc.
53
+ )
54
+ ```
@@ -0,0 +1,31 @@
1
+ # Inworld plugin for LiveKit Agents
2
+
3
+ Support for voice synthesis with [Inworld](https://beta.docs.inworld.ai/api-reference/ttsAPI/texttospeech/synthesize-speech).
4
+
5
+ See [https://docs.livekit.io/agents/integrations/tts/inworld/](https://docs.livekit.io/agents/integrations/tts/inworld/) for more information.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install livekit-plugins-inworld
11
+ ```
12
+
13
+ ## Authentication
14
+
15
+ The Inworld plugin requires a [Inworld API key](https://studio.inworld.ai/login).
16
+
17
+ Set `INWORLD_API_KEY` in your `.env` file.
18
+
19
+ ## Usage
20
+
21
+ Use Inworld TTS within an `AgentSession` or as a standalone speech generator. For example,
22
+ you can use this TTS in the [Voice AI quickstart](/agents/start/voice-ai/).
23
+
24
+ ```python
25
+ from livekit.plugins import inworld
26
+
27
+ session = AgentSession(
28
+ tts=inworld.TTS(voice="Ashley")
29
+ # ... llm, stt, etc.
30
+ )
31
+ ```
@@ -0,0 +1,47 @@
1
+ # Copyright 2025 LiveKit, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Inworld plugin for LiveKit Agents
16
+
17
+ See https://docs.livekit.io/agents/integrations/tts/inworld/ for more information.
18
+ """
19
+
20
+ from .tts import TTS
21
+ from .version import __version__
22
+
23
+ __all__ = [
24
+ "TTS",
25
+ "__version__",
26
+ ]
27
+
28
+ from livekit.agents import Plugin
29
+
30
+ from .log import logger
31
+
32
+
33
+ class InworldPlugin(Plugin):
34
+ def __init__(self) -> None:
35
+ super().__init__(__name__, __version__, __package__, logger)
36
+
37
+
38
+ Plugin.register_plugin(InworldPlugin())
39
+
40
+ # Cleanup docs of unexported modules
41
+ _module = dir()
42
+ NOT_IN_ALL = [m for m in _module if m not in __all__]
43
+
44
+ __pdoc__ = {}
45
+
46
+ for n in NOT_IN_ALL:
47
+ __pdoc__[n] = False
@@ -0,0 +1,3 @@
1
+ import logging
2
+
3
+ logger = logging.getLogger("livekit.plugins.inworld")
@@ -0,0 +1,263 @@
1
+ # Copyright 2025 LiveKit, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from __future__ import annotations
16
+
17
+ import asyncio
18
+ import base64
19
+ import json
20
+ import os
21
+ from dataclasses import dataclass, replace
22
+ from typing import Any, Literal, Union
23
+ from urllib.parse import urljoin
24
+
25
+ import aiohttp
26
+
27
+ from livekit.agents import tts, utils
28
+ from livekit.agents._exceptions import (
29
+ APIConnectionError,
30
+ APIStatusError,
31
+ APITimeoutError,
32
+ )
33
+ from livekit.agents.types import (
34
+ DEFAULT_API_CONNECT_OPTIONS,
35
+ NOT_GIVEN,
36
+ APIConnectOptions,
37
+ NotGivenOr,
38
+ )
39
+
40
+ DEFAULT_BIT_RATE = 64000
41
+ DEFAULT_ENCODING = "OGG_OPUS"
42
+ DEFAULT_MODEL = "inworld-tts-1"
43
+ DEFAULT_SAMPLE_RATE = 48000
44
+ DEFAULT_URL = "https://api.inworld.ai/"
45
+ DEFAULT_VOICE = "Ashley"
46
+ NUM_CHANNELS = 1
47
+
48
+ Encoding = Union[Literal["LINEAR16", "MP3", "OGG_OPUS"], str]
49
+
50
+
51
+ @dataclass
52
+ class _TTSOptions:
53
+ model: str
54
+ encoding: Encoding
55
+ voice: str
56
+ sample_rate: int
57
+ bit_rate: NotGivenOr[int] = NOT_GIVEN
58
+ pitch: NotGivenOr[float] = NOT_GIVEN
59
+ speaking_rate: NotGivenOr[float] = NOT_GIVEN
60
+ temperature: NotGivenOr[float] = NOT_GIVEN
61
+
62
+ @property
63
+ def mime_type(self) -> str:
64
+ if self.encoding == "MP3":
65
+ return "audio/mpeg"
66
+ elif self.encoding == "OGG_OPUS":
67
+ return "audio/ogg"
68
+ else:
69
+ return "audio/wav"
70
+
71
+
72
+ class TTS(tts.TTS):
73
+ def __init__(
74
+ self,
75
+ *,
76
+ api_key: NotGivenOr[str] = NOT_GIVEN,
77
+ voice: NotGivenOr[str] = NOT_GIVEN,
78
+ model: NotGivenOr[str] = NOT_GIVEN,
79
+ encoding: NotGivenOr[Encoding] = NOT_GIVEN,
80
+ bit_rate: NotGivenOr[int] = NOT_GIVEN,
81
+ sample_rate: NotGivenOr[int] = NOT_GIVEN,
82
+ pitch: NotGivenOr[float] = NOT_GIVEN,
83
+ speaking_rate: NotGivenOr[float] = NOT_GIVEN,
84
+ temperature: NotGivenOr[float] = NOT_GIVEN,
85
+ base_url: str = DEFAULT_URL,
86
+ http_session: aiohttp.ClientSession | None = None,
87
+ ) -> None:
88
+ """
89
+ Create a new instance of Inworld TTS.
90
+
91
+ Args:
92
+ api_key (str, optional): The Inworld API key.
93
+ If not provided, it will be read from the INWORLD_API_KEY environment variable.
94
+ voice (str, optional): The voice to use. Defaults to "Ashley".
95
+ model (str, optional): The Inworld model to use. Defaults to "inworld-tts-1".
96
+ encoding (str, optional): The encoding to use. Defaults to "MP3".
97
+ bit_rate (int, optional): Bits per second of the audio. Defaults to 64000.
98
+ sample_rate (int, optional): The audio sample rate in Hz. Defaults to 24000.
99
+ pitch (float, optional): The pitch of the voice. Defaults to 0.0.
100
+ speaking_rate (float, optional): The speed of the voice. Defaults to 1.0.
101
+ temperature (float, optional): Determines the degree of randomness when sampling audio
102
+ tokens to generate the response. Defaults to 0.8.
103
+ base_url (str, optional): The base URL for the Inworld TTS API.
104
+ Defaults to "https://api.inworld.ai/".
105
+ http_session (aiohttp.ClientSession, optional): The HTTP session to use.
106
+ """
107
+ if not utils.is_given(sample_rate):
108
+ sample_rate = DEFAULT_SAMPLE_RATE
109
+ super().__init__(
110
+ capabilities=tts.TTSCapabilities(streaming=False),
111
+ sample_rate=sample_rate,
112
+ num_channels=NUM_CHANNELS,
113
+ )
114
+
115
+ api_key = api_key or os.getenv("INWORLD_API_KEY", "")
116
+ if not api_key:
117
+ raise ValueError("Inworld API key required. Set INWORLD_API_KEY or provide api_key.")
118
+
119
+ self._authorization = f"Basic {api_key}"
120
+ self._base_url = base_url
121
+ self._session = http_session
122
+
123
+ self._opts = _TTSOptions(
124
+ voice=voice if utils.is_given(voice) else DEFAULT_VOICE,
125
+ model=model if utils.is_given(model) else DEFAULT_MODEL,
126
+ encoding=encoding if utils.is_given(encoding) else DEFAULT_ENCODING,
127
+ bit_rate=bit_rate if utils.is_given(bit_rate) else DEFAULT_BIT_RATE,
128
+ sample_rate=sample_rate if utils.is_given(sample_rate) else DEFAULT_SAMPLE_RATE,
129
+ pitch=pitch,
130
+ speaking_rate=speaking_rate,
131
+ temperature=temperature,
132
+ )
133
+
134
+ def update_options(
135
+ self,
136
+ *,
137
+ voice: NotGivenOr[str] = NOT_GIVEN,
138
+ model: NotGivenOr[str] = NOT_GIVEN,
139
+ encoding: NotGivenOr[Encoding] = NOT_GIVEN,
140
+ bit_rate: NotGivenOr[int] = NOT_GIVEN,
141
+ sample_rate: NotGivenOr[int] = NOT_GIVEN,
142
+ pitch: NotGivenOr[float] = NOT_GIVEN,
143
+ speaking_rate: NotGivenOr[float] = NOT_GIVEN,
144
+ temperature: NotGivenOr[float] = NOT_GIVEN,
145
+ ) -> None:
146
+ """
147
+ Update the TTS configuration options.
148
+
149
+ Args:
150
+ voice (str, optional): The voice to use.
151
+ model (str, optional): The Inworld model to use.
152
+ encoding (str, optional): The encoding to use.
153
+ bit_rate (int, optional): Bits per second of the audio.
154
+ sample_rate (int, optional): The audio sample rate in Hz.
155
+ pitch (float, optional): The pitch of the voice.
156
+ speaking_rate (float, optional): The speed of the voice.
157
+ temperature (float, optional): Determines the degree of randomness when sampling audio
158
+ tokens to generate the response. Defaults to 0.8.
159
+ """
160
+ if utils.is_given(voice):
161
+ self._opts.voice = voice
162
+ if utils.is_given(model):
163
+ self._opts.model = model
164
+ if utils.is_given(encoding):
165
+ self._opts.encoding = encoding
166
+ if utils.is_given(bit_rate):
167
+ self._opts.bit_rate = bit_rate
168
+ if utils.is_given(sample_rate):
169
+ self._opts.sample_rate = sample_rate
170
+ if utils.is_given(pitch):
171
+ self._opts.pitch = pitch
172
+ if utils.is_given(speaking_rate):
173
+ self._opts.speaking_rate = speaking_rate
174
+ if utils.is_given(temperature):
175
+ self._opts.temperature = temperature
176
+
177
+ def _ensure_session(self) -> aiohttp.ClientSession:
178
+ if not self._session:
179
+ self._session = utils.http_context.http_session()
180
+
181
+ return self._session
182
+
183
+ def synthesize(
184
+ self,
185
+ text: str,
186
+ *,
187
+ conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
188
+ ) -> tts.ChunkedStream:
189
+ return ChunkedStream(tts=self, input_text=text, conn_options=conn_options)
190
+
191
+
192
+ class ChunkedStream(tts.ChunkedStream):
193
+ def __init__(self, *, tts: TTS, input_text: str, conn_options: APIConnectOptions) -> None:
194
+ super().__init__(tts=tts, input_text=input_text, conn_options=conn_options)
195
+ self._tts: TTS = tts
196
+ self._opts = replace(tts._opts)
197
+
198
+ async def _run(self, output_emitter: tts.AudioEmitter) -> None:
199
+ try:
200
+ audio_config: dict[str, Any] = {
201
+ "audioEncoding": self._opts.encoding,
202
+ }
203
+ if utils.is_given(self._opts.bit_rate):
204
+ audio_config["bitrate"] = self._opts.bit_rate
205
+ if utils.is_given(self._opts.sample_rate):
206
+ audio_config["sampleRateHertz"] = self._opts.sample_rate
207
+ if utils.is_given(self._opts.pitch):
208
+ audio_config["pitch"] = self._opts.pitch
209
+ if utils.is_given(self._opts.temperature):
210
+ audio_config["temperature"] = self._opts.temperature
211
+ if utils.is_given(self._opts.speaking_rate):
212
+ audio_config["speakingRate"] = self._opts.speaking_rate
213
+
214
+ body_params: dict[str, Any] = {
215
+ "text": self._input_text,
216
+ "voiceId": self._opts.voice,
217
+ "modelId": self._opts.model,
218
+ "audioConfig": audio_config,
219
+ }
220
+ if utils.is_given(self._opts.temperature):
221
+ body_params["temperature"] = self._opts.temperature
222
+
223
+ async with self._tts._ensure_session().post(
224
+ urljoin(self._tts._base_url, "/tts/v1/voice:stream"),
225
+ headers={
226
+ "Authorization": self._tts._authorization,
227
+ },
228
+ json=body_params,
229
+ timeout=aiohttp.ClientTimeout(sock_connect=self._conn_options.timeout),
230
+ ) as resp:
231
+ resp.raise_for_status()
232
+
233
+ request_id = utils.shortuuid()
234
+ output_emitter.initialize(
235
+ request_id=request_id,
236
+ sample_rate=self._opts.sample_rate,
237
+ num_channels=NUM_CHANNELS,
238
+ mime_type=self._opts.mime_type,
239
+ )
240
+
241
+ async for line in resp.content:
242
+ if not line:
243
+ break
244
+ data = json.loads(line)
245
+ if result := data.get("result"):
246
+ if audio_content := result.get("audioContent"):
247
+ output_emitter.push(base64.b64decode(audio_content))
248
+ output_emitter.flush()
249
+ elif error := data.get("error"):
250
+ raise APIStatusError(
251
+ message=error.get("message"),
252
+ status_code=error.get("code"),
253
+ request_id=request_id,
254
+ body=None,
255
+ )
256
+ except asyncio.TimeoutError:
257
+ raise APITimeoutError() from None
258
+ except aiohttp.ClientResponseError as e:
259
+ raise APIStatusError(
260
+ message=e.message, status_code=e.status, request_id=None, body=None
261
+ ) from None
262
+ except Exception as e:
263
+ raise APIConnectionError() from e
@@ -0,0 +1,15 @@
1
+ # Copyright 2025 LiveKit, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ __version__ = "1.1.4"
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "livekit-plugins-inworld"
7
+ dynamic = ["version"]
8
+ description = "Agent Framework plugin for voice synthesis with Inworld's API."
9
+ readme = "README.md"
10
+ license = "Apache-2.0"
11
+ requires-python = ">=3.9.0"
12
+ authors = [{ name = "LiveKit", email = "hello@livekit.io" }]
13
+ keywords = ["webrtc", "realtime", "audio", "video", "livekit", "inworld"]
14
+ classifiers = [
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: Apache Software License",
17
+ "Topic :: Multimedia :: Sound/Audio",
18
+ "Topic :: Multimedia :: Video",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.9",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3 :: Only",
24
+ ]
25
+ dependencies = [
26
+ "livekit-agents>=1.1.4",
27
+ ]
28
+
29
+ [project.urls]
30
+ Documentation = "https://docs.livekit.io"
31
+ Website = "https://livekit.io/"
32
+ Source = "https://github.com/livekit/agents"
33
+
34
+ [tool.hatch.version]
35
+ path = "livekit/plugins/inworld/version.py"
36
+
37
+ [tool.hatch.build.targets.wheel]
38
+ packages = ["livekit"]
39
+
40
+ [tool.hatch.build.targets.sdist]
41
+ include = ["/livekit"]