create-spatius-app 0.1.3-beta.1 → 0.1.3

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.
Files changed (45) hide show
  1. package/README.md +12 -10
  2. package/dist/cli.js +199 -207
  3. package/dist/templates.js +183 -199
  4. package/package.json +3 -3
  5. package/templates/cloudflare-livekit/AGENTS.md +2 -0
  6. package/templates/cloudflare-livekit/README.md +10 -0
  7. package/templates/cloudflare-livekit/agent/Dockerfile.pip +8 -0
  8. package/templates/cloudflare-livekit/agent/Dockerfile.uv +8 -0
  9. package/templates/cloudflare-livekit/agent/README.md +16 -1
  10. package/templates/cloudflare-livekit/agent/pyproject.toml +1 -1
  11. package/templates/cloudflare-livekit/agent/src/agent.py +2 -1
  12. package/templates/cloudflare-livekit/agent/tests/test_agent_startup.py +49 -0
  13. package/templates/cloudflare-livekit/agent/uv.lock +14 -14
  14. package/templates/cloudflare-livekit/gitignore +1 -0
  15. package/templates/cloudflare-livekit/package-lock.json +511 -411
  16. package/templates/cloudflare-livekit/package.json +17 -16
  17. package/templates/cloudflare-livekit/pnpm-lock.yaml +572 -480
  18. package/templates/cloudflare-livekit/tests/dev-fixtures.mjs +7 -2
  19. package/templates/cloudflare-livekit/tests/dev-supervision.test.mjs +82 -20
  20. package/templates/layers/agora/AGENTS.md +9 -4
  21. package/templates/layers/agora/README.md +14 -8
  22. package/templates/layers/agora/package-lock.json +6307 -4129
  23. package/templates/layers/agora/package.json +24 -18
  24. package/templates/layers/agora/pnpm-lock.yaml +1765 -563
  25. package/templates/layers/agora/pnpm-workspace.yaml +8 -0
  26. package/templates/layers/agora/scripts/check-worker.mjs +98 -0
  27. package/templates/layers/agora/tsconfig.worker.json +23 -0
  28. package/templates/layers/agora/worker/agora.test.ts +2 -0
  29. package/templates/layers/agora/worker/agora.ts +1 -10
  30. package/templates/layers/agora/worker/index.test.ts +66 -0
  31. package/templates/layers/agora/worker/index.ts +4 -0
  32. package/templates/layers/agora/wrangler.jsonc +24 -0
  33. package/templates/cloudflare-livekit/worker-configuration.d.ts +0 -17184
  34. package/templates/layers/agora/tsconfig.json +0 -16
  35. package/templates/layers/node/Dockerfile +0 -12
  36. package/templates/layers/node/dockerignore +0 -10
  37. package/templates/layers/node/package-lock.json +0 -5569
  38. package/templates/layers/node/pnpm-lock.yaml +0 -3470
  39. package/templates/layers/node/railway.json +0 -8
  40. package/templates/layers/node/scripts/dev-service.mjs +0 -14
  41. package/templates/layers/node/scripts/dev-web.mjs +0 -22
  42. package/templates/layers/node/server/index.ts +0 -92
  43. package/templates/layers/node/vite.config.ts +0 -17
  44. package/templates/layers/railway-agent/agent/railway.json +0 -5
  45. /package/templates/layers/agora/{env.example → dev.vars.example} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-spatius-app",
3
- "version": "0.1.3-beta.1",
4
- "description": "Create a Spatius voice-avatar app with LiveKit or Agora and your choice of hosting.",
3
+ "version": "0.1.3",
4
+ "description": "Create a Spatius voice-avatar app with LiveKit or Agora Conversational AI on Cloudflare Workers.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "create-spatius-app": "./dist/cli.js"
@@ -34,7 +34,7 @@
34
34
  "engines": {
35
35
  "node": ">=22"
36
36
  },
