# Проверка удалённого озвучивания с локальным входом Codex

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

Проверено **30 сентября 2026** в ChatGPT Community с bundled Codex **0.159.0**.
Цель — получить настоящие ответы аудиосервисов, используя авторизацию из
локальных данных Codex, без запуска или перезапуска Desktop.

**Аудио получено тремя дополнительными путями:** прямой OpenAI Realtime,
ChatGPT Read Aloud для существующего ответа и Codex app-server `appendSpeech`
в изолированном экспериментальном режиме авторизации. Настоящего Platform
key в проверенных профилях не было. Это результаты конкретного входа и даты,
не гарантия доступности этих способов для любого аккаунта.

Самодостаточные демо с `--voice` и `--list-voices`, не импортирующие код форка,
находятся в [remote-tts-isolated](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/README.md>).
В этой папке отдельно описаны прямой Realtime, ChatGPT Read Aloud и appendSpeech.

## Результаты запросов

| Маршрут | Фактический ответ | Вывод |
| --- | --- | --- |
| `POST https://api.openai.com/v1/audio/speech` | **429**, `error.code=credit_balance_exhausted`, `error.type=insufficient_quota` | Аудио не получено. Сервис сообщает об отсутствии кредитов. Отказа 401/403 в этих пробах не было; это не доказывает полноценную поддержку ChatGPT-токена всеми Platform API |
| `wss://api.openai.com/v1/realtime?model=gpt-realtime` | Сеанс установлен, получены `session.created`, `session.updated`, аудиочанки и завершённый `response.done` | **Работает:** WAV 24 кГц, mono, PCM16, 6,3 с; обратная удалённая STT соответствует исходному тексту |
| `GET https://chatgpt.com/backend-api/synthesize?...` | **200** для MP3 и AAC с ID последнего подходящего ответа активной ветки | **Работает:** MP3 сохранён и полностью декодирован. Другое, ранее выбранное сообщение дало 404; поддержку произвольного старого сообщения не подтверждаем |
| `POST https://chatgpt.com/backend-api/conversation/{id}/audio_summary/synthesize` | **404**, JSON `{"detail":"Not Found"}` с корректными `podcast` и `executive_briefing` | Аудио не получено. Причина 404 не установлена: этот ответ не доказывает, что маршрут отсутствует у всех аккаунтов |
| `thread/realtime/appendSpeech`, обычный ChatGPT auth | Уведомление `thread/realtime/error`: `realtime conversation requires API key auth` | Обычный app-server не пропускает этот способ авторизации в проверенном WebSocket-сеансе |
| `thread/realtime/appendSpeech`, тот же токен через API-key auth во временном профиле | Start и appendSpeech приняты; получены `thread/realtime/outputAudio/delta` | **Работает как аудиосеанс:** WAV 24 кГц, mono, PCM16. Без инструкции модель отвечает на текст, а не обязательно читает его |

