Потоковый ответ: что делать с частичным текстом и обрывом
Чтение SSE по частям, финальные usage и billing, отмена чтения и сохранение запроса после разрыва соединения.
Что проверено. Парсер streaming.mjs проверен локально: кириллица по одному байту, LF/CRLF, финальные usage/billing, [DONE], ошибка и неполный поток.
Проверен текстовый парсер на синтетических событиях. Скорость модели, сетевые таймауты production и tool_calls через этот пример не измерены.
Сетевой кусок не равен одному событию
SSE может разрезать русский символ, строку JSON или разделитель между двумя чтениями. Используйте TextDecoder в потоковом режиме и собирайте события до пустой строки. Не вызывайте JSON.parse на каждом сетевом chunk.
Скачиваемый парсер сохраняет остаток между чтениями и обрабатывает несколько data-строк одного события. Он принимает только успешный text/event-stream, выводит текстовые delta и требует финальные usage, billing.status=settled и [DONE]. Инструменты намеренно передаются отдельному обработчику приложения.
import { readTextStream } from './streaming.mjs';
// response получен из исходного POST с stream:true
const result = await readTextStream(response, text => process.stdout.write(text));
console.log();
console.log('Расход в микрорублях:', result.usage.cost_microrub);Ошибка после начала потока приходит внутри SSE
После отправки HTTP-заголовков сервер уже не может заменить успешный статус обычным JSON-ответом. Поэтому обработчик проверяет error в событиях. Покажите частичный текст, но пометьте запрос как незавершённый, пока не получены окончательные данные.
Не списывайте деньги в интерфейсе по числу полученных символов. Источник суммы и состояния расчёта находится на сервере. Отдельно сохраняйте идентификатор запроса и признак неполного потока.
Остановка чтения не отменяет платную обработку
AbortController прекращает ожидание клиента. Поставщик мог уже принять запрос, поэтому закрытие вкладки не доказывает отмену и не освобождает резерв.
После обрыва сохраните исходный ключ, endpoint, тело и Idempotency-Key. Не создавайте новый ID при нажатии «восстановить». Повтор той же операции возвращает существующий результат или состояние сверки. Для новой формулировки задания нужен новый логический запрос и отдельный лимит.
Источники и повторная проверка
При изменении API-контракта, доступных параметров, тарифа или версии клиента пример нужно проверить повторно. Редакционная правка сама по себе не подтверждает новый технический тест.