From d910180772aff500feaf19d3805eb5e305630792 Mon Sep 17 00:00:00 2001 From: agent_vscode Date: Mon, 6 Jul 2026 18:02:37 +0300 Subject: [PATCH] docs(readme): reverse-proxy requirements for SSE streaming paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The AI chat SSE endpoints (POST /api/ai-chat/stream, GET /api/ai-chat/runs//stream, POST /api/shares/ai/stream) must bypass response buffering AND compression at every proxy in front of the app — a compressing proxy silently buffers SSE frames until the response closes (pending request, tokens arriving in one burst, reloaded tabs degrading to polling). Document the affected paths, the DevTools tell (Content-Encoding on text/event-stream), and concrete nginx/Traefik configuration in both READMEs. Co-Authored-By: Claude Fable 5 --- README.md | 26 ++++++++++++++++++++++++++ README.ru.md | 26 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/README.md b/README.md index d2b471de..bf6a46ae 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,32 @@ Gitmost follows the upstream Docmost setup. See the Docmost [documentation](https://docmost.com/docs) for self-hosting and development instructions; replace the `docmost/docmost` image with `ghcr.io/vvzvlad/gitmost` where applicable. +### Reverse proxy: SSE streaming paths + +The AI agent streams its answers over Server-Sent Events. These endpoints produce a +long-lived `text/event-stream` response and **must bypass response buffering AND response +compression** at every proxy in front of the app: + +- `POST /api/ai-chat/stream` — the live agent turn stream +- `GET /api/ai-chat/runs//stream` — attach/resume of a detached agent run + (`AI_CHAT_RESUMABLE_STREAM`) +- `POST /api/shares/ai/stream` — the anonymous public-share assistant + +A buffering or compressing proxy does not break these with an error — it silently ruins them: +the request hangs in `pending`, tokens stop streaming and arrive in one burst when the turn +ends, or a reloaded tab falls back to coarse polling. The tell in DevTools is a +`Content-Encoding: gzip/zstd` response header on a `text/event-stream` response. + +The server already sends `X-Accel-Buffering: no` (honored by nginx unless ignored), but +compression middleware is applied by proxy configuration, not headers: + +- **nginx** — `proxy_buffering off; proxy_cache off; gzip off;` for these locations, e.g. + `location ~ ^/api/(ai-chat/(stream$|runs/.+/stream$)|shares/ai/) { ... }` +- **Traefik** — route these paths through a dedicated router **without** the `compress` + middleware (a `compress` middleware buffers SSE frames until the response closes), e.g. + ``PathPrefix(`/api/ai-chat/stream`) || PathPrefix(`/api/ai-chat/runs/`)``. Belt-and-braces: + `traefik.http.middlewares..compress.excludedcontenttypes: text/event-stream`. + ## Migration from Docmost Gitmost's database schema is a **strict superset** of Docmost's. Every Gitmost-specific migration diff --git a/README.ru.md b/README.ru.md index 62552fce..517c4802 100644 --- a/README.ru.md +++ b/README.ru.md @@ -126,6 +126,32 @@ Gitmost повторяет процесс установки upstream-Docmost. смотрите в [документации](https://docmost.com/docs) Docmost; где это применимо, заменяйте образ `docmost/docmost` на `ghcr.io/vvzvlad/gitmost`. +### Reverse proxy: SSE-стриминговые пути + +AI-агент стримит ответы через Server-Sent Events. Эти эндпоинты отдают долгоживущий +`text/event-stream`-ответ и **обязаны обходить буферизацию И сжатие ответов** на каждом +прокси перед приложением: + +- `POST /api/ai-chat/stream` — живой стрим хода агента +- `GET /api/ai-chat/runs//stream` — подключение/резюм detached-рана + (`AI_CHAT_RESUMABLE_STREAM`) +- `POST /api/shares/ai/stream` — анонимный ассистент публичных шар + +Буферизующий или сжимающий прокси не ломает эти пути с ошибкой — он тихо их портит: +запрос висит в `pending`, токены не стримятся и вываливаются одним куском в конце хода, +а перезагруженная вкладка падает в грубый поллинг. Диагностический признак в DevTools — +заголовок `Content-Encoding: gzip/zstd` на ответе с `text/event-stream`. + +Сервер уже шлёт `X-Accel-Buffering: no` (nginx учитывает его по умолчанию), но +compression-мидлвари управляются конфигом прокси, а не заголовками: + +- **nginx** — `proxy_buffering off; proxy_cache off; gzip off;` для этих location, + например `location ~ ^/api/(ai-chat/(stream$|runs/.+/stream$)|shares/ai/) { ... }` +- **Traefik** — вести эти пути через отдельный роутер **без** `compress`-мидлвари + (compress буферизует SSE-кадры до закрытия ответа), например + ``PathPrefix(`/api/ai-chat/stream`) || PathPrefix(`/api/ai-chat/runs/`)``. Для надёжности: + `traefik.http.middlewares..compress.excludedcontenttypes: text/event-stream`. + ## Миграция с Docmost Схема БД Gitmost — это **строгий superset** схемы Docmost. Все Gitmost-специфичные миграции только