`POST /backend-api/pronunciation/synthesize?format=mp3` также остаётся отдельным
рабочим способом для произвольного текста. Контрольный запрос обновлённым
HTTP-клиентом 30 сентября дал 200 и MP3, 155 520 байт, который декодируется.
Опубликованные ранее [образец и инструкция](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/tts-in-fork.md#2-рабочий-http-api-chatgpt-для-произвольного-текста>)
относятся к проверке 29 сентября.

## Откуда взята авторизация

Основной файл — `исторический-каталог/.codex/auth.json`: `auth_mode=chatgpt`, credential
читается из `tokens.access_token`. В нём нет `OPENAI_API_KEY`. В проверенном
`исторический-каталог/.codex-acodex-api/auth.json` есть только gateway-маркер `0000`,
не настоящий Platform key. Переменная `OPENAI_API_KEY` также отсутствовала.

Значения токена, refresh token и cookies не включены в исходники, команды,
отчёт или репозиторий. Для HTTP и прямого WebSocket токен остаётся в памяти
процесса и отправляется как `Authorization: Bearer …`. Cookies для этих проб
не копировались. Другие корпоративные хранилища credentials не использовались.

Для app-server есть важное отличие. Экспериментальный флаг создаёт отдельный
временный `CODEX_HOME` с правами каталога 0700. После `initialize` скрипт вызывает
`account/login/start` с `type=apiKey`, передавая **тот же OAuth access token**.
App-server сам сохраняет временный auth.json. После остановки собственного
процесса скрипт удаляет созданный временный каталог вместе с этим файлом.

Это не получение нового API key и не переключение основного Desktop-входа.
Содержимое основного auth.json до и после живого запуска совпало. Без явного
флага скрипт использует штатный вход и воспроизводит прежнюю ошибку.

## Прямой Realtime из файла текста

В [realtime-text.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/realtime-text.py>) добавлен явный
`--chatgpt-auth-file`. Он не подставляет OAuth-токен автоматически вместо
отсутствующего Platform key. Обычный режим `--api-key-file` сохранён.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
cd "$repo"
remote_tts_out="$(mktemp -d "$audio_out/codex-remote-tts.XXXXXX")"
remote_tts_python="$TTS_DEMO_CACHE/venv/bin/python"

# Зависимость ставится только в отдельный venv, не в системный Python.
uv pip install --python "$remote_tts_python" websocket-client==1.9.2

"$remote_tts_python" scripts/tts-demos/realtime-text.py \
  scripts/tts-demos/sample-ru.txt "$remote_tts_out/realtime.wav" \
  --chatgpt-auth-file "$auth_file" --timeout 40
ffmpeg -v error -i "$remote_tts_out/realtime.wav" -f null -
```

В проверке использованы `gpt-realtime` и голос `marin`. В `session.update`
передаётся инструкция дословно прочитать текст; затем идут текстовый
`conversation.item.create` и `response.create`. PCM16 собирается из
`response.output_audio.delta`. WAV сохраняется только после завершённого
ответа, без перезаписи существующего файла.

Готовый [Realtime образец](#archive-availability) —
302 444 байта, 6,3 с. Обратная удалённая транскрипция:
«Проверка озвучивания. Этот текст превращается в голос без запуска приложения».
Realtime остаётся генеративной моделью: успех одного примера не гарантирует
дословность любого текста, как у специализированного TTS-контракта.

## App-server из файла текста

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
cd "$repo"
remote_app_out="$(mktemp -d "$audio_out/codex-appserver-tts.XXXXXX")"
python3 scripts/tts-demos/appserver-speech.py \
  scripts/tts-demos/sample-ru.txt "$remote_app_out/appserver.wav" \
  --chatgpt-auth-file "$auth_file" \
  --read-verbatim --timeout 30
ffmpeg -v error -i "$remote_app_out/appserver.wav" -f null -
```

Скрипт запускает свой STDIO app-server, создаёт `ephemeral` thread, открывает
WebSocket realtime-сеанс и вызывает `appendSpeech({threadId,text})`. WAV
собирается из `outputAudio/delta`; в этой живой проверке декодирование как
PCM16 подтвердилось. Скрипт прекращает сбор после паузы потока или timeout;
это минимальная демонстрация, не производственный streaming player.

`--read-verbatim` добавляет инструкцию в `thread/realtime/start.params.prompt`.
Без неё тестовый текст вызвал разговорный ответ: «Голос работает, я тебя слышу…».
С ней [образец app-server](#archive-availability) содержит исходный
текст, но STT выявила дополнительное начальное слово «Говорит».
Размер — 297 644 байта, длительность — 6,2 с.
**Флаг просит читать дословно, но не гарантирует это.** Для строгого озвучивания
нельзя выдавать эту ветку за эквивалент `/v1/audio/speech`.

## Чтение существующего ответа ChatGPT

Запрос не принимает произвольный текст. Нужны **ChatGPT conversation ID и
message ID**, не идентификаторы Codex thread/turn. Для живой проверки ID чата
взят из закреплённых ChatGPT-чатов `.codex-global-state.json` для аккаунта
текущего входа. `GET /backend-api/conversation/{id}` вернул существующий чат.
Из цепочки `current_node → parent` выбран последний видимый текстовый ответ
assistant в активной ветке; тексты чата в журнал проверки не выводились.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
python3 "$repo/scripts/tts-demos/http-tts.py" \
  chatgpt-message "$audio_out/new-chatgpt-message.mp3" \
  --auth-file "$auth_file" \
  --conversation-id '<ChatGPT conversation ID>' --message-id '<assistant message ID>'
```

В заголовки добавлены наблюдавшиеся `originator: Codex Desktop`,
`Origin: https://chatgpt.com` и `Accept: */*`, кроме Bearer, User-Agent и
ChatGPT-Account-ID. Без этого набора чтение чата давало 403 challenge;
с ним получено 200. Какая отдельная добавка решила отказ, не установлено.

Повторный успешный MP3: 3 735 840 байт, 186,792 с, 24 кГц, mono; полное
декодирование ffmpeg завершилось с кодом 0. Поскольку он озвучивает личный чат,
файл не помещён в git-каталог. Закрытый каталог имеет права 0700, файл — 0600:
`исторический-каталог/.cache/codex-tts-demos/codex-read-aloud-verified-0b6fjlro/message.mp3`.

## Почему audio summary не получилось

В первоначальном демо `format` ошибочно был равен `mp3`. Живой сервер ответил
422: это поле допускает `podcast`, `executive_briefing`, `study_guide`.
Тот же enum найден в актуальном renderer-бандле Desktop; штатный default —
`podcast`. Скрипт исправлен и получил параметр `--summary-format`.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
python3 "$repo/scripts/tts-demos/http-tts.py" \
  chatgpt-summary "$audio_out/new-chatgpt-summary.mp3" \
  --conversation-id '<ChatGPT conversation ID>' --summary-format podcast
```

С корректными `podcast` и `executive_briefing` тот же существующий чат дал
JSON 404 без `cf-mitigated: challenge`. Причина не установлена. Нельзя
приписывать это Cloudflare, отозванному Platform key или отсутствующему чату:
обычный GET этого чата и чтение его последнего ответа успешно работают.

## Проверка скриптов и границы результата

Прошли **26 тестов TTS** и **7 voice-probe тестов**. В новые проверки входят
правильный enum summary, чтение синтетического OAuth-файла, отказ для `0000`,
отсутствие секретов в ошибках, временный CODEX_HOME и его очистка при успехе
и ошибке, сохранность исходного auth.json, dry-run без credentials/процессов,
WAV через имитатор STDIO app-server и инструкция чтения в session prompt.

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

Рабочие Realtime и app-server аудио полностью декодированы и проверены
отдельной удалённой транскрипцией, не локальным STT. Менялись только демо,
их тесты и документация. Desktop, core-код приложения, proxy/direct входы,
настройки MCP и установленный payload этим исследованием не изменялись.
Файлы не закоммичены и не отправлены в git.

## Карта кода

| Файл и функция | Назначение |
| --- | --- |
| [realtime-text.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/realtime-text.py>), `read_credential` | Явно читает OAuth или Platform credential из выбранного источника |
| Тот же файл, `events`, `synthesize` | Формирует Realtime events, собирает PCM и проверяет завершение ответа |
| [appserver-speech.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/appserver-speech.py>), `read_chatgpt_token`, `main` | Читает токен, управляет временным профилем, RPC и аудиофайлом |
| [http-tts.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py>), `chatgpt_headers`, `build_request` | Заголовки Desktop и корректные контракты message/summary/speech |
| [test_remote_credentials.py](#archive-availability) | Проверяет изоляцию входа и отсутствие credentials в ошибках без сети |
