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.
Files changed (58) hide show
  1. openring-0.1.0/.dockerignore +12 -0
  2. openring-0.1.0/.env.example +36 -0
  3. openring-0.1.0/.gitattributes +3 -0
  4. openring-0.1.0/.githooks/check-leaks +33 -0
  5. openring-0.1.0/.githooks/pre-commit +3 -0
  6. openring-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +58 -0
  7. openring-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  8. openring-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +21 -0
  9. openring-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +14 -0
  10. openring-0.1.0/.github/workflows/ci.yml +60 -0
  11. openring-0.1.0/.github/workflows/release.yml +71 -0
  12. openring-0.1.0/.gitignore +34 -0
  13. openring-0.1.0/CHANGELOG.md +42 -0
  14. openring-0.1.0/CLAUDE.md +101 -0
  15. openring-0.1.0/CODE_OF_CONDUCT.md +13 -0
  16. openring-0.1.0/CONTRIBUTING.md +65 -0
  17. openring-0.1.0/Dockerfile +12 -0
  18. openring-0.1.0/LICENSE +21 -0
  19. openring-0.1.0/PKG-INFO +214 -0
  20. openring-0.1.0/README.md +194 -0
  21. openring-0.1.0/README.pt-BR.md +141 -0
  22. openring-0.1.0/ROADMAP.md +26 -0
  23. openring-0.1.0/SECURITY.md +27 -0
  24. openring-0.1.0/THIRD_PARTY.md +81 -0
  25. openring-0.1.0/bin/openring.js +71 -0
  26. openring-0.1.0/docker-compose.yml +12 -0
  27. openring-0.1.0/docs/releasing.md +48 -0
  28. openring-0.1.0/openring/__init__.py +3 -0
  29. openring-0.1.0/openring/__main__.py +118 -0
  30. openring-0.1.0/openring/api.py +598 -0
  31. openring-0.1.0/openring/auth.py +116 -0
  32. openring-0.1.0/openring/bot.py +310 -0
  33. openring-0.1.0/openring/config.py +42 -0
  34. openring-0.1.0/openring/importer.py +151 -0
  35. openring-0.1.0/openring/keys.py +209 -0
  36. openring-0.1.0/openring/providers.py +458 -0
  37. openring-0.1.0/openring/server_messages.py +142 -0
  38. openring-0.1.0/openring/static/index.html +1138 -0
  39. openring-0.1.0/openring/stats.py +100 -0
  40. openring-0.1.0/openring/store.py +172 -0
  41. openring-0.1.0/openring/tools.py +278 -0
  42. openring-0.1.0/openring/twilio.py +80 -0
  43. openring-0.1.0/package.json +10 -0
  44. openring-0.1.0/pyproject.toml +37 -0
  45. openring-0.1.0/tests/conftest.py +6 -0
  46. openring-0.1.0/tests/test_api.py +282 -0
  47. openring-0.1.0/tests/test_bot.py +39 -0
  48. openring-0.1.0/tests/test_call_flow.py +112 -0
  49. openring-0.1.0/tests/test_importer.py +139 -0
  50. openring-0.1.0/tests/test_keys.py +220 -0
  51. openring-0.1.0/tests/test_main.py +32 -0
  52. openring-0.1.0/tests/test_outbound.py +96 -0
  53. openring-0.1.0/tests/test_providers.py +125 -0
  54. openring-0.1.0/tests/test_release.py +38 -0
  55. openring-0.1.0/tests/test_server_messages.py +273 -0
  56. openring-0.1.0/tests/test_stats.py +44 -0
  57. openring-0.1.0/tests/test_tools.py +296 -0
  58. openring-0.1.0/tests/test_web_call.py +65 -0
@@ -0,0 +1,12 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ venv
5
+ data
6
+ tests
7
+ docs
8
+ **/__pycache__
9
+ *.egg-info
10
+ .pytest_cache
11
+ .ruff_cache
12
+ .env
@@ -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,3 @@
1
+ # Shell scripts must keep Unix line endings, also on Windows checkouts.
2
+ .githooks/* text eol=lf
3
+ *.sh text eol=lf
@@ -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,3 @@
1
+ #!/bin/sh
2
+ # Turn on once per clone with: git config core.hooksPath .githooks
3
+ exec "$(dirname "$0")/check-leaks"
@@ -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,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Report a security problem
4
+ url: https://github.com/elifalanrocha/openring/security/advisories/new
5
+ about: Please report vulnerabilities privately, not in a public issue.
@@ -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.
@@ -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.