# Удалённое озвучивание и транскрипция: HTTP, Realtime и app-server

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

Визуальное продолжение: [учебник с endpoint и форматами на схемах](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/codex-cli-voice-textbook.html>),
[где вызывается каждая функция в интерфейсе и коде](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-ui-entrypoints.html>),
[отдельная схема proxy/direct](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/codex-voice-proxy-textbook.html>).

Этот документ объясняет два удалённых процесса: текст превращается в голос
через `http-tts.py`, а готовый аудиофайл превращается в текст через
`chatgpt-transcribe.sh`. Для каждого процесса показаны цепочка внутри
ChatGPT Community, самостоятельный HTTP-запрос, формат данных и карта кода.

Дополнительно здесь описаны три **изолированных TTS-демо с выбором голоса**:
прямой OpenAI Realtime, ChatGPT Read Aloud `/synthesize` и Codex app-server
`appendSpeech`. Они не импортируют Python-код форка и не требуют запущенного
Desktop. Для каждого ниже приведены схема, сетевой контракт, команды и code-map.

В обеих самостоятельных реализациях синтез или распознавание выполняет сервер
ChatGPT. Python на вашей машине читает файлы, формирует запрос и обрабатывает
ответ. TTS сохраняет MP3; транскрипция печатает текст в терминал. Запускать
Desktop, микрофон или локальные речевые модели для этих команд не требуется.

Документ подготовлен 30 сентября 2026. Код установленного payload
`26.928.20755` сверён 30 сентября; первоначальные успешные удалённые пробы TTS и STT относятся
к 29 сентября. Более общий каталог возможностей и демонстраций находится в
[TTS в форке](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/tts-in-fork.md>), история исследования диктовки — в
[отчёте об аудио](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/voice-app-server.md>).

Дополнение того же дня: новые живые запросы подтвердили Realtime, app-server
во временном профиле и `/synthesize` для последнего ответа ChatGPT.
Speech вернул 429 без кредитов, summary — 404 после исправления enum format.
Точные условия и примеры — в [отчёте удалённого озвучивания](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/remote-audio-check-2026-09-30.md>).

## Файлы и команды запуска

