chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:38 +08:00
commit 309eedff77
1201 changed files with 210635 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# .github/workflows/assign-reviewers.yml
name: Auto-assign reviewers
permissions:
contents: read
pull-requests: write
on:
pull_request:
types: [opened, reopened, ready_for_review]
branches: [main]
jobs:
assign:
if: github.event.pull_request.draft == false && github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
github-token: ${{ secrets.REVIEW_TOKEN }}
script: |
await github.rest.pulls.requestReviewers({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
team_reviewers: ['agent-devs']
});
+161
View File
@@ -0,0 +1,161 @@
name: Build package
permissions:
contents: read
actions: write
on:
workflow_call:
inputs:
package:
required: true
type: string
artifact_name:
required: true
type: string
workflow_dispatch:
inputs:
package:
description: "Name of the package to build"
required: true
default: "livekit-agents"
artifact_name:
description: "Artifact name for the distribution package"
required: true
default: "build-artifact"
jobs:
build_plugins:
runs-on: ubuntu-latest
if: inputs.package != 'livekit-blockguard' && inputs.package != 'livekit-durable'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
submodules: true
lfs: true
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Build package
run: uv build --package ${{inputs.package}}
- name: Upload distribution package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact_name }}
path: dist/
build_durable:
if: inputs.package == 'livekit-durable'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
archs: x86_64
- os: namespace-profile-default-arm64
archs: aarch64
- os: windows-latest
archs: AMD64
- os: macos-latest
archs: x86_64 arm64
defaults:
run:
working-directory: livekit-plugins/livekit-durable
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install cibuildwheel
run: |
python -m pip install --upgrade pip
pip install cibuildwheel
- name: Build wheels
run: cibuildwheel --output-dir dist
env:
CIBW_BUILD_VERBOSITY: 3
CIBW_ARCHS: ${{ matrix.archs }}
- name: Upload distribution package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact_name }}-${{ matrix.os }}
path: livekit-plugins/livekit-durable/dist/
build_blockguard:
if: inputs.package == 'livekit-blockguard'
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-latest
archs: x86_64
- os: namespace-profile-default-arm64
archs: aarch64
- os: windows-latest
archs: AMD64
- os: macos-latest
archs: x86_64 arm64
defaults:
run:
working-directory: livekit-plugins/livekit-blockguard
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.11"
- name: Install cibuildwheel
run: |
python -m pip install --upgrade pip
pip install cibuildwheel
- name: Build wheels
run: cibuildwheel --output-dir dist
env:
CIBW_BUILD_VERBOSITY: 3
CIBW_ARCHS: ${{ matrix.archs }}
- name: Upload distribution package
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact_name }}-${{ matrix.os }}
path: livekit-plugins/livekit-blockguard/dist/
merge_artifacts:
if: ${{ always() && (inputs.package == 'livekit-blockguard' || inputs.package == 'livekit-durable') }}
runs-on: ubuntu-latest
needs: [build_blockguard, build_durable]
steps:
- name: Download all platform wheels
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: ${{ inputs.artifact_name }}-*
path: all_dists
merge-multiple: true
- run: ls -R all_dists
- name: Upload unified wheel artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ${{ inputs.artifact_name }}
path: all_dists/
+78
View File
@@ -0,0 +1,78 @@
name: CI
on:
push:
branches: [main, 0.x]
paths-ignore:
- '**/*.md'
pull_request:
paths-ignore:
- '**/*.md'
workflow_dispatch:
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"
- name: Install the project
run: uv sync --all-extras --dev
- name: Ruff
run: uv run ruff check --output-format=github .
continue-on-error: true
id: ruff-check
- name: Check format
run: uv run ruff format --check .
continue-on-error: true
id: ruff-format
- name: Report ruff errors
if: steps.ruff-check.outcome == 'failure' || steps.ruff-format.outcome == 'failure'
run: |
echo "::error::Ruff checks failed. Please run 'make fix' locally to automatically fix formatting and linting issues."
exit 1
type-check:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.13"]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ matrix.python-version }}
- name: Install the project
run: uv sync --all-extras --dev
- name: Cache mypy
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: .mypy_cache
key: mypy-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}-${{ github.run_id }}
restore-keys: |
mypy-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}-
mypy-${{ matrix.python-version }}-
- name: Check Types
run: uv run python scripts/check_types.py
+168
View File
@@ -0,0 +1,168 @@
name: Deploy Examples
on:
# Manual runs deploy from the branch picked in the Actions "Use workflow
# from" selector — no input needed.
workflow_dispatch:
# Called by publish.yml after a release to deploy the published commit.
workflow_call:
inputs:
ref:
description: "Commit/branch/tag to deploy. Defaults to the triggering ref."
type: string
required: false
permissions:
contents: read
jobs:
deploy:
name: Deploy ${{ matrix.example }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
example: [healthcare, survey, frontdesk, drive-thru, inference, avatar, hotel_receptionist]
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ inputs.ref || github.ref }}
# Fetch Git-LFS assets (e.g. examples/drive-thru/bg_noise.mp3) so the
# real binaries — not pointer files — get uploaded as the build context
# and copied into the image via `COPY . .`. This checkout authenticates
# with GITHUB_TOKEN, so its LFS fetch is allowed (unlike pip's anonymous
# git clone). Without this the agent crashes at runtime decoding a
# 131-byte pointer as audio.
lfs: true
- name: Install LiveKit CLI
run: |
curl -sSL https://get.livekit.io/cli | bash
lk --version
- name: Add LiveKit Cloud project
env:
LIVEKIT_URL: ${{ secrets.LIVEKIT_EXAMPLES_URL }}
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_EXAMPLES_API_KEY }}
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_EXAMPLES_API_SECRET }}
run: |
lk project add examples \
--url "$LIVEKIT_URL" \
--api-key "$LIVEKIT_API_KEY" \
--api-secret "$LIVEKIT_API_SECRET" \
--default
- name: Regenerate livekit.toml from playground.yaml
run: |
python3 -m pip install --quiet pyyaml
python3 <<'PY'
import yaml
from pathlib import Path
data = yaml.safe_load(Path("examples/playground.yaml").read_text())
subdomain = data["project"]["subdomain"]
slug = "${{ matrix.example }}"
entry = data["examples"][slug]
toml = (
"[project]\n"
f' subdomain = "{subdomain}"\n'
"\n"
"[agent]\n"
f' id = "{entry["agent_id"]}"\n'
)
Path(f"examples/{slug}/livekit.toml").write_text(toml)
print(f"wrote examples/{slug}/livekit.toml")
PY
- name: Pin livekit-* requirements to the deployed ref
working-directory: examples/${{ matrix.example }}
env:
# Build the agent against the code at the ref we're deploying,
# not the latest release on PyPI. Without this, a branch deploy
# would silently run against the published livekit-agents instead
# of the branch's own SDK changes. Mirror the checkout ref above,
# using ref_name so it's a plain branch/tag pip can resolve.
DEPLOY_REF: ${{ inputs.ref || github.ref_name }}
run: |
python3 <<'PY'
import os, re
from pathlib import Path
REF = os.environ["DEPLOY_REF"]
REPO = "git+https://github.com/livekit/agents.git"
REPO_ROOT = Path(os.environ["GITHUB_WORKSPACE"])
# The package name (and optional [extras]) at the start of a
# requirement, e.g. "livekit-agents[evals]>=1.5.7".
REQUIREMENT = re.compile(r"^(?P<name>[A-Za-z0-9._-]+)(?P<extras>\[.*\])?")
def monorepo_path(name):
"""Path of `name` within this repo, or None if it lives elsewhere.
We only pin packages that actually exist in the checkout. The
directory basename is the distribution name for every package
here (livekit-agents at the root, everything else under
livekit-plugins/). Anything not found (livekit rtc,
livekit-blingfire, livekit-local-inference, …) keeps its
PyPI pin.
"""
for rel in (name, f"livekit-plugins/{name}"):
if (REPO_ROOT / rel).is_dir():
return rel
return None
def pin_to_ref(line):
"""Repoint an in-repo requirement at the deployed git ref.
External requirements, comments and blanks pass through
untouched (the regex doesn't match a leading '#' or '').
"""
match = REQUIREMENT.match(line.strip())
path = monorepo_path(match["name"]) if match else None
if path is None:
return line
return f"{match['name']}{match['extras'] or ''} @ {REPO}@{REF}#subdirectory={path}"
requirements = Path("requirements.txt")
pinned = [pin_to_ref(line) for line in requirements.read_text().splitlines()]
requirements.write_text("\n".join(pinned) + "\n")
print(f"--- requirements.txt pinned to {REF} ---")
print(requirements.read_text())
PY
- name: Build secrets file
working-directory: examples/${{ matrix.example }}
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
LEMONSLICE_API_KEY: ${{ secrets.LEMONSLICE_API_KEY }}
run: |
: > .env.deploy
# Register the agent under a deterministic name so the playground
# can dispatch to it explicitly. Matches the slug in playground.yaml.
echo "LIVEKIT_AGENT_NAME=${{ matrix.example }}" >> .env.deploy
case "${{ matrix.example }}" in
healthcare)
keys="OPENAI_API_KEY"
;;
avatar)
keys="LEMONSLICE_API_KEY"
;;
*)
keys=""
;;
esac
for k in $keys; do
val="${!k}"
if [ -n "$val" ]; then
echo "${k}=${val}" >> .env.deploy
fi
done
- name: Deploy ${{ matrix.example }}
working-directory: examples/${{ matrix.example }}
run: |
args=()
if [ -s .env.deploy ]; then
args+=(--secrets-file .env.deploy)
fi
lk agent deploy "${args[@]}" .
+83
View File
@@ -0,0 +1,83 @@
name: Weekly Python Download Stats
on:
schedule:
- cron: "0 14 * * 1" # every Monday at 2pm UTC
workflow_dispatch:
permissions:
contents: read
jobs:
stats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Generate report
run: python .github/download_stats.py > stats.txt
- name: Post to Slack
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
CHANNEL: ${{ vars.DOWNLOAD_STATS_SLACK_CHANNEL }}
run: |
python3 << 'PYEOF'
import json, os, urllib.request
from datetime import datetime
token = os.environ["SLACK_BOT_TOKEN"]
channel = os.environ["CHANNEL"]
today = datetime.now().strftime("%Y-%m-%d")
def slack_api(method, payload):
req = urllib.request.Request(
f"https://slack.com/api/{method}",
data=json.dumps(payload).encode() if isinstance(payload, dict) else payload,
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
if isinstance(payload, dict) else {"Authorization": f"Bearer {token}"},
)
return json.loads(urllib.request.urlopen(req).read())
# 1. Post summary message
resp = slack_api("chat.postMessage", {
"channel": channel,
"text": f":python: *Weekly Python PyPI Download Stats* — {today}",
})
assert resp["ok"], f"chat.postMessage failed: {resp.get('error')}"
thread_ts = resp["ts"]
# 2. Get upload URL
file_size = os.path.getsize("stats.txt")
form_data = f"filename=pypi-stats-{today}.txt&length={file_size}".encode()
req = urllib.request.Request(
"https://slack.com/api/files.getUploadURLExternal",
data=form_data,
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/x-www-form-urlencoded",
},
)
resp = json.loads(urllib.request.urlopen(req).read())
assert resp["ok"], f"getUploadURLExternal failed: {resp.get('error')}"
upload_url = resp["upload_url"]
file_id = resp["file_id"]
# 3. Upload file content
import subprocess
subprocess.run(["curl", "-s", "-X", "POST", upload_url, "-F", "file=@stats.txt"], check=True)
# 4. Complete upload in thread
resp = slack_api("files.completeUploadExternal", {
"files": [{"id": file_id, "title": f"pypi-stats-{today}.txt"}],
"channel_id": channel,
"thread_ts": thread_ts,
"initial_comment": "Full report",
})
assert resp["ok"], f"completeUploadExternal failed: {resp.get('error')}"
print("Posted to Slack successfully")
PYEOF
+83
View File
@@ -0,0 +1,83 @@
name: evals
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
actions: write
jobs:
evals:
# don't run tests for PRs on forks
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
strategy:
fail-fast: false
matrix:
example: [frontdesk, drive-thru]
runs-on: ubuntu-latest
name: ${{ matrix.example }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install the project
run: uv sync --all-extras --dev
- name: Setup Google credentials
if: matrix.example == 'google'
shell: bash
run: |
printf '%s' '${{ secrets.GOOGLE_CREDENTIALS_JSON }}' > tests/google.json
- name: Run tests
shell: bash
env:
LIVEKIT_EVALS_VERBOSE: 1
PLUGIN: ${{ matrix.example }}
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ELEVEN_API_KEY: ${{ secrets.ELEVEN_API_KEY }}
CARTESIA_API_KEY: ${{ secrets.CARTESIA_API_KEY }}
AZURE_SPEECH_KEY: ${{ secrets.AZURE_SPEECH_KEY }}
AZURE_SPEECH_REGION: ${{ secrets.AZURE_SPEECH_REGION }} # nit: doesn't have to be secret
GOOGLE_CREDENTIALS_JSON: ${{ secrets.GOOGLE_CREDENTIALS_JSON }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
ASSEMBLYAI_API_KEY: ${{ secrets.ASSEMBLYAI_API_KEY }}
FAL_KEY: ${{ secrets.FAL_KEY }}
PLAYHT_API_KEY: ${{ secrets.PLAYHT_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
PLAYHT_USER_ID: ${{ secrets.PLAYHT_USER_ID }}
RIME_API_KEY: ${{ secrets.RIME_API_KEY }}
SPEECHMATICS_API_KEY: ${{ secrets.SPEECHMATICS_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS: tests/google.json
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
NEUPHONIC_API_KEY: ${{ secrets.NEUPHONIC_API_KEY }}
RESEMBLE_API_KEY: ${{ secrets.RESEMBLE_API_KEY }}
SPEECHIFY_API_KEY: ${{ secrets.SPEECHIFY_API_KEY }}
HUME_API_KEY: ${{ secrets.HUME_API_KEY }}
SPITCH_API_KEY: ${{ secrets.SPITCH_API_KEY }}
LMNT_API_KEY: ${{ secrets.LMNT_API_KEY }}
INWORLD_API_KEY: ${{ secrets.INWORLD_API_KEY }}
run: uv run pytest examples/${{matrix.example}}/test_agent.py -s
+57
View File
@@ -0,0 +1,57 @@
name: Publish docs
on:
workflow_dispatch:
workflow_call:
secrets:
DOCS_DEPLOY_AWS_ACCESS_KEY: {}
DOCS_DEPLOY_AWS_API_SECRET: {}
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
submodules: true
lfs: true
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Install package, plugins, and docs dependencies
run: |
uv sync --all-extras --group docs
- name: Build HTML Docs
run: |
uv run --active pdoc --skip-errors --html --output-dir=docs livekit
- name: Build Markdown Docs
run: |
uv run --active python .github/convert_html_docs.py docs/ docs-md -v --check-links
- name: S3 Upload
run: |
aws s3 sync --delete docs/ s3://livekit-docs/python
aws s3 sync --delete docs-md/ s3://livekit-docs/python-md
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOY_AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_AWS_API_SECRET }}
AWS_DEFAULT_REGION: "us-east-1"
- name: Expire cloudfront cache
run: |
aws cloudfront create-invalidation --distribution-id EJJ40KLJ3TRY9 --paths "/python/*" "/python-md/*"
env:
AWS_ACCESS_KEY_ID: ${{ secrets.DOCS_DEPLOY_AWS_ACCESS_KEY }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DOCS_DEPLOY_AWS_API_SECRET }}
AWS_DEFAULT_REGION: "us-east-1"
+291
View File
@@ -0,0 +1,291 @@
name: Publish to PyPI
on:
workflow_dispatch:
inputs:
version:
description: "What to publish"
type: choice
required: true
options:
- "patch (1.5.1 → 1.5.2)"
- "minor (1.5.1 → 1.6.0)"
- "major (1.5.1 → 2.0.0)"
- "patch-rc (1.5.1 → 1.5.2.rc1)"
- "minor-rc (1.5.1 → 1.6.0.rc1)"
- "major-rc (1.5.1 → 2.0.0.rc1)"
- "next-rc (.rc1 → .rc2)"
- "promote (1.6.0.rc2 → 1.6.0)"
- "republish (retry failed publish, no version bump)"
branch:
description: "Branch to publish from (default: main)"
type: string
required: false
default: "main"
pull_request:
types: [closed]
permissions:
contents: write
pull-requests: write
id-token: write
actions: write
jobs:
# ── Create a version bump PR ────────────────────────────────
bump:
name: Create release PR
if: |
github.event_name == 'workflow_dispatch'
&& !startsWith(inputs.version, 'republish')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ inputs.branch }}
- name: Guard non-main branches
env:
VERSION: ${{ inputs.version }}
BRANCH: ${{ inputs.branch }}
run: |
key=$(echo "$VERSION" | awk '{print $1}')
if [ "$BRANCH" != "main" ]; then
case "$key" in
*-rc|next-rc) ;; # allowed
*) echo "::error::Only RC releases are allowed from non-main branches (got '$key' on '$BRANCH')"; exit 1 ;;
esac
fi
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.10"
- name: Install dependencies
run: pip install click packaging pyyaml colorama
- name: Bump versions
env:
VERSION: ${{ inputs.version }}
run: |
key=$(echo "$VERSION" | awk '{print $1}')
case "$key" in
patch|minor|major)
python .github/update_versions.py --bump-type "$key"
;;
patch-rc|minor-rc|major-rc)
bump="${key%-rc}"
python .github/update_versions.py --bump-type "$bump"
python .github/update_versions.py --pre rc
;;
next-rc)
python .github/update_versions.py --pre rc
;;
promote)
python .github/update_versions.py --bump-type release
;;
esac
- name: Read new version
id: version
run: |
version=$(grep -m1 '__version__' livekit-agents/livekit/agents/version.py | sed 's/.*"\(.*\)".*/\1/')
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "New version: $version"
- name: Close existing release PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr list --state open --json number,headRefName \
--jq '.[] | select(.headRefName | startswith("release/v")) | .number' | while read -r pr; do
echo "Superseding release PR #$pr"
gh pr comment "$pr" --body "Superseded by a new release."
gh pr close "$pr" --delete-branch || true
done
- name: Create release PR
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
BASE_BRANCH: ${{ inputs.branch }}
run: |
branch="release/v${VERSION}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
git add -A
git commit -m "v${VERSION}"
git push --force origin "$branch"
gh pr create \
--base "$BASE_BRANCH" \
--head "$branch" \
--title "livekit-agents@${VERSION}" \
--body "Merging this PR will publish all packages as **${VERSION}** to PyPI."
# ── Discover, build, publish ────────────────────────────────
# Triggered by: merging a release PR OR republish dispatch
discover:
name: Discover packages
if: |
(github.event_name == 'pull_request'
&& github.event.pull_request.merged == true
&& startsWith(github.event.pull_request.head.ref, 'release/v'))
|| (github.event_name == 'workflow_dispatch'
&& startsWith(inputs.version, 'republish'))
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.list.outputs.packages }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ inputs.branch || github.event.pull_request.merge_commit_sha }}
- name: List publishable packages
id: list
run: |
packages=()
packages+=("livekit-agents")
for dir in livekit-plugins/livekit-plugins-*; do
if [ ! -d "$dir" ]; then continue; fi
name=""
if [ -f "$dir/pyproject.toml" ]; then
name=$(grep -m1 '^name\s*=\s*"' "$dir/pyproject.toml" | sed 's/.*"\(.*\)".*/\1/')
fi
if [ -z "$name" ] && [ -f "$dir/setup.py" ]; then
name=$(grep -m1 'name\s*=\s*"' "$dir/setup.py" | sed 's/.*"\(.*\)".*/\1/')
fi
if [ -z "$name" ]; then
name=$(basename "$dir")
fi
packages+=("$name")
done
json=$(printf '%s\n' "${packages[@]}" | jq -R . | jq -sc .)
echo "packages=$json" >> "$GITHUB_OUTPUT"
echo "Will publish: $json"
tag:
name: Tag release
needs: discover
if: |
github.event_name == 'pull_request'
&& github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
- name: Create git tag
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
# HEAD_REF is release/v1.5.2 → extract 1.5.2
version="${HEAD_REF#release/v}"
tag="livekit-agents@${version}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "$tag"
git push origin "$tag"
build:
name: Build ${{ matrix.package }}
needs: discover
strategy:
fail-fast: false
matrix:
package: ${{ fromJson(needs.discover.outputs.packages) }}
uses: ./.github/workflows/build.yml
with:
package: ${{ matrix.package }}
artifact_name: dist-${{ matrix.package }}
publish:
name: Publish ${{ matrix.package }}
needs: [discover, build]
if: always() && needs.discover.result == 'success'
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 10
matrix:
package: ${{ fromJson(needs.discover.outputs.packages) }}
environment: pypi
permissions:
id-token: write
steps:
- name: Download build artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: dist-${{ matrix.package }}
path: dist/
- name: List distributions
run: ls -la dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
docs:
name: Publish docs
needs: [publish]
if: always() && needs.publish.result == 'success'
uses: ./.github/workflows/publish-docs.yml
secrets: inherit
deploy-examples:
name: Deploy examples
needs: [publish]
if: always() && needs.publish.result == 'success'
uses: ./.github/workflows/deploy-examples.yml
with:
ref: ${{ inputs.branch || github.event.pull_request.merge_commit_sha }}
secrets: inherit
dispatch-downstream-bumps:
name: Bump livekit-agents in downstream repos
needs: [publish]
# Only fire for stable releases merged via a release/vX.Y.Z PR.
# RC head refs look like release/v1.5.13rc1 and won't match the regex below.
# Republish retries should use internal-actions' workflow_dispatch fallback.
if: |
always()
&& needs.publish.result == 'success'
&& github.event_name == 'pull_request'
&& github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Resolve stable version from head ref
id: version
env:
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
version="${HEAD_REF#release/v}"
if ! echo "$version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::notice::Head ref '$HEAD_REF' is not a stable release — skipping downstream bump."
exit 0
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Dispatch to internal-actions
if: steps.version.outputs.version != ''
env:
GH_TOKEN: ${{ secrets.SYNC_DISPATCH_TOKEN }}
TARGET_REPO: ${{ vars.TARGET_REPO }}
VERSION: ${{ steps.version.outputs.version }}
run: |
gh api -X POST "repos/${TARGET_REPO}/dispatches" \
-f event_type="livekit-agents-published" \
-f client_payload[version]="$VERSION" \
-f client_payload[source_repo]="${{ github.repository }}" \
-f client_payload[source_sha]="${{ github.event.pull_request.merge_commit_sha }}"
+41
View File
@@ -0,0 +1,41 @@
name: Release gate
on:
pull_request:
types: [opened, synchronize, reopened]
pull_request_review:
types: [submitted, dismissed]
permissions:
pull-requests: read
jobs:
release-gate:
name: Release gate
runs-on: ubuntu-latest
steps:
- name: Check release PR requirements
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
if [[ "$HEAD_REF" != release/v* ]]; then
echo "Not a release PR, skipping"
exit 0
fi
if [ "$PR_AUTHOR" != "github-actions[bot]" ]; then
echo "::error::Release PRs must be created by the publish workflow, not by '$PR_AUTHOR'"
exit 1
fi
approvals=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \
--jq '[group_by(.user.login)[] | sort_by(.submitted_at) | last | select(.state == "APPROVED") | .user.login] | length')
echo "Approvals: $approvals"
if [ "$approvals" -lt 2 ]; then
echo "::error::Release PRs require at least 2 approvals (got $approvals)"
exit 1
fi
+55
View File
@@ -0,0 +1,55 @@
name: test-realtime
on:
workflow_dispatch:
inputs:
branch:
description: "Branch or revision to test"
required: false
type: string
default: "main"
pull_request:
paths:
- "livekit-plugins/livekit-plugins-openai/livekit/plugins/openai/realtime/**"
- "livekit-plugins/livekit-plugins-xai/livekit/plugins/xai/realtime/**"
- "tests/test_realtime/**"
- ".github/workflows/test-realtime.yml"
jobs:
test-realtime:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch != '' && github.event.inputs.branch || github.ref }}
lfs: true
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "latest"
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run Realtime tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }}
AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }}
AZURE_OPENAI_DEPLOYMENT: ${{ secrets.AZURE_OPENAI_DEPLOYMENT }}
AZURE_OPENAI_API_VERSION: ${{ secrets.AZURE_OPENAI_API_VERSION }}
# XAI_API_KEY not set — xAI rate-limits GitHub Actions IPs (429 on ws handshake)
run: |
uv run pytest --realtime -v -s --tb=long -p no:xdist
+271
View File
@@ -0,0 +1,271 @@
name: Test STT
on:
workflow_dispatch:
inputs:
branch:
description: "Branch or revision to test"
required: false
type: string
default: "main"
pr_number:
description: "PR number to post results to (optional)"
required: false
type: string
default: ""
pull_request:
paths:
- "tests/test_stt.py"
- ".github/workflows/test-stt.yml"
issue_comment:
types: [created]
jobs:
slash-command-dispatch:
if: github.event_name == 'issue_comment' && github.event.issue.pull_request
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
actions: write
steps:
- name: Get PR details and check authorization
id: pr-info
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const comment = context.payload.comment.body.trim();
const isCommand = /^\/test-stt(\s|$)/.test(comment);
if (!isCommand) {
core.setOutput('is_command', 'false');
return;
}
// Get PR details
const prResponse = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number,
});
const pr = prResponse.data;
const commenter = context.payload.comment.user.login;
// Check if commenter is an organization member (not just a collaborator)
let isAuthorized = false;
try {
// First check if the repo owner is an organization
const orgResponse = await github.rest.orgs.get({
org: context.repo.owner,
});
// If it's an organization, check if user is a member
if (orgResponse.data) {
try {
await github.rest.orgs.checkMembershipForUser({
org: context.repo.owner,
username: commenter,
});
isAuthorized = true;
console.log(`${commenter} is an organization member`);
} catch (memberError) {
// User is not an organization member
isAuthorized = false;
console.log(`${commenter} is not an organization member`);
}
}
} catch (orgError) {
// Repo owner is not an organization (it's a user account)
// In this case, check if commenter is the repo owner
if (commenter === context.repo.owner) {
isAuthorized = true;
console.log(`${commenter} is the repository owner`);
} else {
isAuthorized = false;
console.log(`${commenter} is not authorized (repo is user-owned, not org-owned)`);
}
}
if (!isAuthorized) {
console.log(`Slash command rejected: ${commenter} is not an organization member`);
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: '-1'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: '❌ `/test-stt` command is only available for organization members.'
});
core.setOutput('is_command', 'false');
return;
}
core.setOutput('is_command', 'true');
core.setOutput('pr_number', pr.number.toString());
core.setOutput('branch', pr.head.ref);
console.log(`Slash command /test-stt detected for PR #${pr.number} by ${commenter}`);
- name: Trigger workflow
if: steps.pr-info.outputs.is_command == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'test-stt.yml',
ref: 'main',
inputs: {
pr_number: '${{ steps.pr-info.outputs.pr_number }}',
branch: 'refs/pull/${{ steps.pr-info.outputs.pr_number }}/head'
}
});
console.log('Workflow triggered successfully');
// Add reaction to comment
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'rocket'
});
test-stt:
if: github.event_name != 'issue_comment'
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.branch != '' && github.event.inputs.branch || github.ref }}
fetch-depth: 0
lfs: true
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
version: "latest"
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install dependencies
run: |
uv sync --all-extras --dev
- name: Setup Google credentials
shell: bash
run: |
printf '%s' '${{ secrets.GOOGLE_STT_CREDENTIALS_JSON }}' > ${{ github.workspace }}/tests/google.json
- name: Run STT tests
env:
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
ASSEMBLYAI_API_KEY: ${{ secrets.ASSEMBLYAI_API_KEY }}
SPEECHMATICS_API_KEY: ${{ secrets.SPEECHMATICS_API_KEY }}
ELEVEN_API_KEY: ${{ secrets.ELEVEN_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKSAI_API_KEY }}
GLADIA_API_KEY: ${{ secrets.GLADIA_API_KEY }}
FAL_KEY: ${{ secrets.FAL_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
NVIDIA_API_KEY: ${{ secrets.NVIDIA_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CARTESIA_API_KEY: ${{ secrets.CARTESIA_API_KEY }}
GRADIUM_API_KEY: ${{ secrets.GRADIUM_API_KEY }}
SONIOX_API_KEY: ${{ secrets.SONIOX_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ github.workspace }}/tests/google.json
AZURE_SPEECH_KEY: ${{ secrets.AZURE_SPEECH_KEY }}
AZURE_SPEECH_REGION: ${{ secrets.AZURE_SPEECH_REGION }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
# AWS_REGION: ${{ secrets.AWS_REGION }}
SARVAM_API_KEY: ${{ secrets.SARVAM_API_KEY }}
run: |
uv run pytest -n 3 -v --stt --tb=long --junitxml=test-results.xml 2>&1 | tee test-output.txt || true
- name: Cleanup Google credentials
if: always()
shell: bash
run: |
rm -f ${{ github.workspace }}/tests/google.json
- name: Generate test summary
if: always()
id: test-summary
run: |
python3 scripts/generate_test_summary.py test-results.xml -o test-summary.md
- name: Post results to PR
if: always() && (github.event_name == 'pull_request' || github.event.inputs.pr_number != '')
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
with:
script: |
const fs = require('fs');
const summary = fs.readFileSync('test-summary.md', 'utf8');
let prNumber;
if ('${{ github.event_name }}' === 'pull_request') {
prNumber = ${{ github.event.pull_request.number || 0 }};
} else if ('${{ github.event.inputs.pr_number }}') {
prNumber = parseInt('${{ github.event.inputs.pr_number }}');
} else {
prNumber = null;
}
if (!prNumber || isNaN(prNumber)) {
console.log('No valid PR number, skipping comment');
return;
}
const body = `${summary}\n\n---\n*Triggered by workflow run [#${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})*`;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('## STT Test Results')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: body
});
console.log('Updated existing comment');
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body
});
console.log('Created new comment');
}
- name: Add to job summary
if: always()
run: |
cat test-summary.md >> $GITHUB_STEP_SUMMARY
+199
View File
@@ -0,0 +1,199 @@
name: tests
on:
push:
branches:
- main
paths-ignore:
- '**/*.md'
pull_request:
branches:
- main
paths-ignore:
- '**/*.md'
workflow_dispatch:
permissions:
contents: read
actions: write
jobs:
blockguard-tests:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Build and install blockguard
run: pip install livekit-plugins/livekit-blockguard/
- name: Install pytest
run: pip install pytest
- name: Run blockguard tests
working-directory: livekit-plugins/livekit-blockguard
run: python -m pytest test_blockguard.py -v
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install the project
run: uv sync --all-extras --dev
- name: Download NLTK data
run: uv run python -c "import nltk; nltk.download('punkt_tab')"
- name: Run tests
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: make unit-tests
evaluation:
# don't run tests for PRs on forks
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
enable-cache: true
cache-dependency-glob: "uv.lock"
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: "3.12"
- name: Install the project
run: uv sync --all-extras --dev
- name: Run evals
env:
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
run: uv run pytest --evals
tests:
# don't run tests for PRs on forks
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false
strategy:
fail-fast: false
matrix:
plugin:
[
cartesia,
deepgram,
elevenlabs,
groq,
openai,
inworld,
]
runs-on: ubuntu-latest
name: livekit-plugins-${{ matrix.plugin }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Google credentials
if: matrix.plugin == 'google'
shell: bash
run: |
printf '%s' '${{ secrets.GOOGLE_CREDENTIALS_JSON }}' > tests/google.json
- name: Run tests
shell: bash
env:
PLUGIN: ${{ matrix.plugin }}
LIVEKIT_URL: ${{ secrets.LIVEKIT_URL }}
LIVEKIT_API_KEY: ${{ secrets.LIVEKIT_API_KEY }}
LIVEKIT_API_SECRET: ${{ secrets.LIVEKIT_API_SECRET }}
DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
ELEVEN_API_KEY: ${{ secrets.ELEVEN_API_KEY }}
CARTESIA_API_KEY: ${{ secrets.CARTESIA_API_KEY }}
AZURE_SPEECH_KEY: ${{ secrets.AZURE_SPEECH_KEY }}
AZURE_SPEECH_REGION: ${{ secrets.AZURE_SPEECH_REGION }} # nit: doesn't have to be secret
GOOGLE_CREDENTIALS_JSON: ${{ secrets.GOOGLE_CREDENTIALS_JSON }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
ASSEMBLYAI_API_KEY: ${{ secrets.ASSEMBLYAI_API_KEY }}
FAL_KEY: ${{ secrets.FAL_KEY }}
PLAYHT_API_KEY: ${{ secrets.PLAYHT_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
PLAYHT_USER_ID: ${{ secrets.PLAYHT_USER_ID }}
RIME_API_KEY: ${{ secrets.RIME_API_KEY }}
SPEECHMATICS_API_KEY: ${{ secrets.SPEECHMATICS_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS: tests/google.json
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
NEUPHONIC_API_KEY: ${{ secrets.NEUPHONIC_API_KEY }}
RESEMBLE_API_KEY: ${{ secrets.RESEMBLE_API_KEY }}
SPEECHIFY_API_KEY: ${{ secrets.SPEECHIFY_API_KEY }}
HUME_API_KEY: ${{ secrets.HUME_API_KEY }}
SPITCH_API_KEY: ${{ secrets.SPITCH_API_KEY }}
LMNT_API_KEY: ${{ secrets.LMNT_API_KEY }}
INWORLD_API_KEY: ${{ secrets.INWORLD_API_KEY }}
SONIOX_API_KEY: ${{ secrets.SONIOX_API_KEY }}
working-directory: tests
run: make test
- name: Upload LiveKit dump
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: lk-dump-${{ matrix.plugin }}
path: lk_dump/**
if-no-files-found: ignore
retention-days: 1
aggregate-dumps:
if: always()
needs: tests
runs-on: ubuntu-latest
steps:
- name: download all dump shards
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: lk-dump-*
path: all_lk_dump
merge-multiple: true
- name: create tarball
id: tar
run: |
if [ -d "all_lk_dump" ] && [ "$(ls -A all_lk_dump)" ]; then
tar -czf lk-dump-all.tar.gz -C all_lk_dump .
echo "created=true" >> "$GITHUB_OUTPUT"
else
echo "No dump shards found, skipping tarball creation."
echo "created=false" >> "$GITHUB_OUTPUT"
fi
- name: upload merged dump
if: steps.tar.outputs.created == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: lk-dump-all
path: lk-dump-all.tar.gz
retention-days: 5