refactor: rename API and frontend projects to Vocero

Rename the project directories, Python distribution and import package, and npm package. Update launcher, Docker, tooling, tests, lockfiles, and documentation while preserving existing API contracts.

Make AGENTS.md self-contained and remove external skill and workflow references.
This commit is contained in:
2026-10-06 19:26:55 -03:00
parent e0304b2bdb
commit abd03ed07d
105 changed files with 353 additions and 312 deletions
+6 -6
View File
@@ -38,8 +38,8 @@ node_modules/
.npm/
package-lock.json
!speech_to_speech/package-lock.json
!s2s-frontend/package-lock.json
s2s-frontend/.eslintcache
!vocero-frontend/package-lock.json
vocero-frontend/.eslintcache
yarn-error.log
npm-debug.log*
yarn-debug.log*
@@ -125,10 +125,10 @@ CVS/
# ─────────────────────────────────────────────────────────────
# Client backend and speech-service runtime artifacts
# ─────────────────────────────────────────────────────────────
s2s-client/.cache/
s2s-client/uploads/
s2s-client/sessions/
s2s-client/*.log
vocero-api/.cache/
vocero-api/uploads/
vocero-api/sessions/
vocero-api/*.log
speech_to_speech/*.log
speech_to_speech/sessions/
speech_to_speech/.cache/
+52 -52
View File
@@ -3,12 +3,13 @@
## Scope and working conventions
This repository is Vocero: a browser voice client, its FastAPI integration
server, and a speech pipeline. Use the code, commands,
configuration, and documentation within this checkout. Do not assume other
repositories or developer-specific directories are available.
server, and a speech pipeline. Keep this guide self-contained and base its
instructions on this checkout's code, configuration, documentation, and tools.
All file and directory references in this guide are relative to the repository
root. HTTP routes mentioned below are URLs, not filesystem paths.
All file and directory references in this guide must point within the repository
and be relative to its root. Commands may use the project's command-line tools,
such as `rg`, `git`, `uv`, `npm`, Docker, and PM2. HTTP routes mentioned below are
URLs, not filesystem paths.
- Check the current branch, worktree, and working-tree changes before editing.
Honor the checkout requested by the user, including direct work on `main`.
@@ -23,35 +24,34 @@ root. HTTP routes mentioned below are URLs, not filesystem paths.
assuming a tool is missing. Do not install system-wide packages unnecessarily.
- When commits are requested, make coherent, scoped Conventional Commits as work
progresses. Avoid excessive micro-commits and unrelated cleanup.
- Invoke `work-on-feature` only when explicitly requested. For a requested PR,
follow the available `pr-description` skill exactly and put verification
details in the handoff when its format excludes them. Issues are optional;
create native Gitea issues or post comments only when requested.
- For a requested pull request, describe the problem, resulting behavior,
validation, and relevant limitations. Create issues or post comments only
when requested.
- When asked to merge locally, use the requested rebase/fast-forward flow;
preserve a recovery ref before rewriting and verify retained patches. A local
merge does not imply a push, deployment, or restart. Report those separately.
- Treat old conversations and proposals as context. Verify claims against the
current code and distinguish proposed authentication/deployment changes from
implemented behavior.
- Treat historical proposals in `docs/proposals/` as context. Verify their
claims against current code and distinguish proposed authentication or
deployment changes from implemented behavior.
## Project map
| Location | Responsibility |
| ---------------------------------- | ------------------------------------------------------------------------------------------- |
| `s2s-client/` | The complete uv project: manifests, environment, Python tooling, tests, and FastAPI package |
| `s2s-client/s2s_client/main.py` | Application assembly, documentation mount, and retained redirects |
| `s2s-client/s2s_client/api/` | HTTP routes, route registration, and response validation helper |
| `s2s-client/s2s_client/models/` | Pydantic request and response contracts |
| `s2s-client/s2s_client/core/` | Environment parsing, logging, public URLs, and startup diagnostics |
| `s2s-client/s2s_client/providers/` | ApiPilot and caller/profile/report HTTP integrations |
| `s2s-client/s2s_client/services/` | Prompt/context construction, reasoning, and tool-context rendering |
| `s2s-client/s2s_client/realtime/` | WebSocket relay, event handling, marker filtering, and audio forwarding |
| `s2s-frontend/` | React, TypeScript, Tailwind, and Vite browser application |
| `vocero-api/` | The complete uv project: manifests, environment, Python tooling, tests, and FastAPI package |
| `vocero-api/vocero_api/main.py` | Application assembly, documentation mount, and retained redirects |
| `vocero-api/vocero_api/api/` | HTTP routes, route registration, and response validation helper |
| `vocero-api/vocero_api/models/` | Pydantic request and response contracts |
| `vocero-api/vocero_api/core/` | Environment parsing, logging, public URLs, and startup diagnostics |
| `vocero-api/vocero_api/providers/` | ApiPilot and caller/profile/report HTTP integrations |
| `vocero-api/vocero_api/services/` | Prompt/context construction, reasoning, and tool-context rendering |
| `vocero-api/vocero_api/realtime/` | WebSocket relay, event handling, marker filtering, and audio forwarding |
| `vocero-frontend/` | React, TypeScript, Tailwind, and Vite browser application |
| `speech_to_speech/` | Separately run VAD → STT → LLM → TTS pipeline and realtime API |
| `start_ui.sh` | Dependency sync and supervision of FastAPI plus Vite |
| `docs/` | Maintained technical documentation, examples, and historical proposals |
`s2s-client/server.py` is a compatibility shim for `server:app`; new backend
`vocero-api/server.py` is a compatibility shim for `server:app`; new backend
implementation belongs in the package. Keep HTTP handlers focused on transport
and reuse providers/services for shared behavior.
@@ -62,37 +62,37 @@ Load-balancer allocation/queues and upstream caller authentication remain.
## Tooling and checks
The uv project must stay inside `s2s-client/`, including
`s2s-client/pyproject.toml`, `s2s-client/uv.lock`, `s2s-client/.python-version`,
`s2s-client/.venv/`, `s2s-client/Makefile`, and
`s2s-client/.pre-commit-config.yaml`.
The uv project must stay inside `vocero-api/`, including
`vocero-api/pyproject.toml`, `vocero-api/uv.lock`, `vocero-api/.python-version`,
`vocero-api/.venv/`, `vocero-api/Makefile`, and
`vocero-api/.pre-commit-config.yaml`.
Use Python 3.12 and the uv version required by `tool.uv.required-version`
(currently 0.12.5). Update dependencies through uv and commit the lockfile;
do not use pip to modify this environment or move it to the repository root.
Keep uv pins in `s2s-client/Dockerfile` and setup documentation aligned when
Keep uv pins in `vocero-api/Dockerfile` and setup documentation aligned when
changing the required version.
The frontend uses npm and `s2s-frontend/package-lock.json`. Its Node requirement is
`^22.18.0 || >=24.0.0`; consult `s2s-frontend/package.json` for current scripts.
The frontend uses npm and `vocero-frontend/package-lock.json`. Its Node requirement is
`^22.18.0 || >=24.0.0`; consult `vocero-frontend/package.json` for current scripts.
Keep application code and supported configuration files in TypeScript.
Commands below run from the repository root:
| Command | Purpose |
| ------------------------------------- | --------------------------------------------------------------------- |
| `make -C s2s-client install` | Sync the locked Python environment and development tools |
| `make -C s2s-client format` | Apply Ruff fixes and formatting |
| `make -C s2s-client check` | Check lock freshness, formatting, Ruff lint, mypy, and pytest |
| `make -C s2s-client test-launcher` | Test readiness, failures, dependency sync, and process-group shutdown |
| `make -C s2s-client test-image` | Build and smoke-test the actual backend Docker image |
| `npm --prefix s2s-frontend ci` | Install locked frontend dependencies |
| `npm --prefix s2s-frontend run check` | Check formatting, lint, TypeScript, Vitest, and launcher integration |
| `npm --prefix s2s-frontend run build` | Type-check and build frontend assets |
| `make -C vocero-api install` | Sync the locked Python environment and development tools |
| `make -C vocero-api format` | Apply Ruff fixes and formatting |
| `make -C vocero-api check` | Check lock freshness, formatting, Ruff lint, mypy, and pytest |
| `make -C vocero-api test-launcher` | Test readiness, failures, dependency sync, and process-group shutdown |
| `make -C vocero-api test-image` | Build and smoke-test the actual backend Docker image |
| `npm --prefix vocero-frontend ci` | Install locked frontend dependencies |
| `npm --prefix vocero-frontend run check` | Check formatting, lint, TypeScript, Vitest, and launcher integration |
| `npm --prefix vocero-frontend run build` | Type-check and build frontend assets |
Python uses Ruff with a 120-character line limit, double quotes, import ordering,
and typed application functions checked by mypy. Frontend formatting and lint
rules live in `s2s-frontend/prettier.config.ts` and
`s2s-frontend/eslint.config.ts`; use those tools rather than introducing
rules live in `vocero-frontend/prettier.config.ts` and
`vocero-frontend/eslint.config.ts`; use those tools rather than introducing
competing conventions.
Run checks appropriate to the change: backend checks for Python/API work,
@@ -126,7 +126,7 @@ evidence; do not bundle them into tooling or structural refactors.
- Response models validate fields constructed by this service. Upstream-owned
values remain permissive where the old parser was permissive: `Any`, optional
fields, open objects, and arbitrary profile JSON are deliberate boundaries.
- `validated_json_response` in `s2s-client/s2s_client/api/responses.py` validates
- `validated_json_response` in `vocero-api/vocero_api/api/responses.py` validates
with Pydantic, then serializes the original payload. Do not substitute
`model_dump()` or automatic response-model serialization: those can insert
defaults, filter unknown keys,
@@ -135,8 +135,8 @@ evidence; do not bundle them into tooling or structural refactors.
- Preserve environment/dotenv parsing, default flags, and import-time behavior
during refactors. Do not introduce stricter configuration validation or change
authentication behavior as part of a tooling change.
- `s2s-client/tests/fixtures/legacy-contracts.json` captures the server before the
migration at `3d42df5`. `s2s-client/tests/test_compatibility.py` compares HTTP
- `vocero-api/tests/fixtures/legacy-contracts.json` captures the server before the
migration at `3d42df5`. `vocero-api/tests/test_compatibility.py` compares HTTP
response bytes, statuses, selected headers, outgoing requests, request schemas,
and realtime frames. Do not regenerate captures merely to make a refactor pass.
- Known reasoning failures, ApiPilot authentication/cache quirks, and relay
@@ -148,9 +148,9 @@ evidence; do not bundle them into tooling or structural refactors.
## Browser and realtime behavior
- React renders the UI; `s2s-frontend/src/realtime/controller.ts` owns observable
application state, `s2s-frontend/src/realtime/client.ts` owns protocol/session
lifecycle, and `s2s-frontend/src/audio/` owns the audio graph and worklets.
- React renders the UI; `vocero-frontend/src/realtime/controller.ts` owns observable
application state, `vocero-frontend/src/realtime/client.ts` owns protocol/session
lifecycle, and `vocero-frontend/src/audio/` owns the audio graph and worklets.
Keep high-frequency audio/WebSocket work outside React rendering and preserve
cleanup across unmounts and reconnects.
- Preserve the existing 16 kHz PCM16 audio path, worklet registration, interruption
@@ -164,11 +164,11 @@ evidence; do not bundle them into tooling or structural refactors.
server/provider calls; do not assume a vector database or a completed magic-link
authentication flow exists.
- Assistant Markdown is parsed with Marked and sanitized with DOMPurify in
`s2s-frontend/src/lib/messages.ts`. User text stays literal, and exports retain
`vocero-frontend/src/lib/messages.ts`. User text stays literal, and exports retain
raw message text. Keep sanitization and unsafe-link regression coverage when
changing it.
- Reuse the existing design tokens, Tailwind integration, and
`s2s-frontend/src/styles/legacy-theme.css`. Verify both light and dark themes,
`vocero-frontend/src/styles/legacy-theme.css`. Verify both light and dark themes,
narrow screens, keyboard focus, dialog dismissal, and voice-state feedback for
UI changes.
Preserve intentional `?debug=true` controls and browser audio-permission flows.
@@ -178,8 +178,8 @@ evidence; do not bundle them into tooling or structural refactors.
## Startup and deployment boundaries
`./start_ui.sh` sources `.env`, syncs `s2s-client/.venv/` with
`uv sync --project s2s-client --locked`, and installs frontend packages when the
`./start_ui.sh` loads the optional root `.env`, syncs `vocero-api/.venv/` with
`uv sync --project vocero-api --locked`, and installs frontend packages when the
manifests change.
It starts Vite on `0.0.0.0:8420` and FastAPI on `127.0.0.1:8421`, with
`UI_HOST`, `UI_PORT`, and `S2S_BACKEND_PORT` overrides. Use distinct ports for
@@ -207,7 +207,7 @@ the local relay. Either disables load-balancer mode. Reasoning supports `PROXY`
and `OPENAI`; unknown `S2S_REASONING_TYPE` values fall back to `PROXY`.
The backend Docker image serves APIs, WebSockets, and root `docs/`, not the React
UI. Build with the repository root as context and `s2s-client/Dockerfile`; the
UI. Build with the repository root as context and `vocero-api/Dockerfile`; the
image listens on 7860. FastAPI's root remains 404, `/docs` is the API explorer,
and project documentation is served below `/docs/` with retained legacy redirects.
@@ -223,10 +223,10 @@ and project documentation is served below `/docs/` with retained legacy redirect
without language suffixes. Historical proposals do not define current behavior.
- Reserve root Markdown for established files such as `README.md` and `AGENTS.md`.
Keep task summaries in the conversation unless a saved artifact is requested.
Record notable changes under `Unreleased` in `s2s-client/CHANGELOG.md`.
Record notable changes under `Unreleased` in `vocero-api/CHANGELOG.md`.
- Keep credentials and deployment values out of commits and examples. Never copy
the root `.env` into the frontend or expose backend secrets through `VITE_*`.
The launcher sources `.env` before startup, so it can override shell values;
The launcher loads `.env` when present, so its values can override shell values;
direct Python startup uses `load_dotenv(..., override=False)`. Preserve both
behaviors and avoid importing live configuration when a test can isolate it.
+8 -8
View File
@@ -6,14 +6,14 @@ documentation, source identifiers, comments, and internal instructions use Engli
| Component | Purpose |
|---|---|
| [s2s-frontend](s2s-frontend) | React, TypeScript, and Tailwind browser UI, served by Vite with hot reload |
| [s2s-client](s2s-client) | FastAPI backend, realtime WebSocket proxy, search and ApiPilot bridges |
| [vocero-frontend](vocero-frontend) | React, TypeScript, and Tailwind browser UI, served by Vite with hot reload |
| [vocero-api](vocero-api) | FastAPI backend, realtime WebSocket proxy, search and ApiPilot bridges |
| [speech_to_speech](speech_to_speech) | Queue-driven VAD, speech recognition, language model, and speech synthesis pipeline |
Start with [client setup and usage](docs/client/usage.md), then read the
[architecture](docs/client/architecture.md) and [documentation index](docs/README.md).
The speech backend needs its own installed model and runtime dependencies;
The uv project in `s2s-client/` installs only the UI server dependencies.
The uv project in `vocero-api/` installs only the UI server dependencies.
## Run the browser client
@@ -24,7 +24,7 @@ S2S_PROXY_TARGET=ws://localhost:8765/v1/realtime ./start_ui.sh
```
Open <http://localhost:8420/> and allow microphone access. The launcher creates
`s2s-client/.venv`, syncs locked Python and frontend dependencies,
`vocero-api/.venv`, syncs locked Python and frontend dependencies,
and loads the root `.env` if present. Vite listens on `UI_PORT` (default `8420`)
and forwards HTTP/WebSocket backend routes to FastAPI on `127.0.0.1:8421`.
Both servers reload during development; stopping the launcher stops both.
@@ -33,16 +33,16 @@ Leave `SPEECH_TO_SPEECH_URL` unset to use the same-origin speech proxy.
The launcher requires uv 0.12.5 (which provisions Python 3.12), Node.js 22.18+ on the 22.x
line or Node.js 24+, npm, Bash 4.4+, `setsid`, and `curl`. For staging hostnames,
set `S2S_DEV_ALLOWED_HOSTS` and forward the Vite HMR WebSocket through the existing
HTTPS reverse proxy. See [development setup](s2s-frontend/README.md).
HTTPS reverse proxy. See [development setup](vocero-frontend/README.md).
The Docker image serves backend routes and shared documentation. The React
frontend runs separately through Vite. To build the backend image, use the
repository root as the Docker build context so documentation is included:
```bash
docker build -f s2s-client/Dockerfile -t s2s-client .
docker build -f vocero-api/Dockerfile -t vocero-api .
docker run --rm -p 7860:7860 \
-e SPEECH_TO_SPEECH_URL=ws://localhost:8765/v1/realtime s2s-client
-e SPEECH_TO_SPEECH_URL=ws://localhost:8765/v1/realtime vocero-api
```
The direct WebSocket URL is resolved by the user's browser. For a server-side
@@ -50,7 +50,7 @@ proxy deployment, supply a backend address reachable from the container instead.
## Project references
- [Change history](s2s-client/CHANGELOG.md)
- [Change history](vocero-api/CHANGELOG.md)
- [Backend development and compatibility](docs/client/development.md)
- [Realtime protocol and pipeline](docs/speech/realtime-api.md)
- [Context injection](docs/client/context-injection.md)
+1 -1
View File
@@ -15,7 +15,7 @@ do not define the current implementation.
- [Design language](client/design.md)
- [Glossary](client/glossary.md)
- [Troubleshooting](client/troubleshooting.md)
- [Change history](../s2s-client/CHANGELOG.md)
- [Change history](../vocero-api/CHANGELOG.md)
## Speech backend
+21 -21
View File
@@ -1,7 +1,7 @@
# Client architecture
The [React browser client](../../s2s-frontend) uses the
[FastAPI backend](../../s2s-client) for HTTP APIs and a speech WebSocket relay.
The [React browser client](../../vocero-frontend) uses the
[FastAPI backend](../../vocero-api) for HTTP APIs and a speech WebSocket relay.
`start_ui.sh` serves Vite on the existing public port `8420` and FastAPI on
loopback port `8421`. Vite forwards backend routes and provides hot reload;
Uvicorn reloads Python edits. The launcher supervises both processes.
@@ -42,22 +42,22 @@ audio. It is not an unconditional byte-for-byte tunnel.
| Source | Responsibility |
|---|---|
| [server.py](../../s2s-client/server.py) | Compatibility entry point for existing `server:app` commands |
| [main.py](../../s2s-client/s2s_client/main.py) | FastAPI assembly, documentation serving and redirects |
| [api](../../s2s-client/s2s_client/api) | HTTP route registration, handlers and compatible response validation |
| [models](../../s2s-client/s2s_client/models) | Pydantic request and response contracts |
| [core](../../s2s-client/s2s_client/core) | Existing environment parsing, logging, URL helpers and diagnostics |
| [providers](../../s2s-client/s2s_client/providers) | Caller context and ApiPilot HTTP integrations |
| [services](../../s2s-client/s2s_client/services) | Context construction, reasoning and tool-context rendering |
| [realtime](../../s2s-client/s2s_client/realtime) | WebSocket relay, events, markers and audio handling |
| [server.py](../../vocero-api/server.py) | Compatibility entry point for existing `server:app` commands |
| [main.py](../../vocero-api/vocero_api/main.py) | FastAPI assembly, documentation serving and redirects |
| [api](../../vocero-api/vocero_api/api) | HTTP route registration, handlers and compatible response validation |
| [models](../../vocero-api/vocero_api/models) | Pydantic request and response contracts |
| [core](../../vocero-api/vocero_api/core) | Existing environment parsing, logging, URL helpers and diagnostics |
| [providers](../../vocero-api/vocero_api/providers) | Caller context and ApiPilot HTTP integrations |
| [services](../../vocero-api/vocero_api/services) | Context construction, reasoning and tool-context rendering |
| [realtime](../../vocero-api/vocero_api/realtime) | WebSocket relay, events, markers and audio handling |
| [start_ui.sh](../../start_ui.sh) | Dependency setup, ports, readiness, reload processes, and shutdown |
| [vite.config.ts](../../s2s-frontend/vite.config.ts) | Public development endpoint, HMR, allowed hosts, and backend routing |
| [App.tsx](../../s2s-frontend/src/App.tsx) | React layout, voice controls, dialogs, profile and reports sidebar |
| [controller.ts](../../s2s-frontend/src/realtime/controller.ts) | Application state, settings, context, queue, tools and external reasoning |
| [client.ts](../../s2s-frontend/src/realtime/client.ts) | Realtime protocol, session lifecycle, audio and transcript events |
| [components](../../s2s-frontend/src/components) | Chat, caller profile, settings and voice components |
| [audio](../../s2s-frontend/src/audio) | TypeScript capture/playback worklets, PCM codec, and visualizer |
| [legacy-theme.css](../../s2s-frontend/src/styles/legacy-theme.css) | Retained themes and responsive visual styles alongside Tailwind |
| [vite.config.ts](../../vocero-frontend/vite.config.ts) | Public development endpoint, HMR, allowed hosts, and backend routing |
| [App.tsx](../../vocero-frontend/src/App.tsx) | React layout, voice controls, dialogs, profile and reports sidebar |
| [controller.ts](../../vocero-frontend/src/realtime/controller.ts) | Application state, settings, context, queue, tools and external reasoning |
| [client.ts](../../vocero-frontend/src/realtime/client.ts) | Realtime protocol, session lifecycle, audio and transcript events |
| [components](../../vocero-frontend/src/components) | Chat, caller profile, settings and voice components |
| [audio](../../vocero-frontend/src/audio) | TypeScript capture/playback worklets, PCM codec, and visualizer |
| [legacy-theme.css](../../vocero-frontend/src/styles/legacy-theme.css) | Retained themes and responsive visual styles alongside Tailwind |
## Server routes
@@ -79,9 +79,9 @@ explorer, while project documentation files are served in the `/docs/`
subdirectories. Markdown remains
source text, and directories without `index.html` are not browsable pages.
FastAPI serves backend and documentation routes only. The frontend source and
assets live in `s2s-frontend` and are served by Vite.
assets live in `vocero-frontend` and are served by Vite.
The uv project and its Python tools are scoped to `s2s-client/`. Response models
The uv project and its Python tools are scoped to `vocero-api/`. Response models
describe service-owned structure and retain loose upstream values. See
[development and compatibility](development.md) before tightening a contract.
@@ -89,11 +89,11 @@ describe service-owned structure and retain loose upstream values. See
1. The browser captures microphone audio with echo cancellation, noise
suppression and automatic gain-control settings.
2. [mic-capture.worklet.ts](../../s2s-frontend/src/audio/mic-capture.worklet.ts) resamples to 16 kHz
2. [mic-capture.worklet.ts](../../vocero-frontend/src/audio/mic-capture.worklet.ts) resamples to 16 kHz
mono PCM16. The browser sends base64 `input_audio_buffer.append` frames.
3. The backend processes VAD → STT → LLM → TTS and emits realtime events.
4. Assistant PCM16 audio at the current 16 kHz protocol rate is decoded and sent to
[audio-playback.worklet.ts](../../s2s-frontend/src/audio/audio-playback.worklet.ts), which buffers,
[audio-playback.worklet.ts](../../vocero-frontend/src/audio/audio-playback.worklet.ts), which buffers,
resamples and fades playback edges. Input speech can clear playback for barge-in.
Audio worklets are loaded through `AudioWorklet.addModule`; keep their processor
+4 -4
View File
@@ -2,10 +2,10 @@
Context reaches the assistant through browser-built instructions, server-patched
realtime session instructions, and HTTP reasoning context. The implementation is
in the server's [context services](../../s2s-client/s2s_client/services/context.py),
[realtime proxy](../../s2s-client/s2s_client/realtime/proxy.py),
[context.ts](../../s2s-frontend/src/lib/context.ts), and the
[WebSocket client](../../s2s-frontend/src/realtime/client.ts).
in the server's [context services](../../vocero-api/vocero_api/services/context.py),
[realtime proxy](../../vocero-api/vocero_api/realtime/proxy.py),
[context.ts](../../vocero-frontend/src/lib/context.ts), and the
[WebSocket client](../../vocero-frontend/src/realtime/client.ts).
## Independent controls
+2 -2
View File
@@ -1,8 +1,8 @@
# Design language
This guide describes the current browser UI. Use
[legacy-theme.css](../../s2s-frontend/src/styles/legacy-theme.css) as the token/selector
reference and [App.tsx](../../s2s-frontend/src/App.tsx) for the theme-specific structure.
[legacy-theme.css](../../vocero-frontend/src/styles/legacy-theme.css) as the token/selector
reference and [App.tsx](../../vocero-frontend/src/App.tsx) for the theme-specific structure.
Earlier documentation described a monochrome-only dark UI; the current design
also uses aurora gradients, glass surfaces, and a separate light theme.
+19 -12
View File
@@ -1,11 +1,18 @@
# Backend development and compatibility
The FastAPI project lives entirely in `s2s-client/`. It uses uv 0.12.5, Python
The FastAPI project lives entirely in `vocero-api/`. It uses uv 0.12.5, Python
3.12, and a committed `uv.lock`. The frontend and speech pipeline keep their
own tooling. Runtime dependency versions match the environment used to capture
the pre-migration server; framework upgrades require a separate compatibility
review.
The Python distribution is named `vocero-api`, and its import package is
`vocero_api`. The browser package and directory are both named `vocero-frontend`.
`start_ui.sh` creates the API environment at its current path on the next start;
do not move an existing virtual environment because its executables contain
absolute paths. Reinstall optional Git hooks after a project-directory rename
with `make -C vocero-api hooks-install`.
Pytest filters only the known `anyio.abc.BlockingPortal` deprecation emitted by
Starlette's test client ([upstream issue](https://github.com/Kludex/starlette/issues/3497)).
The filter matches the exact message, warning category, and module; other
@@ -17,24 +24,24 @@ the deprecated alias. Runtime dependencies and warning handling are unchanged.
From the repository root:
```bash
make -C s2s-client install # uv sync --locked, including development tools
make -C s2s-client format # Ruff safe fixes and formatting
make -C s2s-client check # lock freshness, format, lint, mypy, pytest
make -C s2s-client test-launcher # Bash supervisor integration fixtures
make -C s2s-client test-image # Docker build and isolated runtime smoke test
make -C vocero-api install # uv sync --locked, including development tools
make -C vocero-api format # Ruff safe fixes and formatting
make -C vocero-api check # lock freshness, format, lint, mypy, pytest
make -C vocero-api test-launcher # Bash supervisor integration fixtures
make -C vocero-api test-image # Docker build and isolated runtime smoke test
```
Run `uv lock` inside `s2s-client/` when intentionally changing dependencies.
Run `uv lock` inside `vocero-api/` when intentionally changing dependencies.
Do not install server packages into a shared root environment. The launcher
syncs `s2s-client/.venv` and retains root `.env` loading, port defaults,
syncs `vocero-api/.venv` and retains root `.env` loading, port defaults,
readiness checks, and process-group shutdown. Offline starts require the
locked packages and Python runtime to have been installed or cached already.
Optional hooks use `make -C s2s-client hooks-install`; the configuration stays
in `s2s-client/.pre-commit-config.yaml`. `make -C s2s-client hooks-run` runs the
Optional hooks use `make -C vocero-api hooks-install`; the configuration stays
in `vocero-api/.pre-commit-config.yaml`. `make -C vocero-api hooks-run` runs the
same hooks without installing them. Hooks and Python quality checks apply
only to the client service. The server entry points are `s2s_client.main:app`
and the retained `server:app` shim; use `--app-dir s2s-client` from the root.
only to the client service. The server entry points are `vocero_api.main:app`
and the retained `server:app` shim; use `--app-dir vocero-api` from the root.
## Schema boundaries
+4 -4
View File
@@ -13,7 +13,7 @@ The mode is normalized case-insensitively. Unknown values resolve to `PROXY`.
## Shared instruction assembly
In [context.ts](../../s2s-frontend/src/lib/context.ts), `buildInstructions()` starts
In [context.ts](../../vocero-frontend/src/lib/context.ts), `buildInstructions()` starts
with `config.primaryPrompt || settings.instructions`. `/api/config` pins
`S2S_PRIMARY_PROMPT` when non-empty; otherwise the settings prompt or
`DEFAULT_INSTRUCTIONS` is used. The current server does not fetch a remote agent
@@ -50,7 +50,7 @@ OPENAI_REASONING_MODEL=gpt-4.1-mini
`OPENAI_ORGANIZATION` and `OPENAI_PROJECT` are optional headers. The model value
is a runtime default, not a recommendation about provider availability.
`VoiceController.reason()` in [controller.ts](../../s2s-frontend/src/realtime/controller.ts)
`VoiceController.reason()` in [controller.ts](../../vocero-frontend/src/realtime/controller.ts)
runs after a complete user transcript and posts a body with `transcript`,
`instructions`, `language`, `user_context`, `phoneNumber`, and `episode_id`.
The server combines instructions and context into model messages, requests a
@@ -76,7 +76,7 @@ realtime backend instead.
| Goal | Source |
|---|---|
| Change the base agent prompt | `S2S_PRIMARY_PROMPT` or browser settings when unpinned |
| Change browser context assembly | `buildInstructions()` in `s2s-frontend/src/lib/context.ts` |
| Change HTTP provider messages | `reasoning()` in `s2s-client/s2s_client/services/reasoning.py` |
| Change browser context assembly | `buildInstructions()` in `vocero-frontend/src/lib/context.ts` |
| Change HTTP provider messages | `reasoning()` in `vocero-api/vocero_api/services/reasoning.py` |
| Change external-answer rendering | `VoiceController.reason()` and `RealtimeClient.renderExternalAnswer()` |
| Change native model/tool generation | `speech_to_speech/LLM` and realtime handlers |
+2 -2
View File
@@ -48,8 +48,8 @@ For a native call, the backend emits:
```
`arguments` is a JSON-encoded string. The
[WebSocket client](../../s2s-frontend/src/realtime/client.ts) validates the event and
dispatches `tool`; [controller.ts](../../s2s-frontend/src/realtime/controller.ts)
[WebSocket client](../../vocero-frontend/src/realtime/client.ts) validates the event and
dispatches `tool`; [controller.ts](../../vocero-frontend/src/realtime/controller.ts)
parses the arguments and runs the supported executor.
Web search uses `POST /api/search`, keeping the deployment's Serper key on the
server. Tool output returns to the model as:
+4 -4
View File
@@ -37,7 +37,7 @@ the separate `/v1/realtime` WebSocket on the same public origin.
For a startup dependency error, check uv 0.12.5 and Node/npm on the process
manager's PATH, plus `setsid`/`curl`. uv manages Python 3.12 in
`s2s-client/.venv` and checks the committed lock on every start. Frontend
`vocero-api/.venv` and checks the committed lock on every start. Frontend
dependencies are reused until their manifests change. Changes to root
`.env` or launcher settings require a restart.
@@ -123,9 +123,9 @@ without evidence.
From the repository root, run the frontend checks and local backend fixtures:
```bash
npm --prefix s2s-frontend run check
npm --prefix s2s-frontend run build
make -C s2s-client check
npm --prefix vocero-frontend run check
npm --prefix vocero-frontend run build
make -C vocero-api check
```
These checks cover frontend contracts and local session/queue/WebSocket
+11 -11
View File
@@ -12,8 +12,8 @@ From the repository root:
S2S_PROXY_TARGET=ws://localhost:8765/v1/realtime ./start_ui.sh
```
The launcher loads the root `.env`, syncs `s2s-client/.venv` from the committed
`s2s-client/uv.lock`, and installs the locked
The launcher loads the root `.env`, syncs `vocero-api/.venv` from the committed
`vocero-api/uv.lock`, and installs the locked
frontend dependencies when needed. Vite serves the React frontend on port
8420 and proxies the backend routes to Uvicorn on `127.0.0.1:8421`.
`UI_PORT` overrides the public port; `S2S_BACKEND_PORT` overrides the internal
@@ -24,21 +24,21 @@ the script, values in that file can replace variables supplied on the command li
Use Node.js 22.18+ on the 22.x line or Node.js 24+, npm, uv 0.12.5,
Bash 4.4+, `setsid`, and `curl`. Remote staging access also needs the exact
hostname in `S2S_DEV_ALLOWED_HOSTS`, HTTPS, and WebSocket forwarding for speech
and Vite HMR. See [frontend development setup](../../s2s-frontend/README.md).
and Vite HMR. See [frontend development setup](../../vocero-frontend/README.md).
To install and start the backend manually:
```bash
uv sync --project s2s-client --locked
uv sync --project vocero-api --locked
S2S_PROXY_TARGET=ws://localhost:8765/v1/realtime \
uv run --project s2s-client --locked uvicorn --app-dir s2s-client s2s_client.main:app \
--host 127.0.0.1 --port 8421 --reload --reload-dir s2s-client
uv run --project vocero-api --locked uvicorn --app-dir vocero-api vocero_api.main:app \
--host 127.0.0.1 --port 8421 --reload --reload-dir vocero-api
```
Then, in a second terminal, run `cd s2s-frontend`, `npm ci`, and `npm run dev`.
Then, in a second terminal, run `cd vocero-frontend`, `npm ci`, and `npm run dev`.
uv provisions Python 3.12 when needed. The Python environment belongs only to
`s2s-client`; an existing root `.venv` for speech dependencies is left alone.
`vocero-api`; an existing root `.venv` for speech dependencies is left alone.
See [development and compatibility](development.md) for checks and schema rules.
Open <http://localhost:8420/>, tap the orb/microphone control, grant microphone
@@ -94,9 +94,9 @@ backend address. The image does not run the development launcher or serve a UI.
Use the project root as the build context:
```bash
docker build -f s2s-client/Dockerfile -t s2s-client .
docker build -f vocero-api/Dockerfile -t vocero-api .
docker run --rm -p 7860:7860 \
-e SPEECH_TO_SPEECH_URL=ws://localhost:8765/v1/realtime s2s-client
-e SPEECH_TO_SPEECH_URL=ws://localhost:8765/v1/realtime vocero-api
```
For the proxy path, replace the direct setting with a server-reachable
@@ -104,7 +104,7 @@ For the proxy path, replace the direct setting with a server-reachable
appropriate existing network and use that service's hostname. An upstream
bound only to host loopback is not reachable via an arbitrary container name.
The image includes `s2s-client` and root `docs`. `/docs` is still the FastAPI API
The image includes `vocero-api` and root `docs`. `/docs` is still the FastAPI API
explorer; documentation lives under `/docs/client/`, `/docs/speech/`,
`/docs/adr/`, `/docs/examples/`, and `/docs/proposals/`. Legacy `/project/` and
`/public/docs.html` redirect to the historical proposal. Markdown is served as a
+12 -12
View File
@@ -2,9 +2,9 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="$ROOT_DIR/s2s-client"
VENV_DIR="$CLIENT_DIR/.venv"
FRONTEND_DIR="$ROOT_DIR/s2s-frontend"
API_DIR="$ROOT_DIR/vocero-api"
VENV_DIR="$API_DIR/.venv"
FRONTEND_DIR="$ROOT_DIR/vocero-frontend"
if [ -f "$ROOT_DIR/.env" ]; then
set -a
source "$ROOT_DIR/.env"
@@ -28,7 +28,7 @@ for required_command in uv node npm setsid curl; do
done
if ! node -e 'const [major, minor] = process.versions.node.split(".").map(Number); process.exit((major === 22 && minor >= 18) || major >= 24 ? 0 : 1)'; then
echo "Error: s2s-frontend requires Node.js 22.18+ on the 22.x line, or Node.js 24+."
echo "Error: vocero-frontend requires Node.js 22.18+ on the 22.x line, or Node.js 24+."
exit 1
fi
@@ -46,19 +46,19 @@ if [ "$UI_PORT" = "$S2S_BACKEND_PORT" ]; then
exit 1
fi
if [ ! -f "$CLIENT_DIR/pyproject.toml" ] || [ ! -f "$CLIENT_DIR/uv.lock" ]; then
echo "Error: s2s-client/pyproject.toml and s2s-client/uv.lock are required."
if [ ! -f "$API_DIR/pyproject.toml" ] || [ ! -f "$API_DIR/uv.lock" ]; then
echo "Error: vocero-api/pyproject.toml and vocero-api/uv.lock are required."
exit 1
fi
# uv manages only the client environment; an existing root speech venv is separate.
UV_PROJECT_ENVIRONMENT="$VENV_DIR" uv sync --project "$CLIENT_DIR" --locked
# uv manages only the API environment; an existing root speech venv is separate.
UV_PROJECT_ENVIRONMENT="$VENV_DIR" uv sync --project "$API_DIR" --locked
# Keep restarts offline when the installed frontend manifests have not changed.
if [ ! -f "$FRONTEND_DIR/node_modules/vite/bin/vite.js" ] || \
! cmp -s "$FRONTEND_DIR/package-lock.json" "$FRONTEND_DIR/node_modules/.s2s-package-lock.json" || \
! cmp -s "$FRONTEND_DIR/package.json" "$FRONTEND_DIR/node_modules/.s2s-package.json"; then
echo "Installing frontend dependencies from s2s-frontend/package-lock.json..."
echo "Installing frontend dependencies from vocero-frontend/package-lock.json..."
npm --prefix "$FRONTEND_DIR" ci --include=dev
cp "$FRONTEND_DIR/package-lock.json" "$FRONTEND_DIR/node_modules/.s2s-package-lock.json"
cp "$FRONTEND_DIR/package.json" "$FRONTEND_DIR/node_modules/.s2s-package.json"
@@ -109,10 +109,10 @@ echo " Press Ctrl+C in this terminal to stop both servers."
echo "----------------------------------------------------"
cd "$ROOT_DIR"
setsid "$VENV_DIR/bin/python" -m uvicorn s2s_client.main:app \
--app-dir "$CLIENT_DIR" \
setsid "$VENV_DIR/bin/python" -m uvicorn vocero_api.main:app \
--app-dir "$API_DIR" \
--host 127.0.0.1 --port "$S2S_BACKEND_PORT" \
--reload --reload-dir "$CLIENT_DIR" &
--reload --reload-dir "$API_DIR" &
backend_pid=$!
child_pids+=("$backend_pid")
@@ -7,21 +7,21 @@ repos:
hooks:
- id: ruff-check
name: Ruff lint
entry: uv run --project s2s-client --locked ruff check --force-exclude
entry: uv run --project vocero-api --locked ruff check --force-exclude
language: unsupported
types_or: [python, pyi]
files: ^s2s-client/
files: ^vocero-api/
require_serial: true
- id: ruff-format
name: Ruff formatting
entry: uv run --project s2s-client --locked ruff format --check --force-exclude
entry: uv run --project vocero-api --locked ruff format --check --force-exclude
language: unsupported
types_or: [python, pyi]
files: ^s2s-client/
files: ^vocero-api/
require_serial: true
- id: mypy
name: Python types
entry: uv run --directory s2s-client --locked mypy
entry: uv run --directory vocero-api --locked mypy
language: unsupported
pass_filenames: false
files: ^s2s-client/(s2s_client/|server\.py|pyproject\.toml|uv\.lock)
files: ^vocero-api/(vocero_api/|server\.py|pyproject\.toml|uv\.lock)
@@ -1,4 +1,4 @@
# Changelog — s2s-client (Azurian Vocero)
# Changelog — vocero-api (Azurian Vocero)
All significant client changes are recorded here. This English history
supersedes the former Italian and Spanish versions. Historical entries describe
@@ -9,20 +9,25 @@ the implementation at the recorded date; current operating instructions live in
### Changed
- Renamed the API and browser projects to `vocero-api/` and `vocero-frontend/`.
Python uses the `vocero-api` distribution and `vocero_api` import package;
npm uses `vocero-frontend`. Updated the launcher, Docker setup, tooling,
tests, and documentation while preserving HTTP/WebSocket contracts and
existing environment variables, browser preferences, and PM2 process names.
- Removed the optional workflow reasoning service, its configuration, transcript
forwarding, and browser integration. Reasoning supports `PROXY` and `OPENAI`;
unsupported mode values fall back to `PROXY`.
- Filter the specific Starlette TestClient / AnyIO `BlockingPortal` deprecation
in pytest while retaining the runtime versions used for compatibility captures.
- Added a uv-managed Python 3.12 environment and Ruff, mypy, and pytest quality
checks, all contained in `s2s-client/`. Startup and Docker use the committed
checks, all contained in `vocero-api/`. Startup and Docker use the committed
lockfile. Split the FastAPI service into configuration, route, provider,
context/reasoning, and realtime modules while retaining `server:app` startup.
- Added Pydantic response contracts with permissive upstream fields and original
payload serialization. Captured HTTP, request-schema, and realtime regression
fixtures preserve existing API behavior, including known failures.
- Removed the legacy browser site in favor of the React application in
`s2s-frontend`. FastAPI now serves only backend and documentation routes.
`vocero-frontend`. FastAPI now serves only backend and documentation routes.
- Retired daily talk-time accounting and the associated Hugging Face account
UI. Load-balancer allocation, queue lifecycle, caller profiles, upstream
authentication, and voice-session cleanup remain available.
@@ -1,4 +1,4 @@
# Build from the project root: docker build -f s2s-client/Dockerfile -t s2s-client .
# Build from the project root: docker build -f vocero-api/Dockerfile -t vocero-api .
# Serve backend APIs, the WebSocket proxy, and shared project documentation.
# Run the React/Vite frontend separately; this image contains no browser UI.
# HF Spaces (sdk: docker) routes traffic to $PORT, default 7860.
@@ -8,18 +8,18 @@ COPY --from=ghcr.io/astral-sh/uv:0.12.5 /uv /uvx /bin/
ENV UV_LINK_MODE=copy \
UV_PYTHON_DOWNLOADS=never \
PATH="/app/s2s-client/.venv/bin:$PATH"
PATH="/app/vocero-api/.venv/bin:$PATH"
WORKDIR /app/s2s-client
WORKDIR /app/vocero-api
COPY s2s-client/pyproject.toml s2s-client/uv.lock ./
COPY vocero-api/pyproject.toml vocero-api/uv.lock ./
RUN uv sync --locked --no-dev --no-install-project
COPY s2s-client/README.md s2s-client/server.py ./
COPY s2s-client/s2s_client/ ./s2s_client/
COPY vocero-api/README.md vocero-api/server.py ./
COPY vocero-api/vocero_api/ ./vocero_api/
COPY docs/ /app/docs/
RUN uv sync --locked --no-dev
EXPOSE 7860
CMD ["uvicorn", "s2s_client.main:app", "--host", "0.0.0.0", "--port", "7860"]
CMD ["uvicorn", "vocero_api.main:app", "--host", "0.0.0.0", "--port", "7860"]
+3 -3
View File
@@ -48,13 +48,13 @@ dev:
../start_ui.sh
hooks-install:
$(UV_RUN) pre-commit install --install-hooks --config s2s-client/.pre-commit-config.yaml
$(UV_RUN) pre-commit install --install-hooks --config vocero-api/.pre-commit-config.yaml
hooks-run:
$(UV_RUN) pre-commit run --all-files --config s2s-client/.pre-commit-config.yaml
$(UV_RUN) pre-commit run --all-files --config vocero-api/.pre-commit-config.yaml
test-launcher:
npm --prefix ../s2s-frontend run test:launcher
npm --prefix ../vocero-frontend run test:launcher
test-image:
bash scripts/test_docker_image.sh
+10 -9
View File
@@ -9,15 +9,15 @@ pinned: false
short_description: Voice chat over WebSocket against a HF speech-to-speech
---
# Realtime voice backend
# Vocero API
FastAPI APIs and WebSocket relay for the [React voice frontend](../s2s-frontend).
FastAPI APIs and WebSocket relay for the [React voice frontend](../vocero-frontend).
This service handles configuration, caller context, upstream authentication,
search, reasoning, ApiPilot tools, and optional load-balancer allocation.
The root `start_ui.sh` now serves the [React frontend](../s2s-frontend) with
The root `start_ui.sh` now serves the [React frontend](../vocero-frontend) with
Vite on public port `8420`, proxying backend requests here on loopback port
`8421`. The browser UI lives entirely in `s2s-frontend`; running FastAPI directly
`8421`. The browser UI lives entirely in `vocero-frontend`; running FastAPI directly
or through Docker exposes only backend routes and shared documentation.
The complete [usage guide](../docs/client/usage.md) covers local startup,
@@ -28,7 +28,7 @@ and [change history](CHANGELOG.md) for implementation details and history.
Build from the repository root so the container includes the documentation:
```bash
docker build -f s2s-client/Dockerfile -t s2s-client .
docker build -f vocero-api/Dockerfile -t vocero-api .
```
The Python project is contained in this directory: `pyproject.toml`, `uv.lock`,
@@ -36,8 +36,8 @@ The Python project is contained in this directory: `pyproject.toml`, `uv.lock`,
3.12 (uv can provision Python). From the repository root:
```bash
make -C s2s-client install
make -C s2s-client check
make -C vocero-api install
make -C vocero-api check
```
Tests isolate environment configuration, use HTTP fixtures for allocation and
@@ -46,8 +46,9 @@ frames, keepalive, and teardown. No deployed services or database are needed.
See [backend development and compatibility](../docs/client/development.md) for
formatting, typing, schema policy, optional Git hooks, and deployment checks.
`uvicorn server:app --app-dir s2s-client` remains a supported entry point;
the implementation now lives in `s2s_client/`.
`uvicorn server:app --app-dir vocero-api` remains a supported entry point;
the implementation lives in the `vocero_api/` package. Its Python distribution
name is `vocero-api`.
FastAPI's API explorer remains at `/docs`. Project documents are served as files below
`/docs/client/`, `/docs/speech/`, `/docs/adr/`, `/docs/examples/`, and
@@ -3,7 +3,7 @@ requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "aquaportal-s2s-client"
name = "vocero-api"
version = "0.1.0"
description = "FastAPI integration and realtime proxy for the Vocero frontend"
readme = "README.md"
@@ -29,12 +29,12 @@ dev = [
required-version = "==0.12.5"
[tool.setuptools.packages.find]
include = ["s2s_client*"]
include = ["vocero_api*"]
[tool.ruff]
target-version = "py312"
line-length = 120
include = ["pyproject.toml", "s2s_client/**/*.py", "server.py", "tests/**/*.py", "scripts/**/*.py"]
include = ["pyproject.toml", "vocero_api/**/*.py", "server.py", "tests/**/*.py", "scripts/**/*.py"]
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I", "B", "UP"]
@@ -48,7 +48,7 @@ line-ending = "auto"
[tool.mypy]
python_version = "3.12"
plugins = ["pydantic.mypy"]
files = ["s2s_client", "server.py"]
files = ["vocero_api", "server.py"]
check_untyped_defs = true
disallow_untyped_defs = true
no_implicit_optional = true
@@ -3,7 +3,7 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
IMAGE="s2s-client-smoke:$(date +%s)-$$"
IMAGE="vocero-api-smoke:$(date +%s)-$$"
container_id=""
cleanup() {
if [ -n "$container_id" ]; then
@@ -13,7 +13,7 @@ cleanup() {
}
trap cleanup EXIT
docker build --file "$ROOT_DIR/s2s-client/Dockerfile" --tag "$IMAGE" "$ROOT_DIR"
docker build --file "$ROOT_DIR/vocero-api/Dockerfile" --tag "$IMAGE" "$ROOT_DIR"
container_id="$(docker run --detach --publish 127.0.0.1::7860 "$IMAGE")"
port="$(docker port "$container_id" 7860/tcp)"
ready=false
@@ -3,7 +3,7 @@
import sys
from pathlib import Path
# Also support --app-dir /path/to/s2s-client when launched outside the checkout.
# Also support --app-dir /path/to/vocero-api when launched outside the checkout.
sys.path.insert(0, str(Path(__file__).resolve().parent))
from s2s_client.main import app as app # noqa: E402
from vocero_api.main import app as app # noqa: E402
@@ -17,11 +17,11 @@ import httpx
from fastapi.testclient import TestClient
with patch.dict(os.environ, {}, clear=True), patch("dotenv.load_dotenv", return_value=False):
from s2s_client import main as server
from s2s_client.core import config as settings
from s2s_client.providers import apipilot
from s2s_client.realtime import proxy
from s2s_client.services import context
from vocero_api import main as server
from vocero_api.core import config as settings
from vocero_api.providers import apipilot
from vocero_api.realtime import proxy
from vocero_api.services import context
ASYNC_CLIENT = httpx.AsyncClient
FIXTURE = Path(__file__).parent / "fixtures" / "legacy-contracts.json"
@@ -4,9 +4,9 @@ import pytest
from fastapi.responses import JSONResponse
from pydantic import ValidationError
from s2s_client.api.responses import validated_json_response
from s2s_client.models.requests import ApipilotExecuteRequest, QueueEndPayload, SearchRequest
from s2s_client.models.responses import (
from vocero_api.api.responses import validated_json_response
from vocero_api.models.requests import ApipilotExecuteRequest, QueueEndPayload, SearchRequest
from vocero_api.models.responses import (
AllocationResponse,
QueueCancellationResponse,
ReasoningResponse,
@@ -58,7 +58,7 @@ def test_request_models_keep_legacy_extra_field_and_argument_behavior():
def test_http_routes_publish_response_schemas():
# Import the app only after the compatibility suite's isolated configuration.
from s2s_client.main import app
from vocero_api.main import app
spec = app.openapi()
for path, operations in spec["paths"].items():
@@ -73,6 +73,13 @@ def test_http_routes_publish_response_schemas():
assert config["properties"]["reasoningType"]["enum"] == ["PROXY", "OPENAI"]
def test_legacy_entry_point_exports_the_renamed_application():
import server
from vocero_api.main import app
assert server.app is app
@pytest.mark.parametrize(
("value", "expected"),
[
@@ -85,6 +92,6 @@ def test_http_routes_publish_response_schemas():
],
)
def test_reasoning_configuration_preserves_normalization_and_native_fallback(value, expected):
from s2s_client.core.config import _normalize_reasoning_type
from vocero_api.core.config import _normalize_reasoning_type
assert _normalize_reasoning_type(value) == expected
@@ -1,6 +1,6 @@
"""Backend contract checks with local HTTP fixtures and a loopback voice server.
Run from s2s-client: uv run --locked pytest tests
Run from vocero-api: uv run --locked pytest tests
"""
import json
@@ -16,10 +16,10 @@ from websockets.sync.server import serve
# Never consume deployment credentials or contact services from a developer's .env.
with patch.dict(os.environ, {}, clear=True), patch("dotenv.load_dotenv", return_value=False):
from s2s_client import main as server
from s2s_client.core import config as settings
from s2s_client.providers import apipilot, caller
from s2s_client.services import tool_context
from vocero_api import main as server
from vocero_api.core import config as settings
from vocero_api.providers import apipilot, caller
from vocero_api.services import tool_context
GRANT = {
+39 -39
View File
@@ -24,45 +24,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b8/4bd346e22b28902df4d651910f5242c28d84e4a5c2435ca5c3f797ed7e2e/anyio-4.15.1-py3-none-any.whl", hash = "sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101", size = 132079, upload-time = "2026-09-05T10:42:37.923Z" },
]
[[package]]
name = "aquaportal-s2s-client"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "fastapi" },
{ name = "httpx" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "uvicorn", extra = ["standard"] },
{ name = "websockets" },
]
[package.dev-dependencies]
dev = [
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "fastapi", specifier = "==0.115.6" },
{ name = "httpx", specifier = "==0.28.1" },
{ name = "pydantic", specifier = "==2.13.5" },
{ name = "python-dotenv", specifier = "==1.2.2" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.0" },
{ name = "websockets", specifier = "==17.2" },
]
[package.metadata.requires-dev]
dev = [
{ name = "mypy", specifier = ">=1.16,<2" },
{ name = "pre-commit", specifier = ">=4.4,<5" },
{ name = "pytest", specifier = ">=9,<10" },
{ name = "ruff", specifier = ">=0.12,<1" },
]
[[package]]
name = "certifi"
version = "2026.7.22"
@@ -546,6 +507,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/93/084f10ebbecdedf7902ed68fbecc903f0e9c5742155bfffb05eccbc071da/virtualenv-21.14.5-py3-none-any.whl", hash = "sha256:b0651e0174982bba17cc6f0aaa2ef496730cb85a3f35eb5d1119d1c904f7d1da", size = 5487482, upload-time = "2026-10-02T17:42:23.38Z" },
]
[[package]]
name = "vocero-api"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "fastapi" },
{ name = "httpx" },
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "uvicorn", extra = ["standard"] },
{ name = "websockets" },
]
[package.dev-dependencies]
dev = [
{ name = "mypy" },
{ name = "pre-commit" },
{ name = "pytest" },
{ name = "ruff" },
]
[package.metadata]
requires-dist = [
{ name = "fastapi", specifier = "==0.115.6" },
{ name = "httpx", specifier = "==0.28.1" },
{ name = "pydantic", specifier = "==2.13.5" },
{ name = "python-dotenv", specifier = "==1.2.2" },
{ name = "uvicorn", extras = ["standard"], specifier = "==0.34.0" },
{ name = "websockets", specifier = "==17.2" },
]
[package.metadata.requires-dev]
dev = [
{ name = "mypy", specifier = ">=1.16,<2" },
{ name = "pre-commit", specifier = ">=4.4,<5" },
{ name = "pytest", specifier = ">=9,<10" },
{ name = "ruff", specifier = ">=0.12,<1" },
]
[[package]]
name = "watchfiles"
version = "1.3.0"
@@ -2,10 +2,10 @@
from fastapi import APIRouter
from s2s_client.api.routes import apipilot, caller, config, search, sessions
from s2s_client.models.responses import ReasoningResponse
from s2s_client.realtime.proxy import realtime_proxy
from s2s_client.services.reasoning import reasoning
from vocero_api.api.routes import apipilot, caller, config, search, sessions
from vocero_api.models.responses import ReasoningResponse
from vocero_api.realtime.proxy import realtime_proxy
from vocero_api.services.reasoning import reasoning
router = APIRouter()
router.include_router(config.router)
@@ -3,12 +3,12 @@ from __future__ import annotations
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.models import requests as models_requests
from s2s_client.models.responses import ToolCatalogResponse, ToolExecutionResponse
from s2s_client.providers import apipilot as providers_apipilot
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.models import requests as models_requests
from vocero_api.models.responses import ToolCatalogResponse, ToolExecutionResponse
from vocero_api.providers import apipilot as providers_apipilot
router = APIRouter()
@@ -6,12 +6,12 @@ import httpx
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.models.responses import ReportsResponse, UserProfileResponse
from s2s_client.providers import caller as providers_caller
from s2s_client.services import context as services_context
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.models.responses import ReportsResponse, UserProfileResponse
from vocero_api.providers import caller as providers_caller
from vocero_api.services import context as services_context
router = APIRouter()
@@ -3,11 +3,11 @@ from __future__ import annotations
from fastapi import APIRouter, Request
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.core import urls as core_urls
from s2s_client.models.responses import ConfigResponse
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.core import urls as core_urls
from vocero_api.models.responses import ConfigResponse
router = APIRouter()
@@ -6,11 +6,11 @@ import httpx
from fastapi import APIRouter, HTTPException
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.models import requests as models_requests
from s2s_client.models.responses import SearchResponse
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.models import requests as models_requests
from vocero_api.models.responses import SearchResponse
router = APIRouter()
@@ -6,11 +6,11 @@ import httpx
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.models.requests import QueueEndPayload
from s2s_client.models.responses import AllocationResponse, QueueCancellationResponse
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.models.requests import QueueEndPayload
from vocero_api.models.responses import AllocationResponse, QueueCancellationResponse
router = APIRouter()
@@ -4,7 +4,7 @@ import os
from dotenv import load_dotenv
from s2s_client.core import logging as app_logging
from vocero_api.core import logging as app_logging
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
load_dotenv(os.path.join(ROOT_DIR, ".env"), override=False)
@@ -1,7 +1,7 @@
from __future__ import annotations
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
# Resolve the execution URL for the startup banner using the same normalization
@@ -4,7 +4,7 @@ from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
from fastapi import Request, WebSocket
from s2s_client.core import config as settings
from vocero_api.core import config as settings
def _url_with_query(url: str, **params: str) -> str:
@@ -6,8 +6,8 @@ from fastapi import FastAPI
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles
from s2s_client.api.router import router
from s2s_client.core import config, diagnostics # noqa: F401 - retain the startup banner
from vocero_api.api.router import router
from vocero_api.core import config, diagnostics # noqa: F401 - retain the startup banner
app = FastAPI(title="s2s-demo")
app.include_router(router)
@@ -9,8 +9,8 @@ from typing import Any, cast
import httpx
from fastapi import HTTPException, Request
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
# In-process cache and asynchronous lock: one caller refreshes while others
# read the stale entry. `_APIPILOT_CACHE[auth_fingerprint] = (fetched_at_monotonic, tools_list)`.
@@ -6,8 +6,8 @@ from urllib.parse import urlencode
import httpx
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
async def _fetch_reports_list(phone_number: str | None = None, auth_token: str | None = None) -> list | dict | None:
@@ -4,9 +4,9 @@ import json
import re
from typing import Any, cast
from s2s_client.core import logging as app_logging
from s2s_client.services import context as services_context
from s2s_client.services import tool_context as services_tool_context
from vocero_api.core import logging as app_logging
from vocero_api.services import context as services_context
from vocero_api.services import tool_context as services_tool_context
async def _report_server_debug(hypothesis_id: str, location: str, msg: str, data: dict | None = None) -> None:
@@ -10,14 +10,14 @@ from fastapi import WebSocket, WebSocketDisconnect
from websockets.asyncio.client import ClientConnection
from websockets.typing import Subprotocol
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.core import urls as core_urls
from s2s_client.providers import apipilot as providers_apipilot
from s2s_client.providers import caller as providers_caller
from s2s_client.realtime import events as realtime_events
from s2s_client.services import context as services_context
from s2s_client.services import tool_context as services_tool_context
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.core import urls as core_urls
from vocero_api.providers import apipilot as providers_apipilot
from vocero_api.providers import caller as providers_caller
from vocero_api.realtime import events as realtime_events
from vocero_api.services import context as services_context
from vocero_api.services import tool_context as services_tool_context
async def realtime_proxy(ws: WebSocket) -> None:
@@ -4,9 +4,9 @@ import datetime as _dt_module
import json
from typing import Any, cast
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.services import tool_context as services_tool_context
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.services import tool_context as services_tool_context
VOCERO_USER_CONTEXT_HINT = " Caller profile resolved from the page phoneNumber: "
REPORTS_LIST_HINT = (
@@ -6,15 +6,15 @@ import httpx
from fastapi import HTTPException
from fastapi.responses import JSONResponse
from s2s_client.api.responses import validated_json_response
from s2s_client.core import config as settings
from s2s_client.core import logging as app_logging
from s2s_client.models import requests as models_requests
from s2s_client.models.responses import ReasoningResponse
from s2s_client.providers import apipilot as providers_apipilot
from s2s_client.providers import caller as providers_caller
from s2s_client.services import context as services_context
from s2s_client.services import tool_context as services_tool_context
from vocero_api.api.responses import validated_json_response
from vocero_api.core import config as settings
from vocero_api.core import logging as app_logging
from vocero_api.models import requests as models_requests
from vocero_api.models.responses import ReasoningResponse
from vocero_api.providers import apipilot as providers_apipilot
from vocero_api.providers import caller as providers_caller
from vocero_api.services import context as services_context
from vocero_api.services import tool_context as services_tool_context
def _openai_headers() -> dict[str, str]:
@@ -4,7 +4,7 @@ import json
import re
from typing import cast
from s2s_client.core import logging as app_logging
from vocero_api.core import logging as app_logging
# ---------------------------------------------------------------------------
# APIPILOT — Reasoning engine integration (two passes)
@@ -1,7 +1,7 @@
# Vocero React frontend
React 19, TypeScript, Tailwind CSS 4, and Vite 8 client for the existing
`s2s-client` FastAPI backend. This is a client-side application with React-owned
`vocero-api` FastAPI backend. This is a client-side application with React-owned
rendering and state, including the realtime WebSocket and audio integration.
## Run during development
@@ -10,9 +10,9 @@ From the repository root, run `./start_ui.sh`. Open `http://localhost:8420`.
Add `?debug=true` to access settings, tools, and custom RAG context. Existing
`phoneNumber` and `episode_id` query parameters are supported.
The launcher requires Python 3 with venv support, Node.js 22.18 or later on the
22.x line or Node.js 24+, npm, Bash 4.4+, `setsid` (util-linux), and `curl`.
It creates `.venv` and installs missing Python dependencies. It runs
The launcher requires uv 0.12.5, Node.js 22.18 or later on the 22.x line or
Node.js 24+, npm, Bash 4.4+, `setsid` (util-linux), and `curl`.
It provisions Python 3.12 and syncs `vocero-api/.venv` from the API lockfile. It runs
`npm ci --include=dev` on first start and when either frontend package manifest
changes. Subsequent restarts reuse the installed dependencies.
@@ -26,7 +26,7 @@ changes. Subsequent restarts reuse the installed dependencies.
must differ. Occupied ports fail startup rather than silently selecting a new
endpoint. The launcher waits for FastAPI before starting Vite, stops both
process groups on Ctrl+C/SIGTERM, and exits with a failure if either server exits.
Uvicorn watches only `s2s-client`, avoiding frontend and dependency directories.
Uvicorn watches only `vocero-api`, avoiding frontend and dependency directories.
The Vite child explicitly uses `NODE_ENV=development`, including when a process
manager sets `NODE_ENV=production`, so React Fast Refresh remains enabled.
@@ -49,8 +49,8 @@ variables, which are exposed to browser code.
## Staging with hot reload
PM2 can continue running the root `start_ui.sh` as its existing `s2s_ui` process.
Ensure the Node/npm versions above are on PM2's PATH and deploy `s2s-frontend`
alongside `s2s-client` and `docs`. The reverse proxy still targets port `8420`.
Ensure uv and the Node/npm versions above are on PM2's PATH and deploy `vocero-frontend`
alongside `vocero-api` and `docs`. The reverse proxy still targets port `8420`.
Set the exact public hostname in the root `.env`, without scheme or port:
@@ -74,7 +74,7 @@ Start FastAPI on `127.0.0.1:8421` as described in the
[client usage guide](../docs/client/usage.md), then run:
```sh
cd s2s-frontend
cd vocero-frontend
npm ci
npm run dev
```
@@ -64,21 +64,21 @@ async function waitForFile(filename: string) {
async function launcher(context: TestContext, env: Record<string, string> = {}) {
const root = await mkdtemp(join(tmpdir(), "s2s-launcher-"));
const frontend = join(root, "s2s-frontend");
const frontend = join(root, "vocero-frontend");
const modules = join(frontend, "node_modules");
await mkdir(join(root, "s2s-client/.venv/bin"), { recursive: true });
await mkdir(join(root, "vocero-api/.venv/bin"), { recursive: true });
await mkdir(join(root, "bin"));
await mkdir(join(modules, "vite/bin"), { recursive: true });
await copyFile(new URL("../../start_ui.sh", import.meta.url), join(root, "start_ui.sh"));
await writeFile(join(root, "s2s-client/pyproject.toml"), "");
await writeFile(join(root, "s2s-client/uv.lock"), "");
await writeFile(join(root, "vocero-api/pyproject.toml"), "");
await writeFile(join(root, "vocero-api/uv.lock"), "");
await writeFile(
join(root, "bin/uv"),
'#!/bin/bash\nprintf "%s\\n" "$@" > "$S2S_TEST_ROOT/uv-args"\nprintf "%s" "$UV_PROJECT_ENVIRONMENT" > "$S2S_TEST_ROOT/uv-environment"\nexit "${S2S_TEST_UV_EXIT:-0}"\n',
{ mode: 0o755 },
);
await writeFile(
join(root, "s2s-client/.venv/bin/python"),
join(root, "vocero-api/.venv/bin/python"),
'#!/bin/bash\nif [ "$1" = "-c" ]; then exit 0; fi\nexec node "$S2S_TEST_ROOT/child.mjs" backend "$@"\n',
{ mode: 0o755 },
);
@@ -146,9 +146,30 @@ for (const signal of ["SIGINT", "SIGTERM"] as const) {
assert.equal(run.output().includes("Installing"), false);
assert.equal(
await readFile(join(run.root, "uv-args"), "utf8"),
`sync\n--project\n${run.root}/s2s-client\n--locked\n`,
`sync\n--project\n${run.root}/vocero-api\n--locked\n`,
);
assert.equal(await readFile(join(run.root, "uv-environment"), "utf8"), `${run.root}/s2s-client/.venv`);
assert.equal(await readFile(join(run.root, "uv-environment"), "utf8"), `${run.root}/vocero-api/.venv`);
const backendMetadata: unknown = JSON.parse(await readFile(join(run.root, "backend.json"), "utf8"));
assert.deepEqual(backendMetadata, {
args: [
"backend",
"-m",
"uvicorn",
"vocero_api.main:app",
"--app-dir",
`${run.root}/vocero-api`,
"--host",
"127.0.0.1",
"--port",
String(run.backendPort),
"--reload",
"--reload-dir",
`${run.root}/vocero-api`,
],
target: `http://127.0.0.1:${run.backendPort}`,
hosts: "s2s.test",
nodeEnv: "production",
});
run.child.kill(signal);
assert.equal(await run.exited, signal === "SIGINT" ? 130 : 143);
for (const role of ["backend", "frontend"]) {
@@ -1,11 +1,11 @@
{
"name": "s2s-frontend",
"name": "vocero-frontend",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "s2s-frontend",
"name": "vocero-frontend",
"version": "0.0.0",
"dependencies": {
"dompurify": "^3.4.16",
@@ -1,5 +1,5 @@
{
"name": "s2s-frontend",
"name": "vocero-frontend",
"private": true,
"version": "0.0.0",
"type": "module",

Some files were not shown because too many files have changed in this diff Show More