| Назначение | Файл |
| --- | --- |
| Самостоятельный HTTP TTS | [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-transcribe.sh](#archive-availability) |
| Произвольный текст → WAV через прямой Realtime | [realtime-tts.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/realtime-tts.py>) — Python + websocket-client + ChatGPT auth.json; команды в разделе «Изолированное демо 1» |
| Ответ существующего ChatGPT-чата → MP3/AAC | [chatgpt-read-aloud.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py>) — Python + ChatGPT auth.json + ID ответа либо состояние закреплённых чатов; «Изолированное демо 2» |
| Произвольный текст → WAV через RPC app-server | [appserver-tts.py](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/appserver-tts.py>) — Python + отдельный Codex CLI + ChatGPT auth.json; «Изолированное демо 3» |
| Отдельная инструкция для переноса трёх демо | [remote-tts-isolated/README.md](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/README.md>) |
| Русский текст для TTS | [sample-ru.txt](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/sample-ru.txt>) |
| Аудиофайл пользователя | `исторический-каталог/Documents/raven.flac` |
| Схемы и их редактируемые исходники | [каталог audio-http-diagrams](#archive-availability) |

Переменные из env.sh вычисляют расположение скриптов от корня комплекта.
Выполняйте команды из этого корня, а не из каталога документа. Auth-файл
задаётся отдельно; текущий каталог не выбирает аккаунт.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
# Новый каталог результатов: файлы предыдущих демонстраций сохраняются.
audio_demo_out="$(mktemp -d "$audio_out/chatgpt-audio-http.XXXXXX")"

# TTS: текст из файла → новый MP3.
python3 "$repo/scripts/tts-demos/http-tts.py"    chatgpt-text "$audio_demo_out/speech.mp3"    --text-file "$repo/scripts/tts-demos/sample-ru.txt" --auth-file "$auth_file"

# Проверка декодирования и прослушивание результата TTS.
ffmpeg -v error -i "$audio_demo_out/speech.mp3" -f null -
ffplay -nodisp -autoexit -loglevel error "$audio_demo_out/speech.mp3"

# STT: исходный FLAC → текст в терминале.
"$repo/scripts/voice-probes/chatgpt-transcribe.sh"    "$INPUT_AUDIO" --auth-file "$auth_file"
```

Для TTS достаточно Python 3 и действующего сохранённого ChatGPT-входа;
`ffmpeg` и `ffplay` нужны для проверки и прослушивания. Для STT нужны Bash,
Python 3, готовый файл записи и тот же тип входа. Зависимостей от OpenAI SDK
у этих двух скриптов нет.

## Схема озвучивания текста

![TTS в Desktop и самостоятельном HTTP-клиенте](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/tts-flow.png>)

Слева пользователь запускает озвучивание в языковом виджете Desktop.
Renderer, то есть код интерфейса, готовит JSON и обращается к внутреннему
сетевому мосту Electron. Этот мост получает актуальный токен через соединение
с app-server и отправляет HTTP-запрос. Полученный аудио-Blob используется
интерфейсом для воспроизведения.

Справа наш клиент получает текст из UTF-8 файла. Он самостоятельно собирает
тот же основной JSON, добавляет заголовок Bearer из auth.json и получает
бинарный ответ через urllib. Результат записывается в новый файл. Проверка
через ffmpeg и воспроизведение через ffplay выполняются следующими командами;
`http-tts.py` сам проигрыватель не запускает.

### Как TTS вызывается в форке

В `webview/assets/app-primary-1a4f67814ed6.js` функция `ixt` вызывает:

```javascript
// Сокращённая передача смысла исходного минифицированного кода.
fetch("/pronunciation/synthesize?format=mp3", {
  method: "POST",
  body: JSON.stringify(payload),
  headers: { ...desktopHeaders(), "Content-Type": "application/json" },
  signal
}).then(response => response.blob());
```

Это маршрут произвольного текста в pronunciation/language-learning widgets.
В `start-voice-5f4e1ab7dcfd.js` соответствующий вызов передаёт `text`,
`pronunciation_hint`, `pronunciation_language`, `speed`, `source`, `version`.
Часть полей зависит от виджета и может отсутствовать.

Обычная кнопка ChatGPT Read aloud для уже существующего ответа использует
другой маршрут: `GET /synthesize` с `conversation_id`, `message_id`, `voice`
и `format`. У `http-tts.py` для него есть режим `chatgpt-message`. Основной
рабочий пример в этом документе — `chatgpt-text`, который не требует ID чата
или ответа. Linux-функция Read Aloud с Piper/Kokoro также является отдельной
реализацией; её локальные движки в эту HTTP-цепочку не входят.

### Как TTS выполняет наш клиент

`main` читает входной файл и выбирает режим. `build_request` возвращает URL,
метод и тело без credentials. Для `chatgpt-text` запрос выглядит так:

```http
POST https://chatgpt.com/backend-api/pronunciation/synthesize?format=mp3
Authorization: Bearer <access_token из auth.json>
ChatGPT-Account-ID: <account_id, если сохранён>
User-Agent: <наблюдавшийся Desktop User-Agent>
Content-Type: application/json

{"text":"Проверка озвучивания.","pronunciation_language":"ru","source":"translation"}
```

`text` — строка для чтения, `pronunciation_language` — язык, а `source` —
контекст вызова, повторённый из translation-сценария. Скрипт не добавляет
`pronunciation_hint`, `speed` и `version` в этой ветке. Какой моделью сервер
синтезирует речь, какой у него лимит текста и как он учитывает расход,
из исследованного приватного контракта не установлено.

`chatgpt_headers` читает `auth_mode`, `tokens.access_token` и при наличии
`tokens.account_id`. Значения токена в исходнике нет. `download_audio`
отправляет запрос и принимает до 30 000 000 байт ответа — это ограничение
нашего клиента. Проверяются Content-Type, непустой размер и отсутствие
JSON/HTML вместо аудио. Затем режим `xb` создаёт новый файл без перезаписи.
Эти проверки не заменяют полное декодирование через ffmpeg.

В успешной пробе 29 сентября сервер вернул `200`, `audio/mpeg`, 119 808 байт.
[Созданный MP3](#archive-availability) имеет длительность 7,488 с,
частоту 24 кГц и один канал. Обратная удалённая STT вернула исходный текст.
Это подтверждает работу запроса с тем аккаунтом и окружением на дату пробы.

### Режимы и параметры TTS

| Режим | Запрос | Откуда берётся текст | Доступ |
| --- | --- | --- | --- |
| `chatgpt-text` | `POST /pronunciation/synthesize?format=mp3` | `--text-file` | ChatGPT access token; аудио получено в живой пробе |
| `chatgpt-message` | `GET /synthesize?...` | Существующий ответ по двум ID | 30 сентября получены MP3 и AAC; сохранённый MP3 декодирован |
| `chatgpt-summary` | `POST /conversation/{id}/audio_summary/synthesize` | Содержание чата, файл задаёт инструкции | 30 сентября корректный format дал JSON 404 |
| `openai` | `POST https://api.openai.com/v1/audio/speech` | `--text-file` | Обычный клиент требует Platform key; отдельная OAuth-проба 30 сентября дала 429 credit_balance_exhausted |

`--language` по умолчанию `ru` влияет на `chatgpt-text`. `--voice` относится
к `chatgpt-message` и `openai`; `--model` относится к `openai`. Для этой
публичной ветки скрипт задаёт WAV и ограничивает текст 4096 символами.
Передача `--model` в pronunciation-режиме не выбирает модель сервера.
`--summary-format` задаёт podcast/executive_briefing/study_guide; это тип
резюме, не формат аудиофайла. Значение mp3 в этом JSON было ошибкой демо.

`--timeout` по умолчанию 90 секунд передаётся urllib как сетевой timeout;
скрипт не реализует отдельный общий секундомер всего процесса.
`--dry-run` печатает URL и JSON без чтения credentials и без сети:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
python3 "$repo/scripts/tts-demos/http-tts.py"    chatgpt-text "$audio_out/audio-http-preview.mp3"    --text-file "$repo/scripts/tts-demos/sample-ru.txt"    --dry-run --auth-file "$auth_file"
```

## Три изолированных TTS-демо: что выбрать

Все три выполняют синтез **на удалённом сервисе**. Локальный код только
читает вход, передаёт запрос, собирает аудио и сохраняет файл. Ни Kokoro,
ни Piper, ни локальная STT для них не нужны.

| Путь | Что отправляем | Что получаем | Зависимость от форка |
| --- | --- | --- | --- |
| Прямой Realtime | JSON-события с вашим текстом и голосом | Base64 PCM16-чанки → WAV 24 кГц mono | Нет; нужен websocket-client |
| ChatGPT `/synthesize` | ID существующего чата/ответа, голос, mp3/aac; не новый текст | Готовые бинарные MP3/AAC | Нет; локальный state нужен только для автоматического выбора чата |
| App-server `appendSpeech` | JSON-RPC с текстом и выбранной версией/голосом | RPC-уведомления с base64 аудио → WAV | Нужен Codex CLI с этими RPC; Electron, ASAR и ресурсы форка не нужны |

Для своего текстового файла подходят первый и третий пути. Для озвучивания
готового ответа ChatGPT — второй. Realtime и appendSpeech являются
генеративными сеансами: инструкция просит читать дословно, но не гарантирует
отсутствия добавлений или перефразирования. `/synthesize` не является API
«передать произвольный текст». Для этого выше описан отдельный
`/pronunciation/synthesize`.

### Подготовка и перенос на другую машину

Выполните этот блок в одном Bash-терминале перед командами трёх демо:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
# Создаём независимый каталог 0700; сам auth.json туда не копируем.
remote_demo_dir="$(mktemp -d "$audio_out/remote-voice-demo.XXXXXX")"
remote_source_dir="$repo/scripts/remote-tts-isolated"
cp "$remote_source_dir/realtime-tts.py" "$remote_source_dir/chatgpt-read-aloud.py"    "$remote_source_dir/appserver-tts.py" "$remote_source_dir/sample-ru.txt" "$remote_demo_dir/"

# Только Realtime требует сторонний пакет; устанавливаем его в отдельный venv.
python3 -m venv "$remote_demo_dir/venv"
"$remote_demo_dir/venv/bin/python" -m pip install websocket-client==1.9.2

# Укажите именно ChatGPT-профиль своей машины, не профиль acodex с ключом 0000.
remote_auth_file="$auth_file"
remote_text_file="$remote_demo_dir/sample-ru.txt"
remote_codex_bin="$(command -v codex)"
```

Можно перенести любой `.py` отдельно. У Realtime и app-server входной файл —
обычный непустой UTF-8 текст. Выходная папка должна существовать, а выходной
файл — **не существовать**: повтор команды с тем же именем намеренно даёт отказ.
Для новой пробы меняйте имя результата либо создавайте новый каталог.

Все три читают `tokens.access_token` из файла с `auth_mode:"chatgpt"`.
Default `--auth-file` учитывает `CODEX_HOME`, затем `~/.codex`. Токен не
вписан в код, не передаётся аргументом командной строки и не печатается.
Refresh и вход в аккаунт эти демо не выполняют. Если профиль хранит credential
только в keyring, нужен поддерживаемый файл входа, а не ручная вставка токена
в скрипт. Истёкший вход обновляется штатным механизмом Codex.

Использование ChatGPT OAuth для прямого Realtime и API-key режима временного
app-server **экспериментально**: успех подтверждён для нашего входа 30 сентября,
но не обещан любому аккаунту. Это не создание Platform key и не доказательство
общей доступности всех Platform endpoints. Не отправляйте секретные тексты.

### Изолированное демо 1: прямой OpenAI Realtime

![Прямой Realtime: текст, голос, WebSocket и сборка WAV](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/isolated-realtime.png>)

Здесь вообще нет app-server. Скрипт открывает
`wss://api.openai.com/v1/realtime?model=gpt-realtime`, передаёт Bearer в
заголовке WebSocket handshake и обменивается JSON-событиями поверх WSS/TLS.
Файл текста остаётся локальным: передаётся его содержимое, не путь и не аудио.

Последовательность: дождаться `session.created` → отправить `session.update`
→ дождаться `session.updated` → отправить `conversation.item.create` с
`input_text` → `response.create` → собрать `response.output_audio.delta`
→ дождаться `response.done` со статусом `completed`.

В `session.update` задаются `output_modalities:["audio"]`,
`audio.output.voice`, формат `audio/pcm` с частотой 24000 и инструкция чтения.
Входной `turn_detection:null` отключает ожидание речи микрофона. Выходные
дельты содержат base64 PCM16; локально добавляется контейнер WAV.

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
# Статический документированный список: без сети и без чтения токена.
python3 "$remote_demo_dir/realtime-tts.py" --list-voices --auth-file "$auth_file"

# Проверяем параметры без авторизации, сети и создания WAV.
python3 "$remote_demo_dir/realtime-tts.py"    "$remote_text_file" "$remote_demo_dir/realtime-check.wav" --voice cedar --dry-run --auth-file "$auth_file"

# Настоящий удалённый синтез выбранным голосом.
"$remote_demo_dir/venv/bin/python" "$remote_demo_dir/realtime-tts.py"    "$remote_text_file" "$remote_demo_dir/realtime-cedar.wav"    --auth-file "$remote_auth_file" --model gpt-realtime --voice cedar --timeout 60
ffmpeg -v error -i "$remote_demo_dir/realtime-cedar.wav" -f null -
ffplay -nodisp -autoexit "$remote_demo_dir/realtime-cedar.wav"
```

Голоса: `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`,
`verse`, `marin`, `cedar`; default демо — `marin`. Источник набора и событий —
[официальное описание Realtime](<https://developers.openai.com/api/docs/guides/realtime-conversations>).
Список в демо статический, не endpoint каталога и не проверка доступа аккаунта.
Каждый запуск создаёт новую сессию; голос выбирается до генерации аудио.

Это воспроизведение Realtime-протокола, **не полная копия голосового UI
Desktop**: нет микрофона, WebRTC, звонка, истории чата, Electron auth-моста
или integrity state. HTTP proxy из окружения отключён через
`http_no_proxy=["*"]`. Общий timeout — 60 секунд, локальный предел —
120 секунд аудио. Незавершённый ответ не публикуется как готовый WAV.

### Изолированное демо 2: ChatGPT Read Aloud `/synthesize`

![ChatGPT Read Aloud: каталог голосов, выбор ответа и MP3/AAC](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/isolated-read-aloud.png>)

Этот клиент повторяет основной HTTP-контракт озвучивания существующего
ChatGPT-ответа, но не сетевое окружение Electron. Нужен только стандартный
Python. Модель или произвольный текст в запрос синтеза не передаются.

| Шаг | Запрос | Что передаётся и возвращается |
| --- | --- | --- |
| Каталог голоса | `GET /backend-api/settings/voices?spoken_language=ru&voice_mode=advanced` | Возвращает JSON с именами и ID; вызывается перед каждым синтезом |
| Выбор ответа, если message ID не указан | `GET /backend-api/conversation/{conversation_id}` | Возвращает JSON чата; скрипт идёт по `current_node → parent` и выбирает последний видимый текстовый ответ assistant |
| Озвучивание | `GET /backend-api/synthesize?conversation_id=…&message_id=…&voice=…&format=mp3` | Отправляет ID и параметры; сервер читает сохранённый ответ и возвращает бинарное аудио |

Все адреса относятся к `https://chatgpt.com`. Если ID не заданы, скрипт читает
`.codex-global-state.json` рядом с auth.json и выбирает первый закреплённый
**ChatGPT-чат того же account ID**. Чужую ветку аккаунта он не использует.
Если закреплённого чата нет, задайте `--conversation-id`. Если заданы оба ID,
state-файл и GET чата не нужны; запрос каталога голосов всё равно выполняется.
**Codex thread ID и turn ID сюда не подходят.**

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
# Реальный read-only запрос каталога голосов текущего аккаунта.
python3 "$remote_demo_dir/chatgpt-read-aloud.py"    --auth-file "$remote_auth_file" --list-voices

# Автовыбор последнего подходящего ответа закреплённого ChatGPT-чата.
python3 "$remote_demo_dir/chatgpt-read-aloud.py" "$remote_demo_dir/message-juniper.mp3"    --auth-file "$remote_auth_file" --voice Juniper
ffmpeg -v error -i "$remote_demo_dir/message-juniper.mp3" -f null -
ffplay -nodisp -autoexit "$remote_demo_dir/message-juniper.mp3"

# Явные ID своего ChatGPT-чата/ответа: замените обе строки в угловых скобках.
python3 "$remote_demo_dir/chatgpt-read-aloud.py" "$remote_demo_dir/message-sol.aac"    --auth-file "$remote_auth_file" --conversation-id '<ChatGPT conversation ID>'    --message-id '<assistant message ID>' --voice Sol --format aac
```

Каталог, полученный 30 сентября 2026:

| Имя в интерфейсе / значение `--voice` | Фактический ID ChatGPT в запросе |
| --- | --- |
| Vale | `vale` |
| Sol | `glimmer` |
| Spruce | `orbit` |
| Arbor | `fathom` |
| Ember | `ember` |
| Cove | `cove` |
| Maple | `maple` |
| Breeze | `breeze` |
| Juniper | `juniper` |

Принимается имя или ID без учёта регистра; default — `juniper`.
`--language ru` выбирает язык каталога, **не переводит текст**.
Настройка голоса аккаунта не меняется: PATCH настроек отсутствует.
Озвучивание каждого голоса из каталога отдельно не проверено.

Headers: Bearer из файла, `ChatGPT-Account-ID` при наличии, наблюдавшийся
User-Agent, `originator: Codex Desktop`, `Origin: https://chatgpt.com`,
`Accept: */*`. Cookies и attach/integrity-маркеры не используются.
HTTP proxy отключён, redirects запрещены. JSON чата ограничен 10 МБ,
аудио — 30 МБ, timeout 90 секунд относится к отдельному HTTP-запросу.
Нет создания чата, отправки сообщения, автоматического retry или refresh.
Ранее один ответ дал 404, а последний подходящий — аудио: поддержку любого
старого message ID по этому результату не утверждаем.

### Изолированное демо 3: app-server `appendSpeech`

![App-server: временный профиль, JSON-RPC, удалённый голос и WAV](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/isolated-appserver.png>)

Здесь app-server — отдельный локальный процесс **Codex CLI**, а не HTTP
сервер форка. Python общается с ним строками JSON-RPC через stdin/stdout;
Codex самостоятельно устанавливает удалённый WebSocket-сеанс. Это
отдельный путь от файловых HTTP TTS/STT выше. Desktop не нужен.

| Порядок | RPC / уведомление | Основные параметры или данные |
| --- | --- | --- |
| 1 | `initialize`, затем `initialized` | `capabilities.experimentalApi:true` |
| 2 | `thread/realtime/listVoices` | Каталог v1/v2 и defaults; не проверка синтеза аккаунтом |
| 3 | `account/login/start` | `type:"apiKey"`, `apiKey:tokens.access_token` — только во временном профиле |
| 4 | `thread/start` | `ephemeral:true`, пустой cwd, `sandbox:"read-only"`, `approvalPolicy:"never"` |
| 5 | `thread/realtime/start` | `threadId`, `outputModality:"audio"`, `transport.type:"websocket"`, `includeStartupContext:false`, выбранные `voice`, `version`, инструкция чтения |
| 6 | `thread/realtime/appendSpeech` | `{threadId,text}` — текст целиком; не аудиофайл и не аудиочанки |
| 7 | `thread/realtime/outputAudio/delta` | `params.audio.data` — base64; `sampleRate`, `numChannels` — параметры WAV |
| 8 | `thread/realtime/stop` и остановка своего процесса | Завершение сеанса, удаление временного профиля |

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
# Убедитесь, что command -v нашёл CLI; при необходимости задайте полный путь.
test -n "$remote_codex_bin" && "$remote_codex_bin" --version

# Каталог RPC: отдельный процесс, без чтения токена и без аудиосеанса.
python3 "$remote_demo_dir/appserver-tts.py" --codex "$remote_codex_bin" --list-voices --auth-file "$auth_file"

# Несетевая проверка параметров: даже subprocess не запускается.
python3 "$remote_demo_dir/appserver-tts.py"    "$remote_text_file" "$remote_demo_dir/appserver-check.wav"    --codex "$remote_codex_bin" --version v2 --voice ash --dry-run --auth-file "$auth_file"

# Реальный запрос: код форка и его ресурсы не используются.
python3 "$remote_demo_dir/appserver-tts.py"    "$remote_text_file" "$remote_demo_dir/appserver-ash.wav"    --codex "$remote_codex_bin" --auth-file "$remote_auth_file"    --version v2 --voice ash --timeout 60
ffmpeg -v error -i "$remote_demo_dir/appserver-ash.wav" -f null -
ffplay -nodisp -autoexit "$remote_demo_dir/appserver-ash.wav"
```

| Версия | Каталог RPC | Default |
| --- | --- | --- |
| v1 | `juniper`, `maple`, `spruce`, `ember`, `vale`, `breeze`, `arbor`, `sol`, `cove` | `cove` |
| v2 | `alloy`, `ash`, `ballad`, `coral`, `echo`, `sage`, `shimmer`, `verse`, `marin`, `cedar` | `marin` |

Default демо — v2; без `--voice` используется default каталога выбранной
версии. Голос валидируется перед открытием сеанса. Синтез v1 **не проверен**.
Идентификаторы v1 не равны backend ID ChatGPT: например, `sol`, а не `glimmer`.

Почему особая авторизация: обычный ChatGPT-режим app-server в живой пробе
отклонил WebSocket-сеанс с `realtime conversation requires API key auth`.
Поэтому демо создаёт временный `CODEX_HOME` 0700 и передаёт **тот же OAuth
токен** как apiKey. Это экспериментальный способ пройти локальную проверку,
не новый ключ Platform и не переключение основного профиля. Временный
auth.json записывает сам Codex. Исходный файл только читается.

Subprocess запускается как `codex app-server --listen stdio://` с отдельным
профилем и пустой рабочей папкой. Наследуется технический минимум env;
OPENAI_*, сторонние CODEX_* и proxy env не передаются. При этом сам бинарник
Codex остаётся зависимостью: внутренний выбор удалённого URL принадлежит ему,
этот Python-клиент не фиксирует такой URL и не подключает acodex gateway.
Описание возможностей ядра отдельно: [аудио RPC app-server](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/voice-app-server.md>).

PCM16 и WAV проверены для использованных версий, но это не гарантия формата
любого будущего бинарника. Чанки, пришедшие **до ответа RPC appendSpeech**,
сохраняются в очереди и не теряются. Сбор завершается после трёх секунд паузы
потока; это эвристика демо, не доказательство полного ответа при длинной
паузе и не производственный streaming player. Общий timeout — 60 секунд;
аудио ограничено 30 МБ. Параметры аудио не должны меняться посреди потока.

### Безопасность, ошибки и подтверждённые результаты трёх демо

WAV/MP3/AAC публикуются с правами 0600 через временный файл и атомарную hard
link, без перезаписи существующего результата. Нужна Linux-файловая система
с поддержкой hard link. Скрипты не печатают токен, cookies, личный текст
чата или тела ошибок сервера; JSON stdout содержит параметры результата.

При штатном завершении, ошибке или timeout app-server-демо останавливает
только свой процесс и удаляет временный профиль с credential. При SIGKILL
или отключении питания Python не сможет выполнить cleanup: временный
каталог может остаться. Не запускайте демо с shell tracing `set -x` и не
публикуйте auth.json или полный сетевой дамп.

| Отказ | Что проверить; чего не утверждать |
| --- | --- |
| 401 / отсутствует ChatGPT access token | Выбран ли действующий ChatGPT-профиль; demo не делает refresh и не принимает gateway-маркер `0000` |
| 403 / Cloudflare challenge | Приватный API может отказать внешнему клиенту; отсутствие cookies/integrity отличает его от Desktop; единственная причина всех 403 не установлена |
| 404 `/synthesize` | Свои ChatGPT ID, подходящий ответ и его доступность; не подставлять Codex thread/turn ID |
| Неизвестный голос / RPC отсутствует | Свежий каталог этого пути, правильная версия и совместимый Codex CLI |
| Timeout / длинная пауза | Увеличить `--timeout`; у appendSpeech отдельно остаётся трёхсекундная эвристика окончания |
| Выходной файл уже существует | Использовать новое имя; существующий результат демо не удаляет |

Живые проверки **30 сентября 2026**, до этой документационной доработки:

| Изолированный скрипт | Реально проверенный вариант | Подтверждение |
| --- | --- | --- |
| Realtime | `cedar` | WAV 5,7 с, 24 кГц mono; полное декодирование и обратная удалённая STT подтвердили тестовый текст |
| ChatGPT Read Aloud | Juniper → `juniper`, MP3 | 3 732 000 байт; полное декодирование успешно; личный ответ хранится только в закрытом cache, не в git |
| App-server v2 | `ash`, Codex 0.159.0 | WAV 5,2 с; полное декодирование и удалённая STT соответствуют тексту |
| App-server v2 | `marin`, отдельно скопированный бинарник 0.159.0 | WAV получен без соседних ресурсов форка |
| App-server v2 | `cedar`, официальный CLI 0.156.1 | WAV получен независимо от форка; CLI также вернул каталог |
| Финальная версия app-server-демо | `ash`, официальный CLI 0.156.1 | 271 200 байт PCM; декодирование прошло; исходный auth.json не изменился |

Демо запускались из `/tmp` после копирования вне репозитория. Успех перечисленных
голосов не подтверждает синтез всеми остальными. Общий протокол и статусы
первичных проверок — в [отчёте 30 сентября](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/remote-audio-check-2026-09-30.md>),
варианты и зависимости — в [инструкции изолированных демо](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/README.md>).
При этой доработке документа Desktop не запускался и не перезапускался;
новые платные/удалённые пробы не требовались.

## Схема транскрипции записи

![Файловая диктовка Desktop и отправка готового FLAC нашим скриптом](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/stt-flow.png>)

Слева Desktop получает MediaStream от микрофона. MediaRecorder выдаёт части
записи, из которых клиент собирает Blob. Дальше одна из функций файловой
диктовки создаёт multipart-запрос. Формат этой записи выбирает клиент,
поэтому полученная запись Desktop не обязана совпадать с исходным FLAC,
даже если тот был проигран через виртуальный микрофон.

Справа Bash получает путь к уже готовой записи и запускает вложенный Python.
В HTTP-тело попадают байты именно этого файла. Наш скрипт не декодирует их
в PCM и не перекодирует FLAC в WebM. Сервер выполняет распознавание и
возвращает JSON; Python проверяет `text` и печатает его в stdout.

### Как диктовка работает в форке

В исследованном renderer-бандле `app-initial-41d268a90bf8.js` присутствуют
две формы файлового вызова:

| Функции | Подготовка запроса |
| --- | --- |
| `Fki` и `Rki` | Blob превращается в multipart-байты: `file`, имя, MIME и опциональный `language`; затем `fetch("/transcribe", ...)` |
| `HPc` | FormData содержит `file` и при наличии `language`, `dictation_session_id`, `attempt_id`, `duration_ms`, `conversation_id`, `parent_message_id`; затем `safePost("/transcribe", ...)` |

`Iki` проверяет JSON по схеме `Hki`: нужен строковый `text`; опционально
возвращаются `asset_pointer`, `asset_ttl`, `asset_format` и
`conversation_detail_metadata`. Эти дополнительные данные наш Bash-клиент
не использует. `Lki` допускает повторы некоторых сетевых ошибок и статусов
0, 408, 500, 502, 503, 504 с задержкой и отменой через signal.

Диктовка умеет и отдельный потоковый путь через `/dictation/stream`.
Этот документ описывает файловый `/transcribe`: передача FLAC нашим скриптом
не запускает потоковую сессию и не вызывает `thread/realtime/*`.

В предыдущей UI-пробе пользователь подтвердил соответствие текста записи.
NetLog следующей диктовки зафиксировал `POST /backend-api/transcribe`,
HTTP/2, multipart и ответ `200 application/json`. Он подтверждает сетевой
маршрут Desktop; отправку именно исходных байтов `raven.flac` подтверждает
отдельный файловый запрос. Эти две проверки описаны в
[истории исследования](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/voice-app-server.md>).

### Как отправляет FLAC наша версия

Команда пользователя имеет один обязательный аргумент:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
"$repo/scripts/voice-probes/chatgpt-transcribe.sh"    "$INPUT_AUDIO" --auth-file "$auth_file"
```

Bash выключает трассировку команд, проверяет количество аргументов и Python,
затем через `exec` заменяет себя процессом Python. Код передан закрытым
heredoc `<<'PY'`, поэтому Bash не подставляет в него переменные. В аргументах
Python передаются только пути записи и auth.json.

Python проверяет наличие файла и размер от 1 до 25 000 000 байт. Это наш
лимит исходного аудио; multipart будет немного больше. MIME выбирается
по расширению. Для FLAC в этом окружении это `audio/flac`; содержимое
файла до отправки не валидируется аудиодекодером.

После чтения входа Python создаёт случайный `boundary` и одно multipart-тело:

```http
POST https://chatgpt.com/backend-api/transcribe
Authorization: Bearer <access_token из auth.json>
ChatGPT-Account-ID: <account_id, если сохранён>
User-Agent: <наблюдавшийся Desktop User-Agent>
Accept: application/json
Content-Type: multipart/form-data; boundary=<случайная граница>

--<случайная граница>
Content-Disposition: form-data; name="file"; filename="raven.flac"
Content-Type: audio/flac

<исходные бинарные байты raven.flac>
--<случайная граница>--
```

В реальном теле используются разделители CRLF. Передаются сами аудиоданные,
а не путь к файлу, не base64 и не ссылка на локальный каталог. Наш Bash-скрипт
не отправляет `model`, `language`, ID чата или метаданные диктовки. Сервер
возвращает JSON со строкой `text`; модель распознавания приватного маршрута
из этого контракта не известна.

Файл и весь multipart собираются в памяти. Ответ читается и разбирается
через `json.load`. Timeout сетевых операций равен 90 секундам; собственного
refresh, retry или общего секундомера в этом скрипте нет. Для успешного
вызова stdout содержит только транскрипцию, а для отказа stderr содержит
диагностическое сообщение и процесс завершается с ненулевым кодом.

Чтобы сохранить текст в новый каталог:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
audio_demo_out="$(mktemp -d "$audio_out/chatgpt-transcript.XXXXXX")"
"$repo/scripts/voice-probes/chatgpt-transcribe.sh"    "$INPUT_AUDIO" --auth-file "$auth_file" > "$audio_demo_out/raven.txt"
```

Shell создаёт выходной файл ещё до старта скрипта. Поэтому существование
этого файла после ошибки не подтверждает успех — проверяйте код завершения.
У Bash-версии нет флага `--dry-run`: она воспринимает единственный аргумент
как путь записи. Для несетевой проверки запроса можно использовать соседний
`chatgpt-dictation-file.py --dry-run`; это отдельная реализация.

## Заголовки авторизация и сеть

![Заголовки и тела TTS и STT запросов](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-http-diagrams/wire-format.png>)

Жёлтый блок содержит заголовки наших двух клиентов. Access token подтверждает
ChatGPT-вход и передаётся внутри HTTPS-запроса. Зелёный и синий блоки показывают
разные тела: JSON с текстом для TTS и multipart с файлом для STT. App-server
не обрабатывает эти тела в самостоятельных реализациях.

Внутри Desktop renderer добавляет служебные маркеры
`X-OpenAI-Attach-Auth` и `X-OpenAI-Attach-Integrity-State`. Electron удаляет
эти маркеры из исходящих заголовков и получает текущий токен у app-server:
сначала может использовать кеш, затем `getAuthToken`. При наличии integrity
state добавляется `X-OAI-IS`. Для разрешённых запросов предусмотрен один
повтор после 401 с обновлением токена. Отдельная workspace-routing ветка
использует `getBackendRequestAuth`; отправку выполняет Electron network.

Внешнему клиенту этот внутренний мост не предоставляется как общедоступный
HTTP endpoint. Наши скрипты воспроизводят HTTP-вызов самостоятельно и не
добавляют attach-маркеры, `X-OAI-IS`, cookies или refresh token. Они не
обновляют ChatGPT-вход. Полный цикл входа и обновления остаётся у штатного
клиента; файлы из keyring эти демонстрации читать не умеют.

| Свойство | Desktop | `http-tts.py` | `chatgpt-transcribe.sh` |
| --- | --- | --- | --- |
| Откуда берётся access token | Из актуального app-server соединения или кеша | По умолчанию `~/.codex/auth.json`; иной путь через `--auth-file` | `${CODEX_HOME:-$HOME/.codex}/auth.json` |
| Учитывает ли CODEX_HOME при выборе файла | Зависит от запуска app-server | Автоматически не учитывает | Учитывает |
| Обновление после 401 | Предусмотрено мостом, если повтор разрешён | Нет | Нет |
| Cookies и integrity | Сетевой мост может использовать состояние приложения | Не читаются из Desktop | Не читаются из Desktop |
| Прокси и маршрутизация | Сеть и auth-routing приложения | Стандартные proxy-настройки urllib | `ProxyHandler({})` отключает proxy-настройки urllib |
| Сетевой протокол | Для STT в NetLog наблюдался HTTP/2 | Стандартный urllib использует HTTP/1.1 поверх TLS | Стандартный urllib использует HTTP/1.1 поверх TLS |
| Аудиоданные проходят через app-server | В исследованном файловом HTTP-пути отправляет Electron | Нет | Нет |

Для явного выбора ChatGPT-хранилища в TTS:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
python3 "$repo/scripts/tts-demos/http-tts.py"    chatgpt-text "$audio_out/speech-from-selected-login.mp3"    --text-file "$repo/scripts/tts-demos/sample-ru.txt"    --auth-file "$auth_file"
```

Для STT выбранный `CODEX_HOME` уже используется автоматически. Если в нём
API-key вход acodex или маркер `0000`, скрипт откажется передавать этот вход
на приватный ChatGPT endpoint. Нужен файл именно с `auth_mode:"chatgpt"`.
Адрес `codex-proxy.gis-master.ru` и настройки model gateway в этих двух
скриптах не используются. HTTP proxy в окружении TTS — отдельный сетевой
механизм и не становится от этого acodex endpoint.

## Карта кода самостоятельных скриптов

Ссылки с номерами строк относятся к этому checkout после добавления русских
комментариев. При дальнейших изменениях ориентируйтесь на имена функций и
указанные выражения: номера строк могут сместиться.

### Карта кода TTS

| Точка входа или блок | Где открыть | Что делает |
| --- | --- | --- |
| `CHATGPT`, `USER_AGENT` | [Константы](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L26>) | Задаёт приватный адрес и наблюдавшийся User-Agent |
| `NoRedirect` | [Запрет переходов](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L34>) | Оставляет 3xx ошибкой вместо отправки нового запроса |
| `chatgpt_headers` | [Чтение входа](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L41>) | Читает auth.json, проверяет тип входа и формирует Bearer/account headers |
| `build_request` | [Формирование контракта](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L64>) | Выбирает URL, HTTP-метод и тело для четырёх режимов |
| `download_audio` | [Сеть и аудиофайл](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L96>) | Отправляет запрос, обрабатывает отказ, проверяет и сохраняет байты |
| `main` | [CLI и выполнение](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L139>) | Разбирает аргументы, читает текст, выполняет dry-run либо HTTP-запрос |
| `__main__` | [Вывод ошибок](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/tts-demos/http-tts.py#L201>) | Запускает main и формирует диагностический stderr без содержимого auth.json |

### Карта кода трёх изолированных демо

Каждый файл самодостаточен: здесь нет imports соседних скриптов. Русские
комментарии поясняют сетевой контракт, временные файлы и обработку отказов.

| Скрипт / функция | Где открыть | За что отвечает |
| --- | --- | --- |
| Realtime: `VOICES`, `read_token` | [Голоса и авторизация](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/realtime-tts.py#L19>) | Статический список из официальной документации; чтение access token с проверкой типа входа |
| Realtime: `synthesize` | [WebSocket и события](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/realtime-tts.py#L32>) | Фиксированный WSS URL, выбранный голос, текстовый item, общий deadline, сбор только завершённого PCM |
| Realtime: `save_wav`, `main` | [WAV и CLI](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/realtime-tts.py#L91>) | PCM16 mono 24 кГц, приватная атомарная публикация, list/dry-run без авторизации |
| Read Aloud: `NoRedirect`, `read_auth` | [HTTP и вход](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L22>) | Запрет redirect, чтение токена/account ID, набор Desktop headers |
| Read Aloud: `pinned_conversation` | [Граница аккаунта](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L45>) | Закреплённый ChatGPT-чат только текущего аккаунта |
| Read Aloud: `assistant_message` | [Поиск ответа](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L58>) | Активная ветка, видимый assistant, исключение analysis/tool-ответов, защита от цикла |
| Read Aloud: `get`, `save_audio` | [Запрос и файл](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L79>) | Timeout/размеры, безопасная диагностика HTTP, атомарное сохранение |
| Read Aloud: `voice_catalog` | [Каталог голосов](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L106>) | Read-only GET `/settings/voices`; никаких PATCH настроек |
| Read Aloud: `main` | [Выбор голоса и synthesize](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/chatgpt-read-aloud.py#L122>) | Имя → backend ID, явный/автоматический выбор ответа, GET `/synthesize`, проверка аудио |
| App-server: `read_token`, `synthesize` | [Изолированный профиль и RPC](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/appserver-tts.py#L23>) | Временный CODEX_HOME/cwd, whitelist env, свой subprocess, очередь уведомлений, start/appendSpeech/stop и cleanup |
| App-server: `save_wav` | [Сохранение аудио](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/appserver-tts.py#L167>) | Частота/каналы из ответа; PCM16; атомарный новый WAV |
| App-server: `main` | [Версия, голос и CLI](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/appserver-tts.py#L184>) | Обязательный --codex, list-voices без токена, dry-run без subprocess, чтение текста |
| `test_isolated.py` | [17 проверок](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/scripts/remote-tts-isolated/test_isolated.py>) | Синтетические credentials, mock сети/RPC, безопасность, копирование вне репозитория, голос и первый чанк до RPC-ack |

### Карта кода транскрипции

| Блок | Где открыть | Что делает |
| --- | --- | --- |
| Bash и `exec python3` | [Запуск вложенного клиента](#archive-availability) | Проверяет 1 аргумент и Python, выбирает auth.json, запускает heredoc |
| `ENDPOINT`, `MAX_BYTES`, `USER_AGENT` | [Константы](#archive-availability) | Адрес, локальный лимит 25 МБ и User-Agent |
| `fail`, `NoRedirect` | [Общие ошибки](#archive-availability) | Пишет отказ в stderr и запрещает HTTP redirect |
| `audio`, `auth_path` | [Проверки файлов](#archive-availability) | Разворачивает пути и проверяет наличие, размер и имя записи |
| Чтение `auth` и `tokens` | [Авторизация](#archive-availability) | Читает и проверяет ChatGPT credential, не выводя его |
| `mime_type`, `boundary`, `file_header`, `body` | [Multipart](#archive-availability) | Формирует одно поле file с исходными бинарными байтами |
| `headers`, `request`, `opener` | [Прямой HTTP-клиент](#archive-availability) | Добавляет Bearer, создаёт POST и отключает proxy-настройки |
| `opener.open`, JSON и ошибки | [Ответ сервера](#archive-availability) | Ждёт ответ, разбирает JSON, проверяет строку text |
| `print(result["text"])` | [Полезный stdout](#archive-availability) | Выводит транскрипцию для терминала или следующей команды |

## Карта кода внутри Desktop

Исследованные файлы находятся внутри
`codex-app/resources/app.asar` (исходный payload не включён в переносимый комплект).
Сохранённые выдержки доступны в [bundle-code-map.json](</experiments/voice-ai/archive/c/DOCS/voice-systems-textbook-2026-10-02/voice-systems-textbook/docs/audio-ui-assets/bundle-code-map.json>).
Это минифицированные upstream-бандлы, а не исходники наших HTTP-скриптов.
Их имена и символы относятся к payload `26.928.20755` и могут измениться
при следующем обновлении. Для поиска приведены смысловые строки и функции.

| Файл внутри ASAR | Якорь для поиска | Роль |
| --- | --- | --- |
| `webview/assets/app-primary-1a4f67814ed6.js` | `async function ixt`, `/pronunciation/synthesize?format=mp3` | JSON-вызов произвольного TTS и возврат Blob |
| `webview/assets/start-voice-5f4e1ab7dcfd.js` | `pronunciation_hint`, `pronunciation_language`, `synthesizeAudio` | Передаёт в синтез текст и параметры языкового виджета |
| `webview/assets/app-initial-41d268a90bf8.js` | `new MediaRecorder`, `ondataavailable`, `new Blob` | Получает и собирает запись микрофона |
| Тот же renderer-бандл | `Fki`, `Rki`, `Bki`, `Vki` | Собирает multipart-байты и вызывает файловый `/transcribe` |
| Тот же renderer-бандл | `HPc`, `dictation_session_id`, `safePost` | FormData-вариант файловой диктовки с дополнительными метаданными |
| Тот же renderer-бандл | `Lki`, `Iki`, `Hki` | Повторы отдельных ошибок, JSON и схема результата |
| `webview/assets/app-shared-bedb2212942c.js` | `KNn`, `YNn`, `_dn`, `X-OpenAI-Attach-Auth` | Служебные маркеры renderer-запросов и общие HTTP headers |
| `.vite/build/src-ghAWefM3.js` | `Ds`, `X-OpenAI-Attach-Integrity-State` | Извлекает и удаляет внутренние attach-маркеры из headers |
| `.vite/build/main-DPn4U9E8.js` | `bue`, `xue`, `Sue`, `Cue`, `getAuthToken` | Принимает запрос renderer, получает токен и добавляет авторизацию |
| Тот же main-бандл | `applicationNetwork.fetch`, `host.auth_refresh`, `retriedAfter401` | Отправляет HTTP и при разрешённом 401 обновляет токен и повторяет запрос |

SHA-256 архива, прочитанного 30 сентября:
`bd287f7b0e78d5505978162cbf17e2914c3bf41ae156e3f1f53826318c54dcdd`.
Этот снимок отличается от SHA в отчёте TTS за 29 сентября. Результаты старых
живых проб не выдаются здесь за новую проверку удалённых сервисов; контракт
маршрутов и перечисленные цепочки повторно сверены с текущим кодом.

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

В первоначальной версии документа в двух скриптах добавлены русские комментарии;
затем живые пробы 30 сентября привели к исправлению summary format и добавлению
набора Desktop headers. Для TTS проверены контракты, чтение входа, redirect,
неверный MIME, HTTP 403 и отказ от перезаписи. Вложенный Python из Bash-клиента отдельно проверяется с
синтетическими auth.json и записью, подменённым HTTP-клиентом и без сети:
URL, метод, заголовки, исходные байты, запрет proxy и stdout.

После дополнений 30 сентября прошли 26 тестов TTS-демонстраций и 7 voice-probe
тестов, а для трёх изолированных скриптов — ещё 17 тестов: **50 суммарно**.
Эти тесты не доказывают доступность удалённого сервиса: живые результаты
перечислены отдельно выше. При добавлении трёх разделов все 50 тестов
прогнаны повторно и прошли. Дополнительно проверены 58 локальных ссылок,
включая 28 ссылок со строками кода, синтаксис 10 Bash-блоков и dry-run трёх
изолированных клиентов. Три новые схемы отрисованы и просмотрены визуально;
credentials для этих проверок не читались. Отдельная несетевая проверка исполнила Python именно из
`chatgpt-transcribe.sh`: успешный ответ и отказы при API-key входе, отсутствии
записи, отсутствии поля text и HTTP 403. Значения credentials в этих
проверках синтетические. Новый файл `raven.flac` в удалённый сервис в этом
исследовании не отправлялся; полученные Realtime/app-server WAV проверены
удалённым `/transcribe`. Desktop не запускался и не перезапускался.

Ссылки на изображения и карту кода используют абсолютные пути текущей
машины, чтобы открываться в Codex. Если переносите документ на другой
компьютер, замените префикс `./` на путь
своего клона и скопируйте каталог `docs/audio-http-diagrams/` вместе с ним.

Схемы отрисованы из Graphviz DOT и визуально проверены. Исходники
[tts-flow.dot](#archive-availability),
[stt-flow.dot](#archive-availability),
[wire-format.dot](#archive-availability),
[isolated-realtime.dot](#archive-availability),
[isolated-read-aloud.dot](#archive-availability),
[isolated-appserver.dot](#archive-availability) можно редактировать
и повторно преобразовать в PNG:

```bash
# Выполняйте из корня распакованного комплекта.
source ./scripts/audio-textbook/env.sh
cd "$repo"
dot -Tpng docs/audio-http-diagrams/tts-flow.dot -o docs/audio-http-diagrams/tts-flow.png
dot -Tpng docs/audio-http-diagrams/stt-flow.dot -o docs/audio-http-diagrams/stt-flow.png
dot -Tpng docs/audio-http-diagrams/wire-format.dot -o docs/audio-http-diagrams/wire-format.png
dot -Tpng docs/audio-http-diagrams/isolated-realtime.dot -o docs/audio-http-diagrams/isolated-realtime.png
dot -Tpng docs/audio-http-diagrams/isolated-read-aloud.dot -o docs/audio-http-diagrams/isolated-read-aloud.png
dot -Tpng docs/audio-http-diagrams/isolated-appserver.dot -o docs/audio-http-diagrams/isolated-appserver.png
python3 -W error::ResourceWarning -m unittest discover -s scripts/remote-tts-isolated -p 'test_*.py' -v
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
bash -n scripts/voice-probes/chatgpt-transcribe.sh --auth-file "$auth_file"
```

Полученный ранее HTTP 403 нельзя универсально объяснять одним параметром.
Для STT контрольная Python-проба 29 сентября показала успех после замены
User-Agent; для TTS Python успешно получил MP3, тогда как curl получил 403.
Конкретные правила Cloudflare и стабильность приватных endpoints неизвестны.
При истёкшем входе эти скрипты не выполняют собственный login или refresh.
Для нового живого результата нужно снова выполнить файловые команды и
проверить полученное аудио или текст; прежний успешный результат не является
гарантией бессрочного доступа к приватному API.