37
- "packageManager": "pnpm@12.3.4",
37
+ "packageManager": "pnpm@12.4.1",
38
38
  "publishConfig": {
39
39
  "access": "public",
40
40
  "provenance": true
@@ -22,6 +22,8 @@ Run from the project root:
22
22
 
23
23
  All checks can run after dependency installation, before credential setup.
24
24
  Browser tests require Chromium; see README.md for the installation command.
25
+ Wrangler generates the ignored `worker-configuration.d.ts` during installation
26
+ and before lint, type checking, and builds. Update `wrangler.jsonc` to change bindings.
25
27
 
26
28
  ## Working guidelines
27
29
 
@@ -54,6 +54,16 @@ This starts the Python agent first, then starts the frontend and Cloudflare
54
54
  Worker once the agent has registered with LiveKit. If registration takes more
55
55
  than 60 seconds, startup stops with an error.
56
56
 
57
+ Press Ctrl+C once to stop both services. If shutdown logs cover the shell prompt
58
+ and it looks as though another Ctrl+C is required, use pnpm 12.4.1 or later for
59
+ both the executable on your PATH and the `packageManager` pin in `package.json`.
60
+ An older launcher or project pin can return to the shell before the services
61
+ finish shutting down; [pnpm 12.4.1 fixes signal forwarding and waiting](https://github.com/pnpm/pnpm/releases/tag/v12.4.1).
62
+ For a Homebrew installation, run `brew update` followed by `brew upgrade pnpm`.
63
+ Set `packageManager` to `pnpm@12.4.1`, then run `pnpm install` to refresh the
64
+ package-manager lock entry. Changing only the project pin leaves an older
65
+ launcher in use; upgrading only the launcher leaves an older project pin active.
66
+
57
67
  ## Validate without credentials
58
68
 
59
69
  After installing both dependency sets, you can run these checks before account
@@ -21,6 +21,11 @@ ENV PATH="/app/.venv/bin:$PATH" \
21
21
  PYTHONDONTWRITEBYTECODE=1 \
22
22
  PYTHONUNBUFFERED=1
23
23
 
24
+ # Spatius encodes outgoing avatar audio with the native Opus library.
25
+ RUN apt-get update \
26
+ && apt-get install -y --no-install-recommends libopus0 \
27
+ && rm -rf /var/lib/apt/lists/*
28
+
24
29
  RUN useradd --create-home --uid 10001 appuser
25
30
  WORKDIR /app
26
31
 
@@ -28,4 +33,7 @@ COPY --from=builder --chown=appuser:appuser /app /app
28
33
 
29
34
  USER appuser
30
35
 
36
+ # Fail the build if the runtime cannot load the native audio encoder.
37
+ RUN python -c "import opuslib_next"
38
+
31
39
  CMD ["python", "-m", "src.agent", "start"]
@@ -19,6 +19,11 @@ FROM python:${PYTHON_VERSION}-slim-bookworm
19
19
  ENV PATH="/app/.venv/bin:$PATH" \
20
20
  PYTHONUNBUFFERED=1
21
21
 
22
+ # Spatius encodes outgoing avatar audio with the native Opus library.
23
+ RUN apt-get update \
24
+ && apt-get install -y --no-install-recommends libopus0 \
25
+ && rm -rf /var/lib/apt/lists/*
26
+
22
27
  RUN useradd --create-home --uid 10001 appuser
23
28
  WORKDIR /app
24
29
 
@@ -26,4 +31,7 @@ COPY --from=builder --chown=appuser:appuser /app /app
26
31
 
27
32
  USER appuser
28
33
 
34
+ # Fail the build if the runtime cannot load the native audio encoder.
35
+ RUN python -c "import opuslib_next"
36
+
29
37
  CMD ["python", "-m", "src.agent", "start"]
@@ -6,6 +6,19 @@ and turn detection. The Spatius plugin sends the generated speech through the
6
6
  Spatius motion service and publishes synchronized avatar data into the same
7
7
  LiveKit room as the browser.
8
8
 
9
+ ## Session startup
10
+
11
+ The worker runs `spatius.prewarm` through `AgentServer`'s `setup_fnc` when each
12
+ job process initializes. It prepares automatic region selection, TLS connections,
13
+ and a session token using the same Spatius environment variables as the avatar
14
+ session, so a warm process can skip that setup when a room is dispatched.
15
+ The avatar ID still comes from the job's dispatch metadata.
16
+
17
+ Warm-up is best effort: missing configuration or a failed warm-up leaves normal
18
+ session startup responsible for resolving the region and fetching a token.
19
+ LiveKit keeps idle processes ready in production; development mode initializes
20
+ processes on demand, so its first session still pays the warm-up cost.
21
+
9
22
  ## Voice configuration
10
23
 
11
24
  - TTS uses `cartesia/sonic-3.6` through LiveKit Inference. Setup offers
@@ -77,4 +90,6 @@ Use `lk agent deploy` for later releases. Keep `livekit.toml` local until you
77
90
  have reviewed the project-specific values it contains.
78
91
 
79
92
  The Docker image runs as a non-root user and downloads model support files at
80
- build time so startup does not need to fetch them.
93
+ build time so startup does not need to fetch them. It also installs the native
94
+ Opus library required for Spatius audio forwarding and checks that the Python
95
+ binding can load it during the build.
@@ -5,7 +5,7 @@ description = "A LiveKit voice agent with a Spatius avatar"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11,<3.15"
7
7
  dependencies = [
8
- "livekit-agents[spatius]~=1.8.0",
8
+ "livekit-agents[spatius]~=1.8.1",
9
9
  "livekit-plugins-noise-cancellation~=0.3.0",
10
10
  "python-dotenv>=1.2.1,<2",
11
11
  ]
@@ -43,7 +43,8 @@ class Assistant(Agent):
43
43
  )
44
44
 
45
45
 
46
- server = AgentServer()
46
+ # Warm region, TLS, and session-token caches in each job process before dispatch.
47
+ server = AgentServer(setup_fnc=spatius.prewarm)
47
48
 
48
49
 
49
50
  @server.on("worker_registered")
@@ -11,6 +11,55 @@ with patch("dotenv.load_dotenv"):
11
11
  from src import agent as agent_module
12
12
 
13
13
 
14
+ class ProcessPrewarmTests(unittest.TestCase):
15
+ def setUp(self) -> None:
16
+ self.enterContext(
17
+ patch.dict(
18
+ "os.environ",
19
+ {"SPATIUS_APP_ID": "app-test", "SPATIUS_API_KEY": "key-test"},
20
+ clear=True,
21
+ )
22
+ )
23
+ # Exercise the registered hook and installed plugin without provider calls.
24
+ self.sdk_prewarm = self.enterContext(
25
+ patch(
26
+ "spatius.prewarm",
27
+ new_callable=AsyncMock,
28
+ return_value=SimpleNamespace(
29
+ region="test-region",
30
+ tls_warmed=True,
31
+ session_token_prefetched=True,
32
+ ),
33
+ )
34
+ )
35
+
36
+ def test_process_setup_warms_connection_state_without_an_avatar(self) -> None:
37
+ with patch.object(agent_module.spatius, "AvatarSession") as avatar:
38
+ agent_module.server.setup_fnc(SimpleNamespace())
39
+
40
+ self.sdk_prewarm.assert_awaited_once()
41
+ options = self.sdk_prewarm.call_args.kwargs
42
+ self.assertEqual(options["app_id"], "app-test")
43
+ self.assertEqual(options["api_key"], "key-test")
44
+ self.assertEqual(options["region"], "auto")
45
+ self.assertTrue(options["prefetch_session_token"])
46
+ avatar.assert_not_called()
47
+
48
+ def test_warmup_failure_does_not_fail_process_setup(self) -> None:
49
+ self.sdk_prewarm.side_effect = ConnectionError("warm-up unavailable")
50
+
51
+ with self.assertLogs("livekit.plugins.spatius", level="WARNING"):
52
+ agent_module.server.setup_fnc(SimpleNamespace())
53
+
54
+ self.sdk_prewarm.assert_awaited_once()
55
+
56
+ def test_process_setup_without_configuration_skips_warmup(self) -> None:
57
+ with patch.dict("os.environ", {}, clear=True):
58
+ agent_module.server.setup_fnc(SimpleNamespace())
59
+
60
+ self.sdk_prewarm.assert_not_awaited()
61
+
62
+
14
63
  class AgentStartupTests(unittest.IsolatedAsyncioTestCase):
15
64
  def setUp(self) -> None:
16
65
  self.events: list[str] = []
@@ -777,7 +777,7 @@ wheels = [
777
777
 
778
778
  [[package]]
779
779
  name = "livekit"
780
- version = "1.1.17"
780
+ version = "1.1.18"
781
781
  source = { registry = "https://pypi.org/simple" }
782
782
  dependencies = [
783
783
  { name = "aiofiles" },
@@ -786,18 +786,18 @@ dependencies = [
786
786
  { name = "protobuf" },
787
787
  { name = "types-protobuf" },
788
788
  ]
789
- sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/e20f97969cf21959061cdc6e1c89e3ad88e23693030398df283c236430fc/livekit-1.1.17.tar.gz", hash = "sha256:0553bfc9041dc8430029597a2278b43e4bb75594a72cb9dcc42bf3ee8e1689a0", size = 378467, upload-time = "2026-09-01T05:03:19.976Z" }
789
+ sdist = { url = "https://files.pythonhosted.org/packages/43/06/ed2cda99ce0c7f10f6599588b296d2bde08ec1235a97416e9986629ce131/livekit-1.1.18.tar.gz", hash = "sha256:1097b8613fa4625e3fb0bd4fe5b09a3b04b39151c08b78498831dfc57235d1d1", size = 386573, upload-time = "2026-09-08T01:50:55.05Z" }
790
790
  wheels = [
791
- { url = "https://files.pythonhosted.org/packages/fd/74/d93c5dd19ff2139c070eacb634e075cd2aff239a9cf692002afaa529a630/livekit-1.1.17-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:39188ac6c805643c8ef952cccb96b2e81e430600e626dea9c6aa1faca7013fdb", size = 10468167, upload-time = "2026-09-01T05:03:06.001Z" },
792
- { url = "https://files.pythonhosted.org/packages/d6/3a/9f4f5e3f0c325d0d5d19c5dc1ae345f4bf1353369db3b42e265a1ee4dbc5/livekit-1.1.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:078d249974aed5e845b60f840802e36dc9d2c9c1dd606341fb95ee018749169e", size = 9289118, upload-time = "2026-09-01T05:03:09.011Z" },
793
- { url = "https://files.pythonhosted.org/packages/1a/d5/89293b55b7a719c7f7b7929d995c009bee7ce9f4d29876c77ee878fd9bf9/livekit-1.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:984c7c0efa3e1f917e973ce412a141ff8244baf98ff24587aa82c206b621054f", size = 10580945, upload-time = "2026-09-01T05:03:11.743Z" },
794
- { url = "https://files.pythonhosted.org/packages/c9/4e/aad025688f8aceeecb4ec97aaae3541d64302e03454c84dc57e02bbaf5b9/livekit-1.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:100f2ddd8e433ea906ecf45f1d37063e43e72c7c2cff0b65ddddc11181bc8dd7", size = 11805780, upload-time = "2026-09-01T05:03:14.614Z" },
795
- { url = "https://files.pythonhosted.org/packages/97/7a/5447238155494f2a2fd55ff0f7705b9769fe67e787b52150a5d269eeaa61/livekit-1.1.17-py3-none-win_amd64.whl", hash = "sha256:7a7b1bdb646775d8042ec97baec6c6b615c5828d6f9b6dd2adaa167841d6f6c2", size = 10968316, upload-time = "2026-09-01T05:03:17.671Z" },
791
+ { url = "https://files.pythonhosted.org/packages/d3/b1/be04d760136480aa2d27f8247adc6d0a1e25395b146f04267fb8e716fe6a/livekit-1.1.18-py3-none-macosx_10_15_x86_64.whl", hash = "sha256:8337e956e9816814572c964e7553d66e4845bc3299cb5aeab8a0d671e293ed32", size = 10471856, upload-time = "2026-09-08T01:50:41.85Z" },
792
+ { url = "https://files.pythonhosted.org/packages/d3/60/a0bf23774eecfc012b60bae84f329537053a0e0952d6f43dc6154d01ac5b/livekit-1.1.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8557d6438da4b8968263b44920e7ab606a9ea34bc49fb8fd89054eb3e5cc162d", size = 9292804, upload-time = "2026-09-08T01:50:44.968Z" },
793
+ { url = "https://files.pythonhosted.org/packages/f1/68/4b5e996660185f2db937a356c9e72981eab42c846f24ba992b997fa9f973/livekit-1.1.18-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e9305aa097fe75f533db3205ab15868730e467130ffdda7d4adc3840f30b282a", size = 10584633, upload-time = "2026-09-08T01:50:47.735Z" },
794
+ { url = "https://files.pythonhosted.org/packages/e0/5e/a0220ddf5e53bb3ee4f4969e9ca5e3a3e9567c584b66cd71355a45c069a8/livekit-1.1.18-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:dc2461b668ffa66ca504d05c88af7ea640372f53672ae367145412e374d6aff0", size = 11809468, upload-time = "2026-09-08T01:50:50.332Z" },
795
+ { url = "https://files.pythonhosted.org/packages/f6/47/22455e01c76b271f7d9399546c2becabe4bf0f58a43e79859bb0ca4fc426/livekit-1.1.18-py3-none-win_amd64.whl", hash = "sha256:a61d4fd39392e7d28a7dbbe3b68be7b600115ed32501f5b016579846de945913", size = 10972019, upload-time = "2026-09-08T01:50:52.912Z" },
796
796
  ]
797
797
 
798
798
  [[package]]
799
799
  name = "livekit-agents"
800
- version = "1.8.0"
800
+ version = "1.8.1"
801
801
  source = { registry = "https://pypi.org/simple" }
802
802
  dependencies = [
803
803
  { name = "aiofiles" },
@@ -833,9 +833,9 @@ dependencies = [
833
833
  { name = "typing-extensions" },
834
834
  { name = "watchfiles" },
835
835
  ]
836
- sdist = { url = "https://files.pythonhosted.org/packages/99/88/ace06ebd95b8cc45e25430a2a10ec7ccfb6b386b67907192ad421493858f/livekit_agents-1.8.0.tar.gz", hash = "sha256:132029af152a585770b58623a1a2ac7c8b1b9241b045e8cd220b30415fbcf2ad", size = 2688509, upload-time = "2026-09-05T00:10:03.294Z" }
836
+ sdist = { url = "https://files.pythonhosted.org/packages/5c/f1/cc1fcbc24da6f90cf38809c5ceafff1a1d1c1527a32431ce9b00e64deb95/livekit_agents-1.8.1.tar.gz", hash = "sha256:9319b4a7cf3891db4c1de9edd98fad4918919beadb2ceff5b3fbd34ae67384b5", size = 2712687, upload-time = "2026-09-10T18:59:30.729Z" }
837
837
  wheels = [
838
- { url = "https://files.pythonhosted.org/packages/f4/79/a1dce13ad49ab5b3248727b808ef265b2a535928c53161440d049a0b747d/livekit_agents-1.8.0-py3-none-any.whl", hash = "sha256:0da6c79c71eb7620af14d5c18adba1da796d6743338630813c341e9ce72f45c8", size = 2806075, upload-time = "2026-09-05T00:10:01.23Z" },
838
+ { url = "https://files.pythonhosted.org/packages/0e/2c/29419a913c57b863eba93cbb41df2f3e58b57c3d8f790c6f97be81203ef7/livekit_agents-1.8.1-py3-none-any.whl", hash = "sha256:3e9e4e732ecebcf0512d5eec25621a365c3572289d1f136396d8a1c5ea78e2ac", size = 2832498, upload-time = "2026-09-10T18:59:28.67Z" },
839
839
  ]
840
840
 
841
841
  [package.optional-dependencies]
@@ -935,15 +935,15 @@ wheels = [
935
935
 
936
936
  [[package]]
937
937
  name = "livekit-plugins-spatius"
938
- version = "1.8.0"
938
+ version = "1.8.1"
939
939
  source = { registry = "https://pypi.org/simple" }
940
940
  dependencies = [
941
941
  { name = "livekit-agents" },
942
942
  { name = "spatius", extra = ["opus"] },
943
943
  ]
944
- sdist = { url = "https://files.pythonhosted.org/packages/c0/40/ce00a90cdfd3fd0840ef452e8706e88ae5da9ab2c2cc900a615bfae7abcf/livekit_plugins_spatius-1.8.0.tar.gz", hash = "sha256:76d80c79db4673da4c1ce84f51fbec986a0bd7b888226d8fced68992c7d798e8", size = 8601, upload-time = "2026-09-05T00:12:14.914Z" }
944
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/a7/96d690fa9177fb29ae162fdd3957eee2c87ff3c0b9c4e85956532f94a779/livekit_plugins_spatius-1.8.1.tar.gz", hash = "sha256:d71b74eea79a58861c5893d67d4196d4a3deb2836c23cf4316759aae368b4b9d", size = 10093, upload-time = "2026-09-10T19:01:10.334Z" }
945
945
  wheels = [
946
- { url = "https://files.pythonhosted.org/packages/4d/34/bd44feacb043d16e18396bb7bb9ccb8f100348efce5090d853f8a6bfff1d/livekit_plugins_spatius-1.8.0-py3-none-any.whl", hash = "sha256:c852c7c1ec26b094cc207fa127de1f65e305c6def43d626ee65a250339a1bb69", size = 8712, upload-time = "2026-09-05T00:12:13.838Z" },
946
+ { url = "https://files.pythonhosted.org/packages/f7/93/b562e8e3ff22b19f69575302b41649bcde379497b73b2fc003f76553a8c4/livekit_plugins_spatius-1.8.1-py3-none-any.whl", hash = "sha256:234d2d0817e43a0aa736c2bfc698b364f6ad64c2ba55c469bab35169099f928e", size = 11330, upload-time = "2026-09-10T19:01:09.016Z" },
947
947
  ]
948
948
 
949
949
  [[package]]
@@ -1891,7 +1891,7 @@ dev = [
1891
1891
 
1892
1892
  [package.metadata]
1893
1893
  requires-dist = [
1894
- { name = "livekit-agents", extras = ["spatius"], specifier = "~=1.8.0" },
1894
+ { name = "livekit-agents", extras = ["spatius"], specifier = "~=1.8.1" },
1895
1895
  { name = "livekit-plugins-noise-cancellation", specifier = "~=0.3.0" },
1896
1896
  { name = "python-dotenv", specifier = ">=1.2.1,<2" },
1897
1897
  { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.15,<1" },
@@ -8,6 +8,7 @@ playwright-report/
8
8
 
9
9
  # Cloudflare
10
10
  .wrangler/
11
+ worker-configuration.d.ts
11
12
  .dev.vars
12
13
  .dev.vars.*
13
14
  !.dev.vars.example