open-sse Architecture (Русский)
Зачем нужен отдельный пакет рабочего пространства?
Заголовок раздела «Зачем нужен отдельный пакет рабочего пространства?»open-sse/ является автономным рабочим пространством в монорепозитории OmniRoute по нескольким причинам:
- Повторное использование —
open-sseпубликуется в npm как@omniroute/open-sse, поэтому другие проекты могут использовать его независимо - Чёткие границы — потоковое ядро отделено от специфичного для OmniRoute слоя пользовательского интерфейса и базы данных
- Производительность — ядро не зависит от Next.js, что обеспечивает более быстрый холодный запуск в средах CLI и serverless
- Управление версиями —
open-sseможет выпускать релизы по собственному графику
"workspaces": ["open-sse"]Структура верхнего уровня
Заголовок раздела «Структура верхнего уровня»open-sse/├── index.ts # Публичная точка входа├── types.d.ts # Экспорт публичных типов├── package.json # @omniroute/open-sse├── config/ # Конфигурации провайдеров, константы, реестры├── executors/ # HTTP-исполнители для отдельных провайдеров (67 + base.ts/index.ts)├── handlers/ # Обработчики запросов (chatCore, responses и т. д.)├── lib/ # Внутренние утилиты├── mcp-server/ # Сервер Model Context Protocol├── services/ # Около 298 сервисных модулей├── transformer/ # Преобразователь формата Responses API├── translator/ # Преобразование форматов (OpenAI ↔ Claude ↔ Gemini)└── utils/ # Общие утилиты (журналирование, ошибки, потоки и т. д.)Количество модулей
Заголовок раздела «Количество модулей»| Каталог | Файлы | Назначение |
| executors/ | 167 | HTTP-исполнители для отдельных провайдеров (унифицированы через фабрику DefaultExecutor) |
| handlers/ | 157 | Точки входа запросов (chatCore, responses, embeddings) |
| services/ | ~536 | Маршрутизация, кэширование, ограничение частоты запросов, обновление и т. д. |
| translator/ | 56 | Преобразование форматов (OpenAI ↔ Claude ↔ Gemini) |
| mcp-server/ | 44 | Инструменты и транспорты MCP |
| utils/ | ~108 | Сквозные утилиты (журналирование, ошибки, потоки) |
| config/ | ~339 | Конфигурации провайдеров, константы, реестры |
Конвейер обработки запросов
Заголовок раздела «Конвейер обработки запросов»Каждый запрос к LLM проходит через пятиэтапный конвейер:
┌──────────────┐ HTTP-запрос │ 1. МАРШРУТ │ разрешение комбинации, выбор модели (маршрут Next.js) └──────┬───────┘ │ ▼ ┌──────────────┐ │2. ПРЕОБРАЗОВ.│ преобразование форматов (OpenAI ↔ Claude ↔ Gemini) └──────┬───────┘ │ ▼ ┌──────────────┐ │ 3. ВЫПОЛНЕНИЕ│ исполнитель провайдера, HTTP, повтор, предохранитель └──────┬───────┘ │ ▼ ┌──────────────┐ │ 4. ПОТОК │ преобразование SSE, обратное давление └──────┬───────┘ │ ▼ ┌──────────────┐ │ 5. ЗАПИСЬ │ учёт использования, журнал вызовов, классификация ошибок └──────┬───────┘ │ ▼ HTTP-ответ (SSE или JSON)Этап 1: Маршрутизация (services/combo.ts)
Заголовок раздела «Этап 1: Маршрутизация (services/combo.ts)»Точка входа: handleComboChat() в services/combo.ts
Сопоставляет запрос с конкретным кортежем (provider, model, account, credentials):
- Находит комбинацию по идентификатору (или создаёт виртуальную комбинацию для моделей
auto/*) - Применяет стратегию маршрутизации (приоритетную, взвешенную, циклическую и т. д.)
- Отфильтровывает неработоспособных провайдеров (предохранитель)
- Выбирает следующую подходящую цель
Для моделей auto/* на этом этапе также выполняется следующее:
- Запускается алгоритм 16-факторной оценки (
services/autoCombo/) - Выбирается пара
provider+modelна основе работоспособности, стоимости, задержки и других факторов
Этап 2: Преобразование (translator/)
Заголовок раздела «Этап 2: Преобразование (translator/)»Если исходный формат (например, OpenAI) отличается от целевого формата (например, Claude), запрос преобразуется:
- Системная подсказка → системное сообщение
- Определения инструментов → формат инструментов конкретного провайдера
- Параметры рассуждения/мышления → соответствующие параметры конкретного провайдера
- Нормализация ролей сообщений (
developer→systemдля провайдеров, отличных от OpenAI)
translator/index.ts экспортирует:
translateRequest(body, sourceFormat, targetFormat): TranslatedRequestneedsTranslation(source, target): booleanЭтап 3: Выполнение (executors/)
Заголовок раздела «Этап 3: Выполнение (executors/)»Точка входа: getExecutor(providerId).execute(request, options)
Все провайдеры используют DefaultExecutor (executors/default.ts) через резервный механизм фабрики getExecutor(). Исполнитель:
- Формирует URL вышестоящего сервиса (
buildUrl()) - Добавляет заголовки, специфичные для провайдера (
buildHeaders()) - Преобразует тело запроса (
transformRequest()) - Отправляет HTTP-запрос с повторными попытками и экспоненциальной задержкой
- При необходимости обновляет данные аутентификации (для провайдеров OAuth)
Все исполнители расширяют BaseExecutor (executors/base.ts, 1170 строк кода), который предоставляет:
- Общую логику повторных попыток
- Интеграцию с прокси
- Интеграцию с предохранителем
- Хуки для записи данных об использовании
Этап 4: Потоковая передача (utils/stream.ts)
Заголовок раздела «Этап 4: Потоковая передача (utils/stream.ts)»Для потоковых ответов исполнитель возвращает ReadableStream. Обработчик:
- Пропускает данные через преобразователь SSE (
createSSETransformStreamWithLogger) - Использует периодические сигналы для обнаружения разорванных соединений
- Корректно обрабатывает отключение клиента (
pipeWithDisconnect) - Преобразует SSE → JSON для клиентов, не поддерживающих потоковую передачу
Для непотоковых ответов исполнитель возвращает разобранный JSON-объект, который передаётся дальше без изменений.
Этап 5: Запись (services/usage.ts)
Заголовок раздела «Этап 5: Запись (services/usage.ts)»После ответа (успешного или с ошибкой) данные об использовании регистрируются:
prompt_tokens,completion_tokens,cached_tokensиз ответаcost_usd, рассчитанная на основе данных о ценахlatency_ms,status,error_classв случае ошибки- Сохраняются в таблице
usage_history
Артефакты журнала вызовов (если эта функция включена) записываются в ${DATA_DIR}/call_logs/.
Подробный разбор ключевых файлов
Заголовок раздела «Подробный разбор ключевых файлов»chatCore.ts (5977 строк)
Заголовок раздела «chatCore.ts (5977 строк)»Основной обработчик запросов. Несмотря на свой размер, он имеет чёткую структуру:
// Псевдоструктура chatCore.tsexport async function handleChat(request: NextRequest) { // 1. Аутентификация + CORS await authenticateRequest(request); applyCorsHeaders(response);
// 2. Проверка тела запроса const body = await parseRequestBody(request);
// 3. Определение формата + преобразование const sourceFormat = detectFormat(request); const targetFormat = getTargetFormat(providerId); if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat); }
// 4. Маршрутизация комбо const targets = await resolveComboTargets(comboId, body); for (const target of targets) { try { const result = await executeOnTarget(target, body); await recordUsage(result); return result; } catch (err) { // Переход к следующей цели } }
// 5. Аварийный резервный вариант return await emergencyFallback(body);}Несмотря на то что это одна гигантская функция, она организована в секции с комментариями, соответствующие пяти этапам конвейера.
combo.ts (4456 строк кода)
Заголовок раздела «combo.ts (4456 строк кода)»Механизм маршрутизации, который преобразует комбо в упорядоченный список целей.
export async function handleComboChat(body, comboId): Promise<ChatResult> { const targets = await resolveComboTargets(comboId, body); for (const target of targets) { try { return await handleSingleModel(target, body); } catch (err) { log.warn("цель недоступна, пробуем следующую", { target, err }); } } throw new ComboExhaustedError("Все цели недоступны");}Поддерживает 19 стратегий маршрутизации (см. src/shared/constants/routingStrategies.ts):
| Стратегия | Поведение |
|---|---|
priority |
Упорядоченный список с приоритетом первой цели |
weighted |
Вероятностный выбор по весу каждой цели |
round-robin |
Циклический перебор целей по порядку |
context-relay |
Передача контекста между целями |
fill-first |
Исчерпание квоты перед переходом к следующей цели |
p2c |
Выбор лучшей из двух целей |
random |
Равномерный случайный выбор |
least-used |
Выбор цели с наименьшим количеством недавних обращений |
cost-optimized |
Сначала выбирается самая дешёвая работоспособная цель |
reset-aware |
Учёт периодов сброса лимитов провайдера |
reset-window |
Маршрутизация на основе периода сброса |
headroom |
Сначала выбирается цель с наибольшим остатком квоты |
strict-random |
Полностью равномерный выбор (без взвешивания по качеству) |
auto |
Использование оценки по 16 факторам (autoCombo/) |
lkgp |
Сначала используется последний заведомо работоспособный провайдер |
context-optimized |
Оптимальный выбор для запросов с длинным контекстом |
fusion |
Параллельная отправка группе целей с последующим синтезом через судью (fusion.ts) |
base.ts (1170 строк кода)
Заголовок раздела «base.ts (1170 строк кода)»Абстрактный исполнитель, от которого наследуются все 107 исполнителей. Он содержит:
buildUrl()— стандартное построение URL (подклассы переопределяют для нестандартных случаев)buildHeaders()— стандартные заголовки (аутентификация, тип содержимого)transformRequest()— по умолчанию передаёт запрос без измененийexecute()— основной цикл HTTP-запросов с повторными попытками, задержкой и предохранителем
export class DefaultExecutor extends BaseExecutor { // Обрабатывает всех провайдеров, совместимых с OpenAI/Anthropic // Провайдеры регистрируют конфигурации (URL, аутентификацию, заголовки), но используют общую логику исполнителя}Специфичное для провайдера поведение (заголовки аутентификации, базовый URL, заголовки версии) настраивается через реестр провайдеров, а не через отдельные классы исполнителей.
---
## Сервисы (117 модулей)
Сервисы — это **специализированные модули, предназначенные для одной конкретной задачи**, которые компонуются обработчиками. Основные категории:
### Маршрутизация и Combo
- `combo.ts` — точка входа для запросов с Combo-маршрутизацией- `services/autoCombo/` — оценка по 16 факторам, 8 стратегий автоматической маршрутизации- `wildcardRouter.ts` — сопоставляет маршруты с подстановочными знаками (`gpt-*`)- `modelFamilyFallback.ts` — резервное переключение внутри семейства T5
### Ограничение частоты запросов и квоты
- `rateLimitManager.ts` — алгоритм корзины токенов для каждой пары ключ+провайдер- `usage.ts` — регистрация использования- `quotaCache.ts` — хранящиеся в памяти снимки квот
### Учётная запись и токен
- `tokenRefresh.ts` — обновление OAuth при ответе 401- `accountFallback.ts` — переключение на альтернативную учётную запись- `sessionManager.ts` — состояние многоходовой сессии
### Интеллектуальные функции
- `intentClassifier.ts` — классификация намерения запроса- `taskAwareRouter.ts` — маршрутизация по типу задачи- `thinkingBudget.ts` — распределение токенов рассуждения- `contextManager.ts` — внедрение контекста маршрутизации
### Отказоустойчивость
- `resilience.ts` — оркестрация повторных попыток, задержек и автоматического выключателя- `emergencyFallback.ts` — резервный вариант на крайний случай- `modelDeprecation.ts` — автоматическая маршрутизация на модели-преемники
### Состояние
- `signatureCache.ts` — дедупликация по сигнатуре запроса- `volumeDetector.ts` — сброс нагрузки- `contextHandoff.ts` — сериализация сессии
### Сжатие
- `compression/` (подкаталог) — полный конвейер сжатия- 39 файлов, охватывающих движки, наборы правил и адаптеры
### Навыки
- (описаны в [SKILLS.md](./SKILLS.md))
### Память
- (описана в [MEMORY.md](./MEMORY.md))
---
## Исполнители (75+ файлов)
По одному файлу на каждого провайдера. Все они расширяют `BaseExecutor` и переопределяют различающееся поведение.
### Общие шаблоны
Провайдеры разрешаются через `getExecutor(providerId)`, который возвращает настроенный исполнитель. Провайдеры, совместимые с OpenAI/Anthropic, используют `DefaultExecutor` (`executors/default.ts`). Специфичное для провайдера поведение (базовый URL, заголовки аутентификации, версия API) настраивается в `open-sse/config/providers/`, а преобразования тела запроса выполняются в `open-sse/translator/`.
**Пользовательский URL** задаётся через конфигурацию провайдера:
```ts// Конфигурация провайдера в open-sse/config/providers/export default { id: "together", baseURL: "https://api.together.xyz/v1/chat/completions",}Пользовательская аутентификация обрабатывается через конфигурацию аутентификации реестра провайдеров (ключ API, OAuth, профили заголовков).
Преобразования пользовательского тела запроса (например, отделение system от messages для Anthropic) регистрируются для каждого провайдера в open-sse/translator/.
### Фабрика исполнителей
`executors/index.ts` экспортирует `getExecutor(providerId)`:
```tsimport { getExecutor } from "@omniroute/open-sse/executors";
const executor = getExecutor("anthropic");const result = await executor.execute({ model: "claude-sonnet-4-5", messages: [...],});Разрешение выполняется через ExecutorRegistry (executors/registry.ts): каждый специализированный исполнитель объявляется во встроенной таблице executors/index.ts и регистрируется через registerExecutor(alias, instance) при загрузке модуля; getExecutor() обращается к реестру и использует мемоизированный DefaultExecutor в качестве резервного варианта для любого провайдера без специализированной записи. Полное сопоставление псевдонимов с исполнителями зафиксировано эталонным тестом tests/unit/executor-map-golden.test.ts.
Трансляторы
Заголовок раздела «Трансляторы»Преобразование между 3 форматами: OpenAI, Anthropic, Gemini, а также новым Responses API.
Когда выполняется преобразование
Заголовок раздела «Когда выполняется преобразование»import { needsTranslation, translateRequest } from "@omniroute/open-sse/translator";
if (needsTranslation(sourceFormat, targetFormat)) { body = translateRequest(body, sourceFormat, targetFormat);}Распространённые преобразования:
OpenAI → Anthropic: отдельное полеsystem, заголовокx-api-keyOpenAI → Gemini:contentsвместоmessages,systemInstructionOpenAI → Responses API: массивinput, состояниеprevious_response_id
Обрабатываемые пограничные случаи
Заголовок раздела «Обрабатываемые пограничные случаи»- Роль
developer→systemдля систем, отличных от OpenAI - Роль
system→ объединяется с первым сообщением пользователя для GLM/ERNIE json_schema→responseMimeType+responseSchemaв Geminitools→ формат инструментов, специфичный для провайдера- Параметры рассуждения (o1, Claude) → эквиваленты, специфичные для провайдера
Сервер MCP
Заголовок раздела «Сервер MCP»open-sse/mcp-server/ реализует сервер Model Context Protocol:
- 110 инструментов (управление провайдерами, комбинации, память, кэш, сжатие, прокси, навыки, геймификация, плагины, Notion, Obsidian, локальный корпус)
- 3 транспорта: stdio, SSE, Streamable HTTP
- 33 области доступа для детального управления авторизацией
Регистрация инструментов
Заголовок раздела «Регистрация инструментов»Инструменты регистрируются как отдельные файлы в open-sse/mcp-server/tools/, каждый из которых экспортирует имя, схему, обработчик и область доступа:
import { z } from "zod";export default { name: "omniroute_get_health", description: "Получить снимок состояния системы", scope: "read:health", inputSchema: z.object({}), handler: async (_args, ctx) => { return await getSystemHealth(); },};Транспорты
Заголовок раздела «Транспорты»// stdio (использование в CLI)startMcpStdio(server);
// SSE (потоковая передача на основе HTTP)startMcpSse(server, port);
// Streamable HTTP (современный MCP)startMcpStreamable(server, port);Авторизация
Заголовок раздела «Авторизация»Каждый вызов инструмента проходит проверку областей доступа (open-sse/mcp-server/auth/):
if (!hasScope(apiKey, "providers:read")) { throw new Error("Недостаточная область доступа");}Преобразователи
Заголовок раздела «Преобразователи»open-sse/transformer/ выполняет преобразование между форматами Chat Completions и Responses API.
Зачем нужен отдельный преобразователь?
Заголовок раздела «Зачем нужен отдельный преобразователь?»Responses API — это новый формат OpenAI с диалогами с сохранением состояния (previous_response_id). Когда клиент отправляет запрос Responses, OmniRoute:
- Внутренне преобразует Responses → Chat Completions
- Отправляет запрос провайдеру (любому провайдеру, поддерживающему Chat Completions)
- Преобразует ответ обратно в формат Responses
- Передаёт преобразованный ответ клиенту в потоковом режиме
Преобразователь (transformer/responsesTransformer.ts) предоставляет:
createResponsesApiTransformStream(): TransformStreamОн обрабатывает:
- События
response.output_item.added - События
response.output_text.delta - Событие
response.completed - Сопоставление вызовов инструментов (
function_call↔tool_calls)
Конфигурация
Заголовок раздела «Конфигурация»open-sse/config/ содержит слой конфигурации:
| Файл | Назначение |
|---|---|
providerRegistry.ts |
Реестр чат-моделей на основе каталога из 352 провайдеров |
providerModels.ts |
Псевдонимы моделей, сопоставление форматов |
constants.ts |
Тайм-ауты, ограничения, коды состояния |
defaultThinkingSignature.ts |
Сигнатура рассуждений Claude по умолчанию |
modelStrip.ts (в сервисах) |
Удаление полей для отдельных провайдеров |
Схема реестра провайдеров
Заголовок раздела «Схема реестра провайдеров»interface ProviderConfig { id: string; name: string; baseUrl: string; authType: "bearer" | "api-key" | "oauth" | "cookie"; executorClass: string; defaultModel: string; capabilities: ProviderCapabilities; models: ModelDefinition[];}Проверка с помощью Zod при загрузке модуля гарантирует корректность всех конфигураций провайдеров.
Ограничения производительности
Заголовок раздела «Ограничения производительности»Механизм маршрутизации имеет строгие ограничения по производительности:
| Операция | Целевое значение | Измерение |
|---|---|---|
| Разрешение комбинации | <10ms | Для 50 целей |
| Проверка ограничения частоты запросов | <1ms | Бакет токенов в памяти |
| Резервный выбор семейства моделей | <5ms | Кешированные определения семейств |
| Диспетчеризация маршрутизации запроса | <2ms | Критический путь |
| Без блокирующего ввода-вывода в критическом пути маршрутизации | — | Всё асинхронно |
Антипаттерны
Заголовок раздела «Антипаттерны»❌ Синхронные вызовы БД в combo.ts — выполняйте предварительные вычисления и кешируйте
❌ Логика повторных попыток в обработчиках — используйте retry() из сервиса обеспечения отказоустойчивости
❌ Прямой доступ к конфигурации провайдера — используйте геттеры providerRegistry
❌ Жёстко заданные цепочки резервного выбора — определяйте их в modelFamilyFallback.ts
❌ Изменения состояния между конкурентными запросами — используйте только контекст, ограниченный областью запроса
Добавление нового компонента
Заголовок раздела «Добавление нового компонента»Добавление нового сервиса
Заголовок раздела «Добавление нового сервиса»- Создайте
open-sse/services/[serviceName].tsс чётко определённой зоной ответственности - Экспортируйте основную функцию-обработчик и все необходимые константы
- Добавьте модульные тесты в
tests/unit/services/[serviceName].test.mjs - Интегрируйте сервис в конвейер обработки запросов в
handlers/chatCore.ts(если он связан с маршрутизацией) - Обновите логику маршрутизации в
combo.ts, если сервис влияет на выбор цели - Добавьте документацию в этот файл
Добавление нового исполнителя
Заголовок раздела «Добавление нового исполнителя»- Создайте
open-sse/executors/[provider].ts, расширяющийBaseExecutor - Зарегистрируйте его в
config/providerRegistry.ts - Добавьте его в фабрику
executors/index.ts - Добавьте модульные тесты для исполнителя
- Добавьте документацию в
docs/architecture/ARCHITECTURE.md
Добавление нового инструмента MCP
Заголовок раздела «Добавление нового инструмента MCP»- Создайте или обновите
open-sse/mcp-server/tools/[category]Tools.ts - Определите схему Zod для входных данных
- Зарегистрируйте инструмент в
mcp-server/index.ts - Добавьте его в матрицу областей доступа в
mcp-server/auth/ - Добавьте модульные тесты
См. также
Заголовок раздела «См. также»- ARCHITECTURE.md — высокоуровневая архитектура
- CODEBASE_DOCUMENTATION.md — инженерный справочник
- REPOSITORY_MAP.md — описание каждого каталога
- AUTO-COMBO.md — оценка по 16 факторам
- MCP-SERVER.md — сервер MCP
- A2A-SERVER.md — сервер A2A
- Исходный код:
open-sse/(более 400 файлов, ~143 тыс. строк кода)
HagiCode
HagiCode — агентная среда разработки со структурированными процессами, параллельным выполнением несколькими агентами и интерфейсами Hero Dungeon.
Превращайте идеи в полезное ПО с более умным, быстрым и увлекательным агентным рабочим процессом.

- SmartСтруктурированные процессы превращают намерение в исполнимый путь от идеи до готового изменения.
- EfficientМультиагентные процессы параллельно продвигают исследование, реализацию и проверку.
- FunHero Dungeon делает длительную совместную разработку наглядной и увлекательной.