pipecat-spatialreal 0.1.0__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.
- pipecat_spatialreal-0.1.0/.github/workflows/ci.yml +102 -0
- pipecat_spatialreal-0.1.0/.github/workflows/compat.yml +130 -0
- pipecat_spatialreal-0.1.0/.github/workflows/protobuf-codegen.yml +202 -0
- pipecat_spatialreal-0.1.0/.github/workflows/release.yml +75 -0
- pipecat_spatialreal-0.1.0/.gitignore +19 -0
- pipecat_spatialreal-0.1.0/.python-version +1 -0
- pipecat_spatialreal-0.1.0/CHANGELOG.md +25 -0
- pipecat_spatialreal-0.1.0/LICENSE +202 -0
- pipecat_spatialreal-0.1.0/PKG-INFO +322 -0
- pipecat_spatialreal-0.1.0/README.md +283 -0
- pipecat_spatialreal-0.1.0/env.example +36 -0
- pipecat_spatialreal-0.1.0/examples/README.md +74 -0
- pipecat_spatialreal-0.1.0/examples/bot.py +413 -0
- pipecat_spatialreal-0.1.0/examples/client/README.md +73 -0
- pipecat_spatialreal-0.1.0/examples/client/index.html +199 -0
- pipecat_spatialreal-0.1.0/examples/probe/README.md +500 -0
- pipecat_spatialreal-0.1.0/examples/probe/capture.jsonl +551 -0
- pipecat_spatialreal-0.1.0/examples/probe/capture.wav +0 -0
- pipecat_spatialreal-0.1.0/examples/probe/index.html +999 -0
- pipecat_spatialreal-0.1.0/examples/probe/sync.js +1333 -0
- pipecat_spatialreal-0.1.0/pyproject.toml +108 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/__init__.py +6 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/driven.py +387 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/envelope.py +257 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/fingerprint.py +117 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/onset.py +118 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/processor.py +1379 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/proto/__init__.py +1 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/proto/generated/__init__.py +1 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/proto/generated/avatar_pb2.py +53 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/proto/generated/avatar_pb2.pyi +118 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/py.typed +0 -0
- pipecat_spatialreal-0.1.0/src/pipecat_spatialreal/version.py +3 -0
- pipecat_spatialreal-0.1.0/tests/client_page_check.mjs +366 -0
- pipecat_spatialreal-0.1.0/tests/conftest.py +289 -0
- pipecat_spatialreal-0.1.0/tests/example_loader.py +54 -0
- pipecat_spatialreal-0.1.0/tests/fake_driveningress.py +308 -0
- pipecat_spatialreal-0.1.0/tests/fixtures/sync/make_oracle_fixture.py +339 -0
- pipecat_spatialreal-0.1.0/tests/fixtures/sync/make_stall_fixture.py +387 -0
- pipecat_spatialreal-0.1.0/tests/fixtures/sync/oracle.json +1 -0
- pipecat_spatialreal-0.1.0/tests/fixtures/sync/stall.json +1 -0
- pipecat_spatialreal-0.1.0/tests/probe_check.mjs +1356 -0
- pipecat_spatialreal-0.1.0/tests/test_client_page.py +36 -0
- pipecat_spatialreal-0.1.0/tests/test_docstrings.py +256 -0
- pipecat_spatialreal-0.1.0/tests/test_driven.py +632 -0
- pipecat_spatialreal-0.1.0/tests/test_envelope.py +242 -0
- pipecat_spatialreal-0.1.0/tests/test_example_bot.py +336 -0
- pipecat_spatialreal-0.1.0/tests/test_example_token_route.py +405 -0
- pipecat_spatialreal-0.1.0/tests/test_fake_driveningress.py +223 -0
- pipecat_spatialreal-0.1.0/tests/test_fingerprint.py +196 -0
- pipecat_spatialreal-0.1.0/tests/test_fingerprint_oracle.py +243 -0
- pipecat_spatialreal-0.1.0/tests/test_onset.py +199 -0
- pipecat_spatialreal-0.1.0/tests/test_output_transport.py +160 -0
- pipecat_spatialreal-0.1.0/tests/test_package.py +9 -0
- pipecat_spatialreal-0.1.0/tests/test_play.py +30 -0
- pipecat_spatialreal-0.1.0/tests/test_probe_page.py +222 -0
- pipecat_spatialreal-0.1.0/tests/test_processor.py +3231 -0
- pipecat_spatialreal-0.1.0/tests/test_readme.py +22 -0
- pipecat_spatialreal-0.1.0/tests/test_rtvi_smoke.py +330 -0
- pipecat_spatialreal-0.1.0/tests/test_stall_rule.py +801 -0
- pipecat_spatialreal-0.1.0/tests/test_sync_replay.py +515 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
# A run takes about two minutes; a hung one is cut off here instead of at GitHub's six hours.
|
|
16
|
+
timeout-minutes: 15
|
|
17
|
+
strategy:
|
|
18
|
+
fail-fast: false
|
|
19
|
+
matrix:
|
|
20
|
+
# 3.11 is the floor (pipecat-ai >= 1.0 requires it); 3.12 is what the codegen workflow runs.
|
|
21
|
+
python-version: ["3.11", "3.12"]
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v5
|
|
24
|
+
|
|
25
|
+
- uses: astral-sh/setup-uv@v4
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
|
|
29
|
+
# tests/probe_check.mjs runs the probe page's analysis under Node, so the test that calls it
|
|
30
|
+
# needs one; the runner image's own Node is not something to count on.
|
|
31
|
+
- uses: actions/setup-node@v7
|
|
32
|
+
with:
|
|
33
|
+
node-version: lts/*
|
|
34
|
+
|
|
35
|
+
# The example extra brings FastAPI and the Pipecat runner, which the token-route tests of
|
|
36
|
+
# examples/bot.py import: without it they are skipped.
|
|
37
|
+
- name: Install (dev and example extras)
|
|
38
|
+
run: uv sync --extra dev --extra example
|
|
39
|
+
|
|
40
|
+
- name: Lint
|
|
41
|
+
run: uv run ruff check .
|
|
42
|
+
|
|
43
|
+
- name: Format check
|
|
44
|
+
run: uv run ruff format --check .
|
|
45
|
+
|
|
46
|
+
- name: Test
|
|
47
|
+
run: uv run pytest
|
|
48
|
+
|
|
49
|
+
- name: Build
|
|
50
|
+
run: uv build
|
|
51
|
+
|
|
52
|
+
# The test job resolves the newest versions on every run. This one resolves the lowest that
|
|
53
|
+
# pyproject.toml declares for every direct dependency, so that the floors users can install
|
|
54
|
+
# (pipecat-ai, spatialreal and protobuf above all) stay tested once newer releases come out.
|
|
55
|
+
floors:
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
timeout-minutes: 15
|
|
58
|
+
env:
|
|
59
|
+
# For every uv command of the job: `uv run` locks and syncs again before it runs, and with
|
|
60
|
+
# the default resolution it would bring the newest versions back.
|
|
61
|
+
UV_RESOLUTION: lowest-direct
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v5
|
|
64
|
+
|
|
65
|
+
- uses: astral-sh/setup-uv@v4
|
|
66
|
+
with:
|
|
67
|
+
python-version: "3.11"
|
|
68
|
+
|
|
69
|
+
- uses: actions/setup-node@v7
|
|
70
|
+
with:
|
|
71
|
+
node-version: lts/*
|
|
72
|
+
|
|
73
|
+
- name: Install the lowest declared versions (dev and example extras)
|
|
74
|
+
run: uv sync --resolution lowest-direct --extra dev --extra example
|
|
75
|
+
|
|
76
|
+
# The resolver moves past a floor that cannot be installed (one that conflicts with another
|
|
77
|
+
# requirement, say) without a word. Fail instead, so that such a floor is raised on purpose.
|
|
78
|
+
- name: Check that every runtime floor is what got installed
|
|
79
|
+
run: |
|
|
80
|
+
uv run python - <<'EOF'
|
|
81
|
+
import importlib.metadata
|
|
82
|
+
import tomllib
|
|
83
|
+
|
|
84
|
+
from packaging.requirements import Requirement
|
|
85
|
+
from packaging.version import Version
|
|
86
|
+
|
|
87
|
+
with open("pyproject.toml", "rb") as file:
|
|
88
|
+
dependencies = tomllib.load(file)["project"]["dependencies"]
|
|
89
|
+
wrong = []
|
|
90
|
+
for line in dependencies:
|
|
91
|
+
requirement = Requirement(line)
|
|
92
|
+
floors = [spec.version for spec in requirement.specifier if spec.operator == ">="]
|
|
93
|
+
installed = importlib.metadata.version(requirement.name)
|
|
94
|
+
print(f"{requirement.name}: declared {requirement.specifier}, installed {installed}")
|
|
95
|
+
if floors and Version(installed) != Version(floors[0]):
|
|
96
|
+
wrong.append(requirement.name)
|
|
97
|
+
if wrong:
|
|
98
|
+
raise SystemExit(f"Not installed at the declared floor: {', '.join(wrong)}")
|
|
99
|
+
EOF
|
|
100
|
+
|
|
101
|
+
- name: Test
|
|
102
|
+
run: uv run pytest
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
name: Pipecat compatibility
|
|
2
|
+
|
|
3
|
+
# Once a week, runs the tests against the newest pipecat-ai that pyproject.toml allows, to learn
|
|
4
|
+
# early when a Pipecat release breaks this package.
|
|
5
|
+
#
|
|
6
|
+
# When the tests fail, the job fails and the run is the report: nothing is changed. When they pass
|
|
7
|
+
# on a release newer than the one the README says the package was tested with ("Tested with Pipecat
|
|
8
|
+
# vX.Y.Z"), a second job opens a pull request that moves that line.
|
|
9
|
+
#
|
|
10
|
+
# The two jobs are kept apart because the first runs freshly resolved third-party code (the newest
|
|
11
|
+
# Pipecat and whatever it pulls in). It holds a read-only token and nothing else; its checkout does
|
|
12
|
+
# not keep even that. The second holds the write token and runs no dependency code: it checks out
|
|
13
|
+
# the repository without keeping the token, edits one line of the README itself, and the pull
|
|
14
|
+
# request can carry README.md and nothing else.
|
|
15
|
+
#
|
|
16
|
+
# The pull request is opened with the workflow's own token, so GitHub starts no other workflow on
|
|
17
|
+
# it. The repository setting "Allow GitHub Actions to create and approve pull requests" must be on.
|
|
18
|
+
#
|
|
19
|
+
# workflow_dispatch only finds workflows that are on the default branch.
|
|
20
|
+
|
|
21
|
+
on:
|
|
22
|
+
schedule:
|
|
23
|
+
- cron: "23 6 * * 1" # Mondays, 06:23 UTC
|
|
24
|
+
workflow_dispatch:
|
|
25
|
+
|
|
26
|
+
permissions:
|
|
27
|
+
contents: read
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
check:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
# A run takes a few minutes; a hung one is cut off here instead of at GitHub's six hours.
|
|
33
|
+
timeout-minutes: 15
|
|
34
|
+
# The job's token, which setup-uv also receives by default (its github-token input), can only
|
|
35
|
+
# read.
|
|
36
|
+
permissions:
|
|
37
|
+
contents: read
|
|
38
|
+
outputs:
|
|
39
|
+
installed: ${{ steps.version.outputs.installed }}
|
|
40
|
+
bump: ${{ steps.version.outputs.bump }}
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v5
|
|
43
|
+
with:
|
|
44
|
+
persist-credentials: false
|
|
45
|
+
|
|
46
|
+
- uses: astral-sh/setup-uv@v4
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.12"
|
|
49
|
+
|
|
50
|
+
# tests/probe_check.mjs runs the probe page's analysis under Node.
|
|
51
|
+
- uses: actions/setup-node@v7
|
|
52
|
+
with:
|
|
53
|
+
node-version: lts/*
|
|
54
|
+
|
|
55
|
+
# Libraries resolve fresh here (uv.lock is not committed), and --upgrade-package makes the
|
|
56
|
+
# newest pipecat-ai win even when a lock file has been restored from somewhere. Resolving
|
|
57
|
+
# again, rather than upgrading the installed package alone, keeps the dependencies of the
|
|
58
|
+
# extras in step with the new release.
|
|
59
|
+
- name: Install the newest Pipecat that pyproject.toml allows
|
|
60
|
+
run: uv sync --extra dev --extra example --upgrade-package pipecat-ai
|
|
61
|
+
|
|
62
|
+
# The suite includes tests/test_rtvi_smoke.py, which sends the avatar messages through RTVI
|
|
63
|
+
# and Pipecat's eval transport.
|
|
64
|
+
- name: Test
|
|
65
|
+
run: uv run pytest -q
|
|
66
|
+
|
|
67
|
+
- name: Compare with the README
|
|
68
|
+
id: version
|
|
69
|
+
run: |
|
|
70
|
+
installed="$(uv run python -c 'import importlib.metadata as m; print(m.version("pipecat-ai"))')"
|
|
71
|
+
stated="$(grep -oE 'Tested with Pipecat v[0-9]+(\.[0-9]+)*' README.md | head -n 1 | sed 's/^.* v//')"
|
|
72
|
+
if [ -z "$stated" ]; then
|
|
73
|
+
echo "README.md has no 'Tested with Pipecat vX.Y.Z' line" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
echo "The README says v${stated}; the tests passed on v${installed}."
|
|
77
|
+
newest="$(printf '%s\n%s\n' "$stated" "$installed" | sort -V | tail -n 1)"
|
|
78
|
+
{
|
|
79
|
+
echo "installed=${installed}"
|
|
80
|
+
if [ "$installed" != "$stated" ] && [ "$newest" = "$installed" ]; then
|
|
81
|
+
echo "bump=true"
|
|
82
|
+
else
|
|
83
|
+
echo "bump=false"
|
|
84
|
+
fi
|
|
85
|
+
} >> "$GITHUB_OUTPUT"
|
|
86
|
+
|
|
87
|
+
pr:
|
|
88
|
+
needs: check
|
|
89
|
+
if: needs.check.outputs.bump == 'true'
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
timeout-minutes: 10
|
|
92
|
+
permissions:
|
|
93
|
+
contents: write
|
|
94
|
+
pull-requests: write
|
|
95
|
+
steps:
|
|
96
|
+
- uses: actions/checkout@v5
|
|
97
|
+
with:
|
|
98
|
+
persist-credentials: false
|
|
99
|
+
|
|
100
|
+
# The version comes from the job that ran third-party code, so it is checked before it is
|
|
101
|
+
# used anywhere, and it goes through env, not into the script text.
|
|
102
|
+
- name: Move the README to the tested version
|
|
103
|
+
env:
|
|
104
|
+
INSTALLED: ${{ needs.check.outputs.installed }}
|
|
105
|
+
run: |
|
|
106
|
+
if ! [[ "$INSTALLED" =~ ^[0-9]+(\.[0-9]+)*$ ]]; then
|
|
107
|
+
echo "Not a release version: '${INSTALLED}'" >&2
|
|
108
|
+
exit 1
|
|
109
|
+
fi
|
|
110
|
+
sed -E "s/Tested with Pipecat v[0-9]+(\.[0-9]+)*/Tested with Pipecat v${INSTALLED}/" README.md > README.md.new
|
|
111
|
+
mv README.md.new README.md
|
|
112
|
+
git diff --stat
|
|
113
|
+
|
|
114
|
+
- name: Open a pull request
|
|
115
|
+
uses: peter-evans/create-pull-request@v7
|
|
116
|
+
with:
|
|
117
|
+
token: ${{ github.token }}
|
|
118
|
+
# Only the README goes into the pull request, whatever else is in the working tree.
|
|
119
|
+
add-paths: README.md
|
|
120
|
+
branch: compat/pipecat-${{ needs.check.outputs.installed }}
|
|
121
|
+
delete-branch: true
|
|
122
|
+
commit-message: "docs: tested with Pipecat v${{ needs.check.outputs.installed }}"
|
|
123
|
+
title: "docs: tested with Pipecat v${{ needs.check.outputs.installed }}"
|
|
124
|
+
body: |
|
|
125
|
+
The weekly compatibility check ran the tests against pipecat-ai
|
|
126
|
+
${{ needs.check.outputs.installed }} and they passed, so the README now says the
|
|
127
|
+
package was tested with it.
|
|
128
|
+
|
|
129
|
+
Nothing else changed. The check does not raise the `pipecat-ai` floor in
|
|
130
|
+
`pyproject.toml`.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
name: Protobuf Codegen
|
|
2
|
+
|
|
3
|
+
# Regenerates src/pipecat_spatialreal/proto/generated/ and commits it once the gates pass.
|
|
4
|
+
#
|
|
5
|
+
# Trigger: shared-proto's tag CI dispatches this workflow; it can also be run by hand here.
|
|
6
|
+
# Pushes and PRs do not trigger it: the contract is released only when shared-proto is tagged.
|
|
7
|
+
# workflow_dispatch only finds workflows that are on the default branch.
|
|
8
|
+
#
|
|
9
|
+
# Auth: the org GitHub App `spatialreal-codegen` (secrets CODEGEN_APP_ID /
|
|
10
|
+
# CODEGEN_APP_PRIVATE_KEY), not a personal token. The App installation must include this repo and
|
|
11
|
+
# shared-proto.
|
|
12
|
+
#
|
|
13
|
+
# The App can reach every repository of its installation, so neither its key nor a token of its own
|
|
14
|
+
# is ever in a job that runs third-party code. A job is the unit that matters: GitHub gives a job
|
|
15
|
+
# every secret it names when the job starts. Three jobs, in order:
|
|
16
|
+
#
|
|
17
|
+
# contract reads the contract from shared-proto with a token that can only read that repository,
|
|
18
|
+
# and hands pipecat/v1/avatar.proto on. It runs GitHub's own actions and git, nothing
|
|
19
|
+
# else.
|
|
20
|
+
# generate installs the dev extra (freshly resolved third-party packages), generates the code and
|
|
21
|
+
# runs the gates. It names no secret, holds a read-only token and keeps none of it in
|
|
22
|
+
# the checkout, and hands the two generated files on.
|
|
23
|
+
# publish takes exactly those two files, and only then mints a token that can only write this
|
|
24
|
+
# repository, used by the push alone. It runs no dependency code.
|
|
25
|
+
|
|
26
|
+
on:
|
|
27
|
+
workflow_dispatch:
|
|
28
|
+
inputs:
|
|
29
|
+
shared_proto_ref:
|
|
30
|
+
description: shared-proto ref (tag / branch / sha)
|
|
31
|
+
type: string
|
|
32
|
+
required: false
|
|
33
|
+
default: main
|
|
34
|
+
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
|
|
38
|
+
jobs:
|
|
39
|
+
contract:
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
timeout-minutes: 10
|
|
42
|
+
outputs:
|
|
43
|
+
short_sha: ${{ steps.ref.outputs.short_sha }}
|
|
44
|
+
steps:
|
|
45
|
+
- name: Mint a token that can only read shared-proto
|
|
46
|
+
id: app-token
|
|
47
|
+
uses: actions/create-github-app-token@v3
|
|
48
|
+
with:
|
|
49
|
+
app-id: ${{ secrets.CODEGEN_APP_ID }}
|
|
50
|
+
private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}
|
|
51
|
+
owner: ${{ github.repository_owner }}
|
|
52
|
+
repositories: shared-proto
|
|
53
|
+
permission-contents: read
|
|
54
|
+
|
|
55
|
+
- name: Checkout shared-proto
|
|
56
|
+
uses: actions/checkout@v5
|
|
57
|
+
with:
|
|
58
|
+
repository: SpatialReal-ai/shared-proto
|
|
59
|
+
ref: ${{ inputs.shared_proto_ref || 'main' }}
|
|
60
|
+
token: ${{ steps.app-token.outputs.token }}
|
|
61
|
+
persist-credentials: false
|
|
62
|
+
path: proto-src
|
|
63
|
+
|
|
64
|
+
- name: Record the commit
|
|
65
|
+
id: ref
|
|
66
|
+
run: echo "short_sha=$(git -C proto-src rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
|
67
|
+
|
|
68
|
+
# Only the file this package is generated from: the rest of shared-proto stays out of the job
|
|
69
|
+
# that runs third-party code. Should avatar.proto ever import another file, protoc names it
|
|
70
|
+
# and it is added here.
|
|
71
|
+
- uses: actions/upload-artifact@v7
|
|
72
|
+
with:
|
|
73
|
+
name: contract
|
|
74
|
+
path: proto-src/pipecat/v1/avatar.proto
|
|
75
|
+
if-no-files-found: error
|
|
76
|
+
retention-days: 1
|
|
77
|
+
|
|
78
|
+
generate:
|
|
79
|
+
needs: contract
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
# A run takes a few minutes; a hung one is cut off here instead of at GitHub's six hours.
|
|
82
|
+
timeout-minutes: 15
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v5
|
|
85
|
+
with:
|
|
86
|
+
persist-credentials: false
|
|
87
|
+
|
|
88
|
+
- uses: actions/download-artifact@v8
|
|
89
|
+
with:
|
|
90
|
+
name: contract
|
|
91
|
+
path: proto-src/pipecat/v1
|
|
92
|
+
|
|
93
|
+
# No pip cache: what a third-party package left in it would come back in later runs.
|
|
94
|
+
- uses: actions/setup-python@v5
|
|
95
|
+
with:
|
|
96
|
+
python-version: "3.12"
|
|
97
|
+
|
|
98
|
+
# tests/probe_check.mjs runs the probe page's analysis under Node.
|
|
99
|
+
- uses: actions/setup-node@v7
|
|
100
|
+
with:
|
|
101
|
+
node-version: lts/*
|
|
102
|
+
|
|
103
|
+
# grpcio-tools is pinned in the dev extra: it decides the gencode protobuf version, which
|
|
104
|
+
# must stay <= the `protobuf>=` floor in pyproject.toml, or an older runtime refuses to
|
|
105
|
+
# import the generated code. Bump the pin together with that floor.
|
|
106
|
+
- name: Install (dev extra)
|
|
107
|
+
run: python -m pip install -e ".[dev]"
|
|
108
|
+
|
|
109
|
+
- name: Generate
|
|
110
|
+
run: |
|
|
111
|
+
out="$(mktemp -d)"
|
|
112
|
+
gen=src/pipecat_spatialreal/proto/generated
|
|
113
|
+
python -m grpc_tools.protoc -I proto-src \
|
|
114
|
+
--python_out="$out" --pyi_out="$out" pipecat/v1/avatar.proto
|
|
115
|
+
rm -f "$gen/avatar_pb2.py" "$gen/avatar_pb2.pyi"
|
|
116
|
+
mv "$out/pipecat/v1/avatar_pb2.py" "$out/pipecat/v1/avatar_pb2.pyi" "$gen/"
|
|
117
|
+
rm -rf proto-src "$out"
|
|
118
|
+
git status --short "$gen"
|
|
119
|
+
|
|
120
|
+
# Gate: the package still imports (the generated module loads against the installed protobuf
|
|
121
|
+
# runtime) and the tests pass. A failure usually means plugin code relies on something the
|
|
122
|
+
# generated code no longer has — fix that first, then re-run.
|
|
123
|
+
- name: Gate — import smoke test
|
|
124
|
+
run: python -c "import pipecat_spatialreal"
|
|
125
|
+
|
|
126
|
+
- name: Gate — unit tests
|
|
127
|
+
run: python -m pytest tests -q
|
|
128
|
+
|
|
129
|
+
# The contract lives in shared-proto; this repo holds generated code only.
|
|
130
|
+
- name: Gate — generated code only
|
|
131
|
+
run: |
|
|
132
|
+
if git ls-files --others --cached --exclude-standard '*.proto' | grep .; then
|
|
133
|
+
echo "Only generated code is committed here." >&2
|
|
134
|
+
exit 1
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
- uses: actions/upload-artifact@v7
|
|
138
|
+
with:
|
|
139
|
+
name: generated
|
|
140
|
+
path: |
|
|
141
|
+
src/pipecat_spatialreal/proto/generated/avatar_pb2.py
|
|
142
|
+
src/pipecat_spatialreal/proto/generated/avatar_pb2.pyi
|
|
143
|
+
if-no-files-found: error
|
|
144
|
+
retention-days: 1
|
|
145
|
+
|
|
146
|
+
publish:
|
|
147
|
+
needs: [contract, generate]
|
|
148
|
+
runs-on: ubuntu-latest
|
|
149
|
+
timeout-minutes: 10
|
|
150
|
+
steps:
|
|
151
|
+
- uses: actions/checkout@v5
|
|
152
|
+
with:
|
|
153
|
+
persist-credentials: false
|
|
154
|
+
|
|
155
|
+
- uses: actions/download-artifact@v8
|
|
156
|
+
with:
|
|
157
|
+
name: generated
|
|
158
|
+
path: ${{ runner.temp }}/generated
|
|
159
|
+
|
|
160
|
+
# The two generated files by name, whatever else the artifact might hold.
|
|
161
|
+
- name: Take the generated code
|
|
162
|
+
id: take
|
|
163
|
+
run: |
|
|
164
|
+
gen=src/pipecat_spatialreal/proto/generated
|
|
165
|
+
cp "$RUNNER_TEMP/generated/avatar_pb2.py" "$RUNNER_TEMP/generated/avatar_pb2.pyi" "$gen/"
|
|
166
|
+
git status --short
|
|
167
|
+
if git diff --quiet; then
|
|
168
|
+
echo "No changes."
|
|
169
|
+
echo "changed=false" >> "$GITHUB_OUTPUT"
|
|
170
|
+
else
|
|
171
|
+
echo "changed=true" >> "$GITHUB_OUTPUT"
|
|
172
|
+
fi
|
|
173
|
+
|
|
174
|
+
# Minted last, and with no owner given, for this repository alone.
|
|
175
|
+
- name: Mint a token that can only write this repository
|
|
176
|
+
if: steps.take.outputs.changed == 'true'
|
|
177
|
+
id: app-token
|
|
178
|
+
uses: actions/create-github-app-token@v3
|
|
179
|
+
with:
|
|
180
|
+
app-id: ${{ secrets.CODEGEN_APP_ID }}
|
|
181
|
+
private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }}
|
|
182
|
+
permission-contents: write
|
|
183
|
+
|
|
184
|
+
# The ref goes through env, not into the script text: a tag name is not trusted input. The
|
|
185
|
+
# token reaches git through a credential helper for this one push: it is never written to the
|
|
186
|
+
# git config and never appears on a command line.
|
|
187
|
+
- name: Commit and push
|
|
188
|
+
if: steps.take.outputs.changed == 'true'
|
|
189
|
+
env:
|
|
190
|
+
SHARED_PROTO_REF: ${{ inputs.shared_proto_ref || 'main' }}
|
|
191
|
+
SHORT_SHA: ${{ needs.contract.outputs.short_sha }}
|
|
192
|
+
PUSH_TOKEN: ${{ steps.app-token.outputs.token }}
|
|
193
|
+
run: |
|
|
194
|
+
git config user.name "github-actions[bot]"
|
|
195
|
+
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
196
|
+
git add src/pipecat_spatialreal/proto/generated/avatar_pb2.py \
|
|
197
|
+
src/pipecat_spatialreal/proto/generated/avatar_pb2.pyi
|
|
198
|
+
git commit -m "chore(proto): regenerate from shared-proto@${SHARED_PROTO_REF} (${SHORT_SHA})"
|
|
199
|
+
# shellcheck disable=SC2016 # the helper's shell expands $1 and $PUSH_TOKEN, not this one
|
|
200
|
+
git -c credential.helper= \
|
|
201
|
+
-c 'credential.helper=!f() { test "$1" = get && printf "username=x-access-token\npassword=%s\n" "$PUSH_TOKEN"; }; f' \
|
|
202
|
+
push
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing a GitHub Release triggers this; pushing the tag alone does not. `published` is the
|
|
4
|
+
# type that fires for a release made in the GitHub UI (`created` is skipped for a draft that is
|
|
5
|
+
# published later). Publishing uses PyPI trusted publishing (OIDC), so there is no API token:
|
|
6
|
+
# PyPI holds a publisher for SpatialReal-ai/pipecat-spatialreal, workflow `release.yml`,
|
|
7
|
+
# environment `pypi`. Those three must stay as they are, or the upload is refused.
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
release:
|
|
11
|
+
types: [published]
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
build:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 15
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v5
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v4
|
|
24
|
+
with:
|
|
25
|
+
python-version: "3.11"
|
|
26
|
+
|
|
27
|
+
- name: Build sdist and wheel
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
# The release tag names the version PyPI will show forever; refuse a tag that does not
|
|
31
|
+
# match the version the package was built with (src/pipecat_spatialreal/version.py).
|
|
32
|
+
- name: Check the release tag against the built version
|
|
33
|
+
env:
|
|
34
|
+
TAG: ${{ github.event.release.tag_name }}
|
|
35
|
+
run: |
|
|
36
|
+
for whl in dist/*.whl; do
|
|
37
|
+
built=$(basename "$whl" | sed -E 's/^pipecat_spatialreal-([^-]+)-.*/\1/')
|
|
38
|
+
done
|
|
39
|
+
echo "tag=$TAG built=$built"
|
|
40
|
+
test "$TAG" = "v$built" || { echo "release tag $TAG does not match the built version $built"; exit 1; }
|
|
41
|
+
|
|
42
|
+
# The generated protobuf module is what a broken build drops silently, and the package
|
|
43
|
+
# imports it at load time. Install the built wheel, not the source tree, into a fresh
|
|
44
|
+
# environment and import it from outside the checkout.
|
|
45
|
+
- name: Smoke-test the built wheel in a fresh environment
|
|
46
|
+
run: |
|
|
47
|
+
uv venv "$RUNNER_TEMP/wheel-venv"
|
|
48
|
+
uv pip install --python "$RUNNER_TEMP/wheel-venv/bin/python" dist/*.whl
|
|
49
|
+
cd "$RUNNER_TEMP"
|
|
50
|
+
"$RUNNER_TEMP/wheel-venv/bin/python" -c "import pipecat_spatialreal, pipecat_spatialreal.proto.generated.avatar_pb2; print(pipecat_spatialreal.__file__)"
|
|
51
|
+
|
|
52
|
+
- uses: actions/upload-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: dist
|
|
55
|
+
path: dist/
|
|
56
|
+
if-no-files-found: error
|
|
57
|
+
|
|
58
|
+
publish:
|
|
59
|
+
needs: build
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
timeout-minutes: 10
|
|
62
|
+
environment: pypi
|
|
63
|
+
permissions:
|
|
64
|
+
# id-token for PyPI trusted publishing; contents because naming any permission drops the
|
|
65
|
+
# defaults for the rest.
|
|
66
|
+
id-token: write
|
|
67
|
+
contents: read
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/download-artifact@v4
|
|
70
|
+
with:
|
|
71
|
+
name: dist
|
|
72
|
+
path: dist/
|
|
73
|
+
|
|
74
|
+
- name: Publish to PyPI
|
|
75
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.egg-info/
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.DS_Store
|
|
9
|
+
.env
|
|
10
|
+
|
|
11
|
+
# Libraries resolve fresh in CI (see ci.yml) instead of pinning through a lock file.
|
|
12
|
+
uv.lock
|
|
13
|
+
|
|
14
|
+
# The contract lives in shared-proto; this repo holds generated code only. The codegen
|
|
15
|
+
# workflow refuses any committed .proto, this keeps one from being added by accident.
|
|
16
|
+
*.proto
|
|
17
|
+
|
|
18
|
+
# hack/sync-sim/thrsearch_stall.py's data cache, if one is pointed into the repo.
|
|
19
|
+
hack/sync-sim/*.pkl
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
6
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-11-09
|
|
9
|
+
|
|
10
|
+
First release. Tested with Pipecat v1.15.0, the SpatialReal Python SDK 0.3.0 and the SpatialReal
|
|
11
|
+
web SDK 1.0.0-beta.6, on Daily and LiveKit.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `SpatialRealAvatarProcessor`: place it right after the TTS service. It streams the bot's speech
|
|
16
|
+
to SpatialReal for facial animation, sends the animation to the browser as RTVI server messages,
|
|
17
|
+
and releases the audio in step with it. The SpatialReal web SDK (1.0.0-beta.6 or newer) renders
|
|
18
|
+
the avatar in the browser, so the pipeline carries no video.
|
|
19
|
+
- Audio and animation stay in sync: the web SDK lines the animation up with the audio the browser
|
|
20
|
+
actually receives.
|
|
21
|
+
- Interruptions stop the avatar together with the voice. If SpatialReal cannot be reached, the bot
|
|
22
|
+
keeps talking without animation and the processor reconnects.
|
|
23
|
+
- Works with Pipecat's Daily and LiveKit transports.
|
|
24
|
+
- An example bot (`examples/bot.py`) and web page (`examples/client`). The bot gives the browser a
|
|
25
|
+
short-lived session token, so the API key stays on the server.
|