openring 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.
- openring-0.1.0/.dockerignore +12 -0
- openring-0.1.0/.env.example +36 -0
- openring-0.1.0/.gitattributes +3 -0
- openring-0.1.0/.githooks/check-leaks +33 -0
- openring-0.1.0/.githooks/pre-commit +3 -0
- openring-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
- openring-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- openring-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +21 -0
- openring-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- openring-0.1.0/.github/workflows/ci.yml +60 -0
- openring-0.1.0/.github/workflows/release.yml +71 -0
- openring-0.1.0/.gitignore +34 -0
- openring-0.1.0/CHANGELOG.md +42 -0
- openring-0.1.0/CLAUDE.md +101 -0
- openring-0.1.0/CODE_OF_CONDUCT.md +13 -0
- openring-0.1.0/CONTRIBUTING.md +65 -0
- openring-0.1.0/Dockerfile +12 -0
- openring-0.1.0/LICENSE +21 -0
- openring-0.1.0/PKG-INFO +214 -0
- openring-0.1.0/README.md +194 -0
- openring-0.1.0/README.pt-BR.md +141 -0
- openring-0.1.0/ROADMAP.md +26 -0
- openring-0.1.0/SECURITY.md +27 -0
- openring-0.1.0/THIRD_PARTY.md +81 -0
- openring-0.1.0/bin/openring.js +71 -0
- openring-0.1.0/docker-compose.yml +12 -0
- openring-0.1.0/docs/releasing.md +48 -0
- openring-0.1.0/openring/__init__.py +3 -0
- openring-0.1.0/openring/__main__.py +118 -0
- openring-0.1.0/openring/api.py +598 -0
- openring-0.1.0/openring/auth.py +116 -0
- openring-0.1.0/openring/bot.py +310 -0
- openring-0.1.0/openring/config.py +42 -0
- openring-0.1.0/openring/importer.py +151 -0
- openring-0.1.0/openring/keys.py +209 -0
- openring-0.1.0/openring/providers.py +458 -0
- openring-0.1.0/openring/server_messages.py +142 -0
- openring-0.1.0/openring/static/index.html +1138 -0
- openring-0.1.0/openring/stats.py +100 -0
- openring-0.1.0/openring/store.py +172 -0
- openring-0.1.0/openring/tools.py +278 -0
- openring-0.1.0/openring/twilio.py +80 -0
- openring-0.1.0/package.json +10 -0
- openring-0.1.0/pyproject.toml +37 -0
- openring-0.1.0/tests/conftest.py +6 -0
- openring-0.1.0/tests/test_api.py +282 -0
- openring-0.1.0/tests/test_bot.py +39 -0
- openring-0.1.0/tests/test_call_flow.py +112 -0
- openring-0.1.0/tests/test_importer.py +139 -0
- openring-0.1.0/tests/test_keys.py +220 -0
- openring-0.1.0/tests/test_main.py +32 -0
- openring-0.1.0/tests/test_outbound.py +96 -0
- openring-0.1.0/tests/test_providers.py +125 -0
- openring-0.1.0/tests/test_release.py +38 -0
- openring-0.1.0/tests/test_server_messages.py +273 -0
- openring-0.1.0/tests/test_stats.py +44 -0
- openring-0.1.0/tests/test_tools.py +296 -0
- openring-0.1.0/tests/test_web_call.py +65 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# --- Server ---------------------------------------------------------------
|
|
2
|
+
# Bearer token for the REST API. Always set this before exposing the server.
|
|
3
|
+
OPENRING_API_KEY=change-me
|
|
4
|
+
# Dashboard password. Leave empty to choose it at http://localhost on the first visit; set it when
|
|
5
|
+
# nobody opens the dashboard on the server itself (Docker, VPS). Reset: openring --reset-password
|
|
6
|
+
OPENRING_PASSWORD=
|
|
7
|
+
# Public https URL Twilio can reach (e.g. from `ngrok http 8000`)
|
|
8
|
+
PUBLIC_URL=https://your-subdomain.ngrok-free.app
|
|
9
|
+
# Optional: assistant used when a number has no assistant attached
|
|
10
|
+
DEFAULT_ASSISTANT_ID=
|
|
11
|
+
|
|
12
|
+
# --- Telephony (Twilio) ----------------------------------------------------
|
|
13
|
+
TWILIO_ACCOUNT_SID=
|
|
14
|
+
# Required to answer calls: it proves each webhook really comes from Twilio
|
|
15
|
+
TWILIO_AUTH_TOKEN=
|
|
16
|
+
# false skips that check (local tests only)
|
|
17
|
+
VALIDATE_TWILIO_SIGNATURE=true
|
|
18
|
+
|
|
19
|
+
# --- Speech-to-text --------------------------------------------------------
|
|
20
|
+
# Deepgram gives US$200 of free credit to new accounts
|
|
21
|
+
DEEPGRAM_API_KEY=
|
|
22
|
+
|
|
23
|
+
# --- LLM (any OpenAI-compatible provider) ---------------------------------
|
|
24
|
+
DEFAULT_LLM_PROVIDER=openai
|
|
25
|
+
DEFAULT_LLM_MODEL=gpt-4.1-mini
|
|
26
|
+
OPENAI_API_KEY=
|
|
27
|
+
GROQ_API_KEY=
|
|
28
|
+
OPENROUTER_API_KEY=
|
|
29
|
+
DEEPSEEK_API_KEY=
|
|
30
|
+
# OmniRoute (free gateway to 150+ free model providers)
|
|
31
|
+
OMNIROUTE_BASE_URL=http://localhost:20128/v1
|
|
32
|
+
OMNIROUTE_API_KEY=
|
|
33
|
+
|
|
34
|
+
# --- Text-to-speech ---------------------------------------------------------
|
|
35
|
+
# Leave empty to use Kokoro (free, runs locally)
|
|
36
|
+
ELEVENLABS_API_KEY=
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Refuses to publish secrets or private notes. Plain sh and grep, so it runs in Git Bash, macOS and Linux.
|
|
3
|
+
# .githooks/check-leaks checks the files staged for the next commit (the pre-commit hook)
|
|
4
|
+
# .githooks/check-leaks --all checks every tracked file (CI)
|
|
5
|
+
|
|
6
|
+
KEYS='gsk_[A-Za-z0-9]{20,}|sk-(proj-|ant-|or-v1-)?[A-Za-z0-9_-]{30,}|AC[0-9a-f]{32}|-----BEGIN [A-Z ]*PRIVATE KEY-----'
|
|
7
|
+
fail=0
|
|
8
|
+
|
|
9
|
+
if [ "$1" = "--all" ]; then
|
|
10
|
+
files=$(git ls-files)
|
|
11
|
+
content() { cat -- "$1" 2>/dev/null; }
|
|
12
|
+
else
|
|
13
|
+
files=$(git diff --cached --name-only --diff-filter=ACMR)
|
|
14
|
+
content() { git show ":$1" 2>/dev/null; }
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
for f in $files; do
|
|
18
|
+
case "$f" in
|
|
19
|
+
private/*|.env|*/.env|.env.*|*.db|*.sqlite)
|
|
20
|
+
[ "$f" = ".env.example" ] && continue
|
|
21
|
+
echo "check-leaks: $f must stay on your computer (it is private or holds secrets)"
|
|
22
|
+
fail=1
|
|
23
|
+
continue ;;
|
|
24
|
+
esac
|
|
25
|
+
if content "$f" | grep -Eq "$KEYS"; then
|
|
26
|
+
echo "check-leaks: $f contains something that looks like an API key or token"
|
|
27
|
+
content "$f" | grep -En "$KEYS" | cut -c1-60 | sed 's/^/ /'
|
|
28
|
+
fail=1
|
|
29
|
+
fi
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
[ $fail -eq 0 ] || echo "check-leaks: nothing was committed. Remove the secret, or keep the file out of git."
|
|
33
|
+
exit $fail
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something doesn't work as expected
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: |
|
|
8
|
+
Thanks for reporting. Never paste API keys, tokens or phone numbers: replace them with `xxx`.
|
|
9
|
+
Found a security problem? Report it privately instead, see SECURITY.md.
|
|
10
|
+
- type: textarea
|
|
11
|
+
id: what
|
|
12
|
+
attributes:
|
|
13
|
+
label: What happened
|
|
14
|
+
description: What you did, what you expected, and what happened instead.
|
|
15
|
+
validations:
|
|
16
|
+
required: true
|
|
17
|
+
- type: textarea
|
|
18
|
+
id: steps
|
|
19
|
+
attributes:
|
|
20
|
+
label: Steps to reproduce
|
|
21
|
+
placeholder: |
|
|
22
|
+
1. Create an assistant with ...
|
|
23
|
+
2. Start a browser test call
|
|
24
|
+
3. ...
|
|
25
|
+
validations:
|
|
26
|
+
required: true
|
|
27
|
+
- type: input
|
|
28
|
+
id: version
|
|
29
|
+
attributes:
|
|
30
|
+
label: OpenRing version
|
|
31
|
+
description: Shown under the logo in the dashboard, or in `GET /health`.
|
|
32
|
+
placeholder: "0.2.0"
|
|
33
|
+
validations:
|
|
34
|
+
required: true
|
|
35
|
+
- type: dropdown
|
|
36
|
+
id: install
|
|
37
|
+
attributes:
|
|
38
|
+
label: How do you run OpenRing?
|
|
39
|
+
options: ["npx openring", "pip / uv", "Docker", "From a git checkout"]
|
|
40
|
+
validations:
|
|
41
|
+
required: true
|
|
42
|
+
- type: input
|
|
43
|
+
id: os
|
|
44
|
+
attributes:
|
|
45
|
+
label: Operating system
|
|
46
|
+
placeholder: Windows 11, macOS 15, Ubuntu 24.04 ...
|
|
47
|
+
- type: input
|
|
48
|
+
id: providers
|
|
49
|
+
attributes:
|
|
50
|
+
label: Providers involved
|
|
51
|
+
description: Speech-to-text, model, voice and telephony, if the problem is in a call.
|
|
52
|
+
placeholder: Deepgram nova-3, Groq openai/gpt-oss-20b, Kokoro, Twilio
|
|
53
|
+
- type: textarea
|
|
54
|
+
id: logs
|
|
55
|
+
attributes:
|
|
56
|
+
label: Logs
|
|
57
|
+
description: The terminal output around the error, with keys removed.
|
|
58
|
+
render: text
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest an idea, a provider to support or a Vapi feature to match
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What do you want to do?
|
|
9
|
+
description: The problem or use case, not only the solution. Who is it for (agency, developer, ...)?
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: proposal
|
|
14
|
+
attributes:
|
|
15
|
+
label: How could OpenRing help?
|
|
16
|
+
description: If Vapi already has this, link its docs; OpenRing keeps Vapi's JSON shape.
|
|
17
|
+
- type: dropdown
|
|
18
|
+
id: contribute
|
|
19
|
+
attributes:
|
|
20
|
+
label: Would you like to build it?
|
|
21
|
+
options: ["Yes, with some guidance", "Maybe", "No"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
## What this changes
|
|
2
|
+
|
|
3
|
+
<!-- One or two sentences. Link the issue it closes, e.g. "Closes #12". -->
|
|
4
|
+
|
|
5
|
+
## How I tested it
|
|
6
|
+
|
|
7
|
+
<!-- New or updated tests, and anything you checked by hand (dashboard, a browser test call, a phone call). -->
|
|
8
|
+
|
|
9
|
+
## Checklist
|
|
10
|
+
|
|
11
|
+
- [ ] `pytest` passes
|
|
12
|
+
- [ ] `ruff check .` passes
|
|
13
|
+
- [ ] New fields keep Vapi's JSON shape, and nothing existing was renamed
|
|
14
|
+
- [ ] No keys, tokens or real phone numbers in code, tests or screenshots
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ci-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v5
|
|
19
|
+
- uses: actions/setup-python@v6
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
- run: pip install "ruff>=0.13"
|
|
23
|
+
- run: ruff check .
|
|
24
|
+
- name: Check the npm launcher parses
|
|
25
|
+
run: node --check bin/openring.js
|
|
26
|
+
- name: No secrets or private notes in the repository
|
|
27
|
+
run: sh .githooks/check-leaks --all
|
|
28
|
+
|
|
29
|
+
test:
|
|
30
|
+
strategy:
|
|
31
|
+
fail-fast: false
|
|
32
|
+
matrix:
|
|
33
|
+
os: [ubuntu-latest]
|
|
34
|
+
python: ["3.11", "3.12", "3.13"]
|
|
35
|
+
# While the repository is private, Windows minutes count double and macOS ten times, so a plain
|
|
36
|
+
# push to main tests Linux only; pull requests, release tags and manual runs add Windows and macOS.
|
|
37
|
+
# Once the repository is public, Actions minutes are free: drop this condition.
|
|
38
|
+
include: ${{ fromJSON((github.event_name == 'push' && github.ref_type == 'branch') && '[]' || '[{"os":"windows-latest","python":"3.12"},{"os":"macos-latest","python":"3.12"}]') }}
|
|
39
|
+
runs-on: ${{ matrix.os }}
|
|
40
|
+
steps:
|
|
41
|
+
- uses: actions/checkout@v5
|
|
42
|
+
- uses: actions/setup-python@v6
|
|
43
|
+
with:
|
|
44
|
+
python-version: ${{ matrix.python }}
|
|
45
|
+
cache: pip
|
|
46
|
+
cache-dependency-path: pyproject.toml
|
|
47
|
+
- run: pip install -e ".[dev,kokoro]"
|
|
48
|
+
- run: pytest -q
|
|
49
|
+
|
|
50
|
+
docker:
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/checkout@v5
|
|
54
|
+
- run: docker build -t openring:ci .
|
|
55
|
+
- name: The container answers /health
|
|
56
|
+
run: |
|
|
57
|
+
docker run -d --name openring -p 8000:8000 openring:ci
|
|
58
|
+
for i in $(seq 1 30); do curl -sf http://localhost:8000/health && exit 0; sleep 1; done
|
|
59
|
+
docker logs openring
|
|
60
|
+
exit 1
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Pushing a tag like v0.2.0 publishes that version to PyPI, then the `npx openring` launcher to npm.
|
|
4
|
+
# Both use trusted publishing (OpenID Connect): no tokens are stored in this repository.
|
|
5
|
+
# One-time setup is described in docs/releasing.md.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
check:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
outputs:
|
|
18
|
+
version: ${{ steps.version.outputs.version }}
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v5
|
|
21
|
+
- uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.12"
|
|
24
|
+
- name: The tag matches every version number
|
|
25
|
+
id: version
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
py=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
29
|
+
init=$(python -c "import re; print(re.search(r'__version__ = \"(.+)\"', open('openring/__init__.py').read())[1])")
|
|
30
|
+
npm=$(node -p "require('./package.json').version")
|
|
31
|
+
echo "tag=$tag pyproject=$py __init__=$init package.json=$npm"
|
|
32
|
+
[ "$tag" = "$py" ] && [ "$tag" = "$init" ] && [ "$tag" = "$npm" ]
|
|
33
|
+
echo "version=$tag" >> "$GITHUB_OUTPUT"
|
|
34
|
+
- run: pip install -e ".[dev,kokoro]"
|
|
35
|
+
- run: pytest -q
|
|
36
|
+
|
|
37
|
+
pypi:
|
|
38
|
+
needs: check
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
environment: pypi
|
|
41
|
+
permissions:
|
|
42
|
+
id-token: write # trusted publishing
|
|
43
|
+
steps:
|
|
44
|
+
- uses: actions/checkout@v5
|
|
45
|
+
- uses: actions/setup-python@v6
|
|
46
|
+
with:
|
|
47
|
+
python-version: "3.12"
|
|
48
|
+
- run: pip install build
|
|
49
|
+
- run: python -m build
|
|
50
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
51
|
+
|
|
52
|
+
npm:
|
|
53
|
+
# After PyPI: the launcher installs exactly this version from there.
|
|
54
|
+
needs: [check, pypi]
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
environment: npm
|
|
57
|
+
permissions:
|
|
58
|
+
id-token: write # trusted publishing and provenance
|
|
59
|
+
steps:
|
|
60
|
+
- uses: actions/checkout@v5
|
|
61
|
+
- uses: actions/setup-node@v5
|
|
62
|
+
with:
|
|
63
|
+
node-version: "24" # trusted publishing needs npm 11.5.1 or later
|
|
64
|
+
registry-url: https://registry.npmjs.org
|
|
65
|
+
- name: Publish (npm trusts this workflow only once the package exists)
|
|
66
|
+
run: |
|
|
67
|
+
if npm view openring version > /dev/null 2>&1; then
|
|
68
|
+
npm publish --provenance --access public
|
|
69
|
+
else
|
|
70
|
+
echo "::warning::openring is not on npm yet: publish the first version by hand, see docs/releasing.md"
|
|
71
|
+
fi
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Secrets and local data: never commit these
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
data/
|
|
6
|
+
*.db
|
|
7
|
+
*.db-journal
|
|
8
|
+
*.sqlite
|
|
9
|
+
|
|
10
|
+
# Maintainers' private notes (plans, strategy); see CLAUDE.md
|
|
11
|
+
private/
|
|
12
|
+
|
|
13
|
+
# Local Claude Code settings
|
|
14
|
+
.claude/settings.local.json
|
|
15
|
+
CLAUDE.local.md
|
|
16
|
+
|
|
17
|
+
# Python and tools
|
|
18
|
+
.venv/
|
|
19
|
+
venv/
|
|
20
|
+
__pycache__/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
build/
|
|
23
|
+
dist/
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# Node
|
|
28
|
+
node_modules/
|
|
29
|
+
|
|
30
|
+
# Editors and OS
|
|
31
|
+
.vscode/
|
|
32
|
+
.idea/
|
|
33
|
+
.DS_Store
|
|
34
|
+
Thumbs.db
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to OpenRing are listed here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-10-30
|
|
8
|
+
|
|
9
|
+
The first public release. Everything below is new.
|
|
10
|
+
|
|
11
|
+
### Voice agents
|
|
12
|
+
|
|
13
|
+
- Vapi-compatible REST API for assistants (`/assistant`), phone numbers (`/phone-number`), calls
|
|
14
|
+
(`/call`) and tools (`/tool`): existing Vapi clients work by changing the base URL.
|
|
15
|
+
- Inbound phone calls through Twilio, and outbound calls with `POST /call`.
|
|
16
|
+
- Talk to an assistant from the browser, with no phone number needed.
|
|
17
|
+
- Assistant tools in Vapi's format: `function`, `endCall` and `transferCall`.
|
|
18
|
+
- Import assistants, tools and phone numbers from a Vapi-compatible API.
|
|
19
|
+
- Per-turn latency on every call, in Vapi's `artifact.performanceMetrics` shape, and Vapi-style
|
|
20
|
+
`end-of-call-report` webhooks.
|
|
21
|
+
|
|
22
|
+
### Providers
|
|
23
|
+
|
|
24
|
+
- Models from any OpenAI-compatible provider: OpenAI, Groq, OpenRouter, DeepSeek, OmniRoute or a
|
|
25
|
+
custom URL.
|
|
26
|
+
- Speech to text with Deepgram; voices from ElevenLabs or Kokoro, a free voice that runs on your
|
|
27
|
+
computer.
|
|
28
|
+
- Connect providers from the dashboard: each key is checked with the provider before it's saved, and
|
|
29
|
+
OpenRouter offers one-click sign-in.
|
|
30
|
+
|
|
31
|
+
### Dashboard
|
|
32
|
+
|
|
33
|
+
- Overview with calls, minutes, success rate, response time and the platform fee you didn't pay.
|
|
34
|
+
- Assistants, phone numbers, calls with transcripts and latency, providers and settings, in light and
|
|
35
|
+
dark themes.
|
|
36
|
+
- Sign-in with a password; the API keeps its Bearer key.
|
|
37
|
+
|
|
38
|
+
### Running it
|
|
39
|
+
|
|
40
|
+
- One command: `npx openring` installs everything, including Python, and opens the dashboard.
|
|
41
|
+
- Docker image and `docker-compose.yml`.
|
|
42
|
+
- Twilio webhooks are rejected unless their signature checks out.
|
openring-0.1.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# OpenRing: project context for Claude
|
|
2
|
+
|
|
3
|
+
OpenRing is an open-source, self-hosted alternative to paid voice AI agent platforms. People run it on
|
|
4
|
+
their own machine or server with their own provider keys, so there is **no per-minute platform fee**.
|
|
5
|
+
Its API is **Vapi-compatible**: existing clients migrate by changing the base URL. Code, commits and
|
|
6
|
+
docs are in English (README.pt-BR.md is the exception). See CONTRIBUTING.md for setup and rules.
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
- `openring/api.py` FastAPI app: Vapi-style REST (`/assistant`, `/phone-number`, `/call`), Twilio
|
|
11
|
+
voice webhook (`POST /twilio/voice`, validates `X-Twilio-Signature`, refuses calls while
|
|
12
|
+
`TWILIO_AUTH_TOKEN` is unset unless `VALIDATE_TWILIO_SIGNATURE=false`), media websocket
|
|
13
|
+
(`/ws/twilio`: one connection per call, it atomically claims the queued call via `Store.claim`;
|
|
14
|
+
the assistant comes from the call record, never from stream parameters), `POST /call/web`
|
|
15
|
+
(browser test call: answers the dashboard's WebRTC offer with Pipecat's `SmallWebRTCConnection`
|
|
16
|
+
and runs the call as a background task; non-trickle ICE), `POST /call` (outbound, Vapi body:
|
|
17
|
+
creates a queued `outboundPhoneCall`, Twilio REST places it with
|
|
18
|
+
`Url=$PUBLIC_URL/twilio/voice?callId=...`; signatures cover the query string; `POST /twilio/status`
|
|
19
|
+
ends calls that never connected with Vapi's endedReason), dashboard at `/`, `/health`,
|
|
20
|
+
`GET /stats` (overview numbers, `openring/stats.py`).
|
|
21
|
+
- Dashboard login (`openring/auth.py`, like OmniRoute): one password per server, scrypt hash in the DB
|
|
22
|
+
`settings` table; sessions in the `sessions` table as `HttpOnly`/`SameSite=Strict` cookies. Requests
|
|
23
|
+
authorize with the Bearer API key OR session cookie + `X-OpenRing: dashboard` header (CSRF guard).
|
|
24
|
+
`/auth/state|setup|login|logout|password`; the first password is chosen only from direct localhost
|
|
25
|
+
or set by `OPENRING_PASSWORD`; `openring --reset-password` clears it. `/dashboard/info` (session only)
|
|
26
|
+
gives the Settings/Providers pages the API key, URLs and which provider keys are set (never the keys).
|
|
27
|
+
- `openring/bot.py` one live call: `run_call(transport, sample_rate=...)` runs Pipecat's pipeline
|
|
28
|
+
transport -> STT -> LLM -> TTS; `twilio_transport` (8 kHz) and `web_transport` (WebRTC) build the
|
|
29
|
+
transport. First message, end-call phrases, saves transcript and per-turn latency
|
|
30
|
+
(`artifact.performanceMetrics`, Vapi's shape, in ms, from Pipecat's `UserBotLatencyObserver`),
|
|
31
|
+
sends Vapi-style `end-of-call-report` to `server.url`. Services are built in a worker thread.
|
|
32
|
+
- `openring/providers.py` maps the Vapi assistant JSON to a stack using Vapi's provider names (`ALIASES`
|
|
33
|
+
for other spellings). LLM: OpenAI-compatible APIs (`LLM_PROVIDERS`: openai, anthropic, google, groq,
|
|
34
|
+
openrouter, deepseek, xai, cerebras, together-ai, deepinfra, perplexity-ai, mistral, fireworks, ollama,
|
|
35
|
+
omniroute) + `custom-llm` with `model.url`. STT (`STT_SERVICES`): deepgram, assembly-ai, gladia, soniox,
|
|
36
|
+
cartesia, 11labs. TTS (`TTS_SERVICES`): 11labs, kokoro (free, local, default), cartesia, deepgram (Vapi
|
|
37
|
+
short voice names -> `aura-2-<name>-en`), openai, rime-ai, neuphonic, smallest-ai, inworld. Defaults
|
|
38
|
+
(nova-3, af_heart) apply only to their own provider. Kokoro uses the
|
|
39
|
+
int8 model (~120 MB with voices) in `~/.cache/pipecat/kokoro-onnx`: `warm_up()` at startup
|
|
40
|
+
downloads it if missing (atomic `.part` rename) and loads it once; Pipecat's per-service model
|
|
41
|
+
load is patched to share that instance. Calls refuse Kokoro with a progress message until ready.
|
|
42
|
+
- Groq's catalog changed in 2026: Llama 3.x is gone; use `openai/gpt-oss-20b` (fast) or
|
|
43
|
+
`openai/gpt-oss-120b`. Groq's Cloudflare returns 403 to Python's default urllib User-Agent.
|
|
44
|
+
- `openring/tools.py` Vapi tools: `function` (POSTs Vapi's `tool-calls` message to `tool.server.url` or the
|
|
45
|
+
assistant's `server.url`, reads `{"results": [{"toolCallId", "result"|"error"}]}`), `endCall`
|
|
46
|
+
(endedReason `assistant-ended-call`), `transferCall` (waits until the bot stops speaking, keeps Pipecat's
|
|
47
|
+
Twilio serializer from hanging up, then redirects the call with `<Dial>` TwiML; endedReason
|
|
48
|
+
`assistant-forwarded-call`; browser calls can't transfer). `model.tools` + saved `/tool` docs via
|
|
49
|
+
`model.toolIds`; unsupported types are skipped with a warning. Registered with `llm.register_function`
|
|
50
|
+
and `ToolsSchema` on the LLMContext; the transcript keeps `tool_calls` / `tool_call_result` rows.
|
|
51
|
+
- `openring/importer.py` + `POST /import {baseUrl, apiKey}`: copies tools, assistants and Twilio numbers from
|
|
52
|
+
any Vapi-compatible API (pages back with `createdAtLt`), drops source-owned fields and secrets, remaps
|
|
53
|
+
`model.toolIds`, marks docs with `importedFrom` so a re-import updates them; reports per-assistant problems
|
|
54
|
+
(unsupported providers/tools) without refusing the import. Dashboard: **Import** on the Assistants page.
|
|
55
|
+
- `openring/store.py` SQLite document store (JSON docs) for assistants, phone_numbers, calls, tools.
|
|
56
|
+
- `openring/static/index.html` single-file dashboard (vanilla JS, no build step), styled after OmniRoute:
|
|
57
|
+
sidebar with sections, dark/light theme, big-number cards, tabs. Pages: Home (overview + get started),
|
|
58
|
+
Assistants (tiles; editor with Setup/JSON tabs), Phone numbers, Calls (filters; detail with transcript,
|
|
59
|
+
per-turn latency, JSON), Providers, Settings (API access, password).
|
|
60
|
+
- `openring/__main__.py` `openring` command: first run creates `~/.openring/.env` with a generated
|
|
61
|
+
`OPENRING_API_KEY`, DB at `~/.openring/openring.db`, binds 127.0.0.1:8000.
|
|
62
|
+
- `bin/openring.js` + `package.json` npm launcher: installs uv if missing, runs
|
|
63
|
+
`uv tool run --python 3.12 --from "openring[kokoro]==<package.json version>" openring` (the PyPI release
|
|
64
|
+
matching the launcher), opens the browser. `OPENRING_SOURCE` (e.g. `file:///C:/openring` or a git URL)
|
|
65
|
+
runs that source instead. Until the first PyPI release, `npx` needs `OPENRING_SOURCE`.
|
|
66
|
+
- Releases (`docs/releasing.md`): tag `vX.Y.Z` -> `.github/workflows/release.yml` checks the tag matches
|
|
67
|
+
`pyproject.toml`, `openring/__init__.py` and `package.json`, tests, publishes to PyPI, then npm. Trusted
|
|
68
|
+
publishing (OIDC), no stored tokens; npm's first version is published by hand (npm needs the package to
|
|
69
|
+
exist before trusting a workflow).
|
|
70
|
+
- Built on Pipecat 1.15 (services use `Settings(...)` objects; VAD via
|
|
71
|
+
`LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer())`).
|
|
72
|
+
|
|
73
|
+
## Commands
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install -e ".[dev,kokoro]" # dev install
|
|
77
|
+
pytest # incl. a simulated Twilio call and a real local WebRTC call
|
|
78
|
+
python -m openring # run server (reads ./.env and ~/.openring/.env)
|
|
79
|
+
npx openring # one-command run, once released on npm and PyPI
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Only one server can use port 8000; stop the old one first, or pass `--port`.
|
|
83
|
+
|
|
84
|
+
## Phone testing notes
|
|
85
|
+
|
|
86
|
+
- A Twilio trial account calls only verified numbers, through Twilio's shared "Sandbox Number". That
|
|
87
|
+
sandbox's proxy re-signs webhooks (they fail validation with the account token) and the media stream
|
|
88
|
+
never opens, so callers hear silence. Test with an inbound call to a number you bought, or upgrade.
|
|
89
|
+
- The Calls API on trial rejects the `Method`/`StatusCallbackMethod` parameters.
|
|
90
|
+
- To reach a local server, use a tunnel (`cloudflared tunnel --url http://localhost:8000`). Quick-tunnel
|
|
91
|
+
URLs change on every start: update `PUBLIC_URL` and the number's voice webhook.
|
|
92
|
+
|
|
93
|
+
## Conventions
|
|
94
|
+
|
|
95
|
+
- Keep the Vapi JSON shape for any new field; prefer adding over renaming.
|
|
96
|
+
- Every feature gets a test; `pytest` and `ruff check .` must pass before pushing.
|
|
97
|
+
- Never commit `.env` or keys. Don't use Vapi's name or logo except to say "Vapi-compatible".
|
|
98
|
+
- Third-party licenses are listed in THIRD_PARTY.md; update it when dependencies change.
|
|
99
|
+
|
|
100
|
+
Maintainers keep private notes (plans, status, working rules) outside git; Claude loads them when present:
|
|
101
|
+
@private/CLAUDE.md
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
OpenRing follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
4
|
+
In short: be respectful and welcoming, assume good intent, give and take feedback gracefully, and keep
|
|
5
|
+
discussions about the work. Harassment, insults, sexualized language or imagery, and publishing others'
|
|
6
|
+
private information are not acceptable, in any project space or when representing the project.
|
|
7
|
+
|
|
8
|
+
## Reporting
|
|
9
|
+
|
|
10
|
+
Report unacceptable behavior to the maintainer at **alan.rocha@realvalorai.com.br**. Every report is
|
|
11
|
+
reviewed promptly and kept confidential. Maintainers may remove comments, commits or other
|
|
12
|
+
contributions that break this code, and may ban people who repeatedly or seriously do so, following the
|
|
13
|
+
Contributor Covenant's enforcement guidelines.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Contributing to OpenRing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. OpenRing is a self-hosted voice AI agent platform with a Vapi-compatible API, so the
|
|
4
|
+
most useful contributions make it easier to run, cheaper per minute, or more compatible with existing
|
|
5
|
+
Vapi clients. Small pull requests get merged fastest.
|
|
6
|
+
|
|
7
|
+
New here? Look for issues labeled
|
|
8
|
+
[`good first issue`](https://github.com/elifalanrocha/openring/labels/good%20first%20issue).
|
|
9
|
+
|
|
10
|
+
## Set up
|
|
11
|
+
|
|
12
|
+
You need Python 3.11 or later and, to try the npm launcher, Node.js 18 or later.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone https://github.com/elifalanrocha/openring
|
|
16
|
+
cd openring
|
|
17
|
+
python -m venv .venv
|
|
18
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
19
|
+
pip install -e ".[dev,kokoro]"
|
|
20
|
+
pytest # about 20 s; includes a simulated Twilio call and a local WebRTC call
|
|
21
|
+
python -m openring # dashboard at http://localhost:8000
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The first start downloads the free Kokoro voice (about 120 MB). Provider keys go in `~/.openring/.env`
|
|
25
|
+
or are added from the dashboard's Providers page; see [.env.example](.env.example).
|
|
26
|
+
|
|
27
|
+
## Where things are
|
|
28
|
+
|
|
29
|
+
| Path | What it does |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| `openring/api.py` | FastAPI app: the Vapi-style REST API, Twilio webhooks, the media websocket and the dashboard routes |
|
|
32
|
+
| `openring/bot.py` | One live call: the Pipecat pipeline transport → speech-to-text → model → voice |
|
|
33
|
+
| `openring/providers.py` | Maps a Vapi assistant JSON to providers; add an OpenAI-compatible model provider with one line in `LLM_PROVIDERS` |
|
|
34
|
+
| `openring/keys.py` | Connects providers from the dashboard: checks a key with the provider, then saves it |
|
|
35
|
+
| `openring/store.py` | SQLite document store for assistants, phone numbers, calls and tools |
|
|
36
|
+
| `openring/static/index.html` | The whole dashboard: one file, vanilla JavaScript, no build step |
|
|
37
|
+
| `bin/openring.js` | The `npx openring` launcher |
|
|
38
|
+
| `tests/` | pytest suite; every feature has a test here |
|
|
39
|
+
|
|
40
|
+
## Rules of the road
|
|
41
|
+
|
|
42
|
+
- **Keep the Vapi JSON shape.** A new field uses Vapi's name and structure when Vapi has one. Prefer
|
|
43
|
+
adding fields over renaming them, so existing clients keep working.
|
|
44
|
+
- **Every change comes with a test**, and `pytest` and `ruff check .` must pass. CI runs both on
|
|
45
|
+
Linux, Windows and macOS.
|
|
46
|
+
- **Never commit keys, tokens or `.env` files**, not even in tests: use obviously fake values.
|
|
47
|
+
Run `git config core.hooksPath .githooks` once, and a pre-commit check stops you if you try; CI runs
|
|
48
|
+
the same check.
|
|
49
|
+
- **No paid service is required to run the tests.** Mock provider HTTP calls (see `tests/test_keys.py`).
|
|
50
|
+
- **Say "Vapi-compatible"**, and don't use Vapi's name or logo in any other way.
|
|
51
|
+
- Lines are up to 120 characters; `ruff check . --fix` sorts imports.
|
|
52
|
+
|
|
53
|
+
## Pull requests
|
|
54
|
+
|
|
55
|
+
1. Open an issue first for anything larger than a bug fix, so we can agree on the approach.
|
|
56
|
+
2. Branch from `main`, keep the change focused, and describe what it does and how you tested it.
|
|
57
|
+
3. Fill in the pull request template; a maintainer reviews within a few days.
|
|
58
|
+
|
|
59
|
+
By contributing, you agree that your work is released under the [MIT License](LICENSE).
|
|
60
|
+
|
|
61
|
+
## Security problems
|
|
62
|
+
|
|
63
|
+
Please don't open a public issue for a vulnerability. See [SECURITY.md](SECURITY.md).
|
|
64
|
+
|
|
65
|
+
Everyone taking part follows the [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*
|
|
5
|
+
COPY pyproject.toml README.md ./
|
|
6
|
+
COPY openring ./openring
|
|
7
|
+
RUN pip install --no-cache-dir ".[kokoro]"
|
|
8
|
+
|
|
9
|
+
ENV OPENRING_DB_PATH=/data/openring.db HOST=0.0.0.0 OPENRING_HOME=/data
|
|
10
|
+
VOLUME ["/data"]
|
|
11
|
+
EXPOSE 8000
|
|
12
|
+
CMD ["openring"]
|
openring-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alan Rocha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|