# Разговор WebRTC V3 без Codex

Переносимый запуск: команды ниже выполняются из корня комплекта, не из каталога этой страницы. Подготовка описана в [AUDIO-README.md](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/AUDIO-README.md>).

Этот клиент открывает голосовой разговор сам. Codex CLI, локальный app-server,
Electron и `codex-voice-host` ему не нужны. От них здесь остался протокол:
создание call через HTTPS, звук через WebRTC и служебные события через отдельный
WSS sideband. Контракт сверён с исходниками CLI **0.156.1**, тег
`rust-v0.156.1`, commit `81e8e29b2956dfe9b092c63953a9ed282781e77c`.

**Живая файловая проба 30 сентября 2026 прошла именно по WebRTC V3.**
Получены SDP answer и call ID, передано 682 и принято 610 RTP-пакетов,
пришли финальные transcript пользователя и ассистента. Ответ — WAV PCM16 mono
48 кГц, 12,12 с, 1 163 564 байта; ffmpeg полностью его декодировал.
В WAV входит весь принятый поток, включая тишину, а не только речь.
Личные реплики и сам ответ в репозиторий не помещены. Основной auth.json не изменён.
Это результат одного аккаунта и даты, не обещание доступа для любого входа.

Финальная версия того же дня повторно прошла V3: 722RTP отправлено, 651 принято,
есть обе финальные реплики и полностью декодируемый ответ. Дополнительные
проверки не дают принять приветствие до user turn за ответ на файл, блокируют
WSS redirect и освобождают остальные ресурсы даже при ошибке закрытия capture.

## Установка и запуск

Скопируйте этот каталог куда удобно. Скрипт не импортирует файлы репозитория.
Нужны Python 3.12, PortAudio для микрофона/динамиков и отдельный venv.
На этой машине PortAudio уже установлен; зависимости поставлены только в
`.runtime/voice/`.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
voice_demo="$repo/scripts/webrtc-voice-isolated"
voice_venv="$voice_venv"
python3 -m venv "$voice_venv"
"$voice_venv/bin/pip" install -r "$voice_demo/requirements.txt"

# Ни сеть, ни чтение входа, ни микрофон здесь не используются.
"$voice_venv/bin/python" "$voice_demo/voice-console.py" --dry-run --auth-file "$auth_file"
"$voice_venv/bin/python" "$voice_demo/voice-console.py" --list-voices --auth-file "$auth_file"
"$voice_venv/bin/python" "$voice_demo/voice-console.py" --list-devices --auth-file "$auth_file"

# Только запускает консоль. Разговор начнётся после ввода /voice.
"$voice_venv/bin/python" "$voice_demo/voice-console.py"    --auth-file "$auth_file" --voice cove
```

В консоли `/voice` начинает разговор, повторный `/voice` его останавливает.
Микрофон после соединения работает постоянно. `/voice mute` переключает mute:
захват прекращается, очередь очищается, по WebRTC идут нулевые сэмплы.
Повторная команда возобновляет захват, но не отправляет старую очередь.
`/voice settings` показывает устройства, выбранные input/output и голос.
`/quit`, EOF и Ctrl+C закрывают собственные устройства, peer connection,
sideband и HTTP-сессию. Наушники нужны: полноценного AEC здесь нет.

Устройства задаются до запуска через `--input-device N --output-device N`.
Их номера зависят от машины. Выбор всех физических устройств в живом разговоре
не проверялся; файловая проба не открывает микрофон или динамики.

Каталог V3 этой версии: `juniper`, `maple`, `spruce`, `ember`, `vale`,
`breeze`, `arbor`, `sol`, `cove`. Default — `cove`. Наличие имени в каталоге
не означает, что каждый голос проверен синтезом. Живая проба использовала cove.
В V3 это каталог V1; голоса GA V2 вроде marin здесь не подставляются.

## Файловая проверка без участия пользователя

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
voice_demo="$repo/scripts/webrtc-voice-isolated"
voice_python="$voice_venv/bin/python"
voice_out="$(mktemp -d "$audio_out/voice-v3.XXXXXX")"

# Перекодирование, не локальное распознавание речи.
ffmpeg -nostdin -v error -n -i "$INPUT_AUDIO"    -ar 48000 -ac 1 -c:a pcm_s16le "$voice_out/input.wav"

"$voice_python" "$voice_demo/voice-console.py"    --file "$voice_out/input.wav" --output "$voice_out/answer.wav"    --auth-file "$auth_file" --timeout 30 --max-seconds 45

# Полное декодирование и параметры, а не только заголовок файла.
ffmpeg -nostdin -v error -i "$voice_out/answer.wav" -f null -
ffprobe -v error -show_entries stream=codec_name,sample_rate,channels,bits_per_sample    -show_entries format=duration,size -of json "$voice_out/answer.wav"
```

В файловом режиме captions не печатаются: в результате только их длины,
RTP-счётчики и путь к файлу. Успех требует отправленных и принятых RTP-пакетов,
непустых финальных реплик обеих сторон и ненулевого декодированного сигнала.
Проверка рассчитана на запись одной реплики. После файла клиент посылает тишину
и сохраняет полученный звук. Ранний assistant-final и фиксированный хвост 1 с
не доказывают полноту ответа на весь многорепликовый файл или полный media drain.
Таймаут или только SDP без медиа не объявляются успешным разговором. WAV имеет права 0600;
существующий файл не перезаписывается.

## Что отправляется

1. `POST https://chatgpt.com/backend-api/codex/realtime/calls?intent=quicksilver&architecture=avas`.
   JSON содержит `sdp` и `session`: instructions, model=`gpt-live-1-codex`,
   audio.output.voice и delegation.type=`client`. SDP — параметры соединения,
   не аудиофайл. Ответ — plain SDP answer и Location с call ID.
2. После answer клиент подключается к `wss://api.openai.com/v1/live/<call_id>`.
   Повторный session.update не отправляется: session уже задана в POST.
   Sideband даёт input/output_transcript.added и turn.done. Аудио из возможного
   output_audio.delta не воспроизводится второй раз.
3. Звук передаётся Opus-пакетами через DTLS-SRTP. Для исходного PCM клиента
   используется mono PCM16 48 кГц, кадры 960 сэмплов/20 мс. aiortc 1.15.0
   внутри преобразует mono в stereo для своего libopus encoder; Mono подтверждён для synthetic silence CLI helper;
   параметры живого encoder в сохранённом снимке не установлены. SDP у обоих объявляет opus/48000/2. RTP clock — не контейнер
   и не доказательство stereo исходной записи. Клиенты семантически совместимы,
   но их SDP, payload types, ICE, Opus-байты и сетевые отпечатки не одинаковы.

Bearer и account ID читаются непосредственно из выбранного auth.json.
Также отправляются протокольный `OpenAI-Alpha: quicksilver=v2`, новый x-session-id
и честные идентификаторы нашего клиента: originator=`codex_voice_isolated`,
User-Agent=`codex-voice-isolated/0.1 aiortc/1.15.0`.
`quicksilver=v2` здесь — значение alpha-заголовка для V3 в исходниках CLI,
а не переключение на обычный Realtime WebSocket V2.

Первоначально без alpha-заголовка сервер вернул HTTP 400 с
`invalid_quicksilver_alpha_header`. После сверки `realtime_request_headers`
заголовок добавлен, и проба прошла. Кроме того, исправлен default model:
необязательный model в RPC ядро CLI перед HTTP дополняет gpt-live-1-codex.
По первым отказам нельзя утверждать, что именно отсутствие model их вызвало.

## Ограничения и безопасность

Клиент не обновляет токен и не меняет основной профиль. При 401 нужен свежий
собственный вход, полученный обычным способом. Cookies и integrity/attestation
не читаются, не копируются и не подделываются. HTTP redirect запрещён,
автоматические HTTP proxy окружения отключены, TLS проверяется. Sideband
подключается только к зафиксированному хосту, не к адресу из Location.
Полный SDP содержит IP и ICE credentials; он не печатается и не сохраняется.

Это **только разговор**. При delegation.created клиент сообщает о неподдерживаемой
агентной работе и закрывает сеанс. Нет thread/turn истории Codex, tools,
permissions, файлового контекста, выполнения команд, MCP или handoff в агент.
В демо перенесены только создание соединения, чтение своего входа, настройка
голоса, sideband captions, медиа и завершение. При отказе V3 клиент не делает
fallback на GA WebSocket и не объявляет другой транспорт тем же результатом.

## Проверки и code-map

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
"$voice_venv/bin/python" -W error::ResourceWarning \
  -m unittest discover \
  -s "$repo/scripts/webrtc-voice-isolated" \
  -p 'test_*.py' -v
```

Тесты используют синтетический вход и локальные HTTP/RTC peers. Проверяют
реальную передачу Opus, RTP и декодирование без сервиса, mute и запоздавший
callback, выравнивание кадров, WAV-валидацию, отказ 403, безопасное закрытие,
V3 captions, делегацию, dry-run после копирования и сохранность входа.

| Блок | Наш исходник | Штатный источник CLI 0.156.1 |
| --- | --- | --- |
| Чтение входа/заголовки | voice-console.py: read_auth | core/realtime_conversation.rs: realtime_request_headers; core/client.rs |
| Call JSON | call_body | core/realtime_conversation.rs: build_realtime_session_config; codex-api/.../methods_frameless_bidi.rs: session_json |
| SDP/call/sideband | VoiceSession._start | codex-api/.../realtime_call.rs: create_with_session_and_headers; realtime_websocket/methods.rs |
| PCM/mute | InputTrack | voice-host/src/audio_track.rs |
| Captions | caption, receive_events | realtime_websocket/protocol_frameless_bidi.rs |
| Аудиоответ | receive_audio | voice-host/src/transport.rs; aiortc/codecs/opus.py |
| Завершение | close, console, file_probe | realtime_websocket/methods.rs: close; TUI realtime lifecycle |

Полные кликабельные схемы, главы остальных аудиоспособов и карта кода:
[HTML-учебник](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/codex-cli-voice-textbook.html#voice-console>).
Исходники закрытого голосового сервиса нам недоступны; описан наблюдаемый контракт